From 75313f5a407bb6021c304029c64b9a6439e14b0f Mon Sep 17 00:00:00 2001 From: Jeff Lewis Date: Tue, 22 Nov 2022 10:52:22 -0700 Subject: [PATCH] change client middleware --- .../markdown/06-guides/endpoint-middleware.md | 54 +++++++++++-------- modules/guides/src/smithy4s/guides/Auth.scala | 6 +-- .../src/smithy4s/guides/AuthClient.scala | 14 ++--- .../http4s/ClientEndpointMiddleware.scala | 51 ++++++++++++++++++ ...e.scala => ServerEndpointMiddleware.scala} | 10 ++-- .../http4s/SimpleProtocolBuilder.scala | 13 +++-- .../http4s/SmithyHttp4sReverseRouter.scala | 2 +- .../smithy4s/http4s/SmithyHttp4sRouter.scala | 2 +- .../SmithyHttp4sClientEndpoint.scala | 8 ++- .../SmithyHttp4sServerEndpoint.scala | 4 +- .../EndpointSpecificMiddlewareSpec.scala | 43 +++++++++++++-- 11 files changed, 149 insertions(+), 58 deletions(-) create mode 100644 modules/http4s/src/smithy4s/http4s/ClientEndpointMiddleware.scala rename modules/http4s/src/smithy4s/http4s/{EndpointSpecificMiddleware.scala => ServerEndpointMiddleware.scala} (87%) diff --git a/modules/docs/markdown/06-guides/endpoint-middleware.md b/modules/docs/markdown/06-guides/endpoint-middleware.md index 351874df1..33163c2ba 100644 --- a/modules/docs/markdown/06-guides/endpoint-middleware.md +++ b/modules/docs/markdown/06-guides/endpoint-middleware.md @@ -9,9 +9,9 @@ As of version `0.17.x` of smithy4s, we have changed this by providing a new mech In this guide, we will show how you can implement a smithy4s middleware that is aware of the authentication traits in your specification and is able to implement authenticate on an endpoint-by-endpoint basis. This is useful if you have different or no authentication on one or more endpoints. -## EndpointSpecificMiddleware +## ServerEndpointMiddleware / ClientEndpointMiddleware -`EndpointSpecificMiddleware` is the interface that we have provided for implementing middleware. For some use cases, you will need to use the full interface. However, for this guide and for many uses cases, you will be able to rely on the simpler interface called `EndpointSpecificMiddlewareSpec.Simple`. This interface requires a single method which looks as follows: +`ServerEndpointMiddleware` is the interface that we have provided for implementing service middleware. For some use cases, you will need to use the full interface. However, for this guide and for many use cases, you will be able to rely on the simpler interface called `ServerEndpointMiddleware.Simple`. This interface requires a single method which looks as follows: ```scala def prepareWithHints( @@ -22,6 +22,15 @@ def prepareWithHints( This means that given the hints for the service and a specific endpoint, our implementation will provide a transformation of an `HttpApp`. If you are not familiar with `Hints`, they are the smithy4s construct that represents Smithy Traits. They are called hints to avoid naming conflicts and confusion with Scala `trait`s. +The `ClientEndpointMiddleware` interface is essentially the same as the one for `ServerEndpointMiddleware` with the exception that we are returning a transformation on `Client[F]` instead of `HttpApp[F]`. This looks like: + +```scala +def prepareWithHints( + serviceHints: Hints, + endpointHints: Hints + ): Client[F] => Client[F] +``` + ## Smithy Spec Let's look at the smithy specification that we will use for this guide. First, let's define the service. @@ -104,7 +113,7 @@ import org.http4s._ import smithy4s.http4s.SimpleRestJsonBuilder import smithy4s._ import org.http4s.headers.Authorization -import smithy4s.http4s.EndpointSpecificMiddleware +import smithy4s.http4s.ServerEndpointMiddleware ``` #### AuthChecker @@ -170,16 +179,16 @@ Let's break down what we did above step by step. The step numbers below correspo 6. If the token was found to be valid, we pass the request into the `inputApp` from step 2 in order to get a response. 7. If the header was found to be invalid, we return the `NotAuthorizedError` that we defined in our smithy file above. -#### EndpointSpecificMiddleware.Simple +#### ServerEndpointMiddleware.Simple -Next, let's create our middleware by implementing the `EndpointSpecificMiddleware.Simple` interface we discussed above. +Next, let's create our middleware by implementing the `ServerEndpointMiddleware.Simple` interface we discussed above. ```scala mdoc:silent object AuthMiddleware { def apply( authChecker: AuthChecker // 1 - ): EndpointSpecificMiddleware[IO] = - new EndpointSpecificMiddleware.Simple[IO] { + ): ServerEndpointMiddleware[IO] = + new ServerEndpointMiddleware.Simple[IO] { private val mid: HttpApp[IO] => HttpApp[IO] = middleware(authChecker) // 2 def prepareWithHints( serviceHints: Hints, @@ -229,31 +238,36 @@ To see the **full code** example of what we walk through below, go [here](https: It is possible that you have a client where you want to apply a similar type of middleware that alters some part of a request depending on the endpoint being targeted. In this part of the guide, we will show how you can do this for a client using the same smithy specification we defined above. We will make it so our authentication token is only sent if we are targeting an endpoint which requires it. -#### EndpointSpecificMiddleware.Simple +#### ClientEndpointMiddleware.Simple The interface that we define for this middleware is going to look very similar to the one we defined above. This makes sense because this middleware is effectively the dual of the middleware above. +```scala mdoc:invisible +import org.http4s.client._ +import smithy4s.http4s.ClientEndpointMiddleware +``` + ```scala mdoc:silent object Middleware { - private def middleware(bearerToken: String): HttpApp[IO] => HttpApp[IO] = { // 1 - inputApp => - HttpApp[IO] { request => + private def middleware(bearerToken: String): Client[IO] => Client[IO] = { // 1 + inputClient => + Client[IO] { request => val newRequest = request.withHeaders( // 2 Authorization(Credentials.Token(AuthScheme.Bearer, bearerToken)) ) - inputApp(newRequest) + inputClient.run(newRequest) } } - def apply(bearerToken: String): EndpointSpecificMiddleware[IO] = // 3 - new EndpointSpecificMiddleware.Simple[IO] { + def apply(bearerToken: String): ClientEndpointMiddleware[IO] = // 3 + new ClientEndpointMiddleware.Simple[IO] { private val mid = middleware(bearerToken) def prepareWithHints( serviceHints: Hints, endpointHints: Hints - ): HttpApp[IO] => HttpApp[IO] = { + ): Client[IO] => Client[IO] = { serviceHints.get[smithy.api.HttpBearerAuth] match { case Some(_) => endpointHints.get[smithy.api.Auth] match { @@ -268,16 +282,12 @@ object Middleware { } ``` -1. Here we are creating an inner middleware function, just like we did above. The only difference is that this time we are adding a value to the request instead of extracting one from it. -2. Add the `Authorization` header to the request and pass it to the `inputApp` that we are transforming in this middleware. -3. This function is actually the *exact same* as the function for the middleware we implemented above. The only difference is that this apply method accepts a `bearerToken` as a parameter. This is the token that we will add into the `Authorization` header when applicable. +1. Here we are creating an inner middleware function, just like we did above. The only differences are that this time we are adding a value to the request instead of extracting one from it and we are operating on `Client` instead of `HttpApp`. +2. Add the `Authorization` header to the request and pass it to the `inputClient` that we are transforming in this middleware. +3. This function is actually the *exact same* as the function for the middleware we implemented above. The only differences are that this apply method accepts a `bearerToken` as a parameter and returns a function on `Client` instead of `HttpApp`. The provided `bearerToken` is what we will add into the `Authorization` header when applicable. #### SimpleRestJsonBuilder -```scala mdoc:invisible -import org.http4s.client._ -``` - As above, we now just need to wire our middleware into our actual implementation. Here we are constructing a client and specifying the middleware we just defined. ```scala mdoc:silent diff --git a/modules/guides/src/smithy4s/guides/Auth.scala b/modules/guides/src/smithy4s/guides/Auth.scala index 954f16fff..1c85b3804 100644 --- a/modules/guides/src/smithy4s/guides/Auth.scala +++ b/modules/guides/src/smithy4s/guides/Auth.scala @@ -26,7 +26,7 @@ import com.comcast.ip4s._ import smithy4s.http4s.SimpleRestJsonBuilder import smithy4s.Hints import org.http4s.headers.Authorization -import smithy4s.http4s.EndpointSpecificMiddleware +import smithy4s.http4s.ServerEndpointMiddleware final case class ApiToken(value: String) @@ -91,8 +91,8 @@ object AuthMiddleware { def apply( authChecker: AuthChecker - ): EndpointSpecificMiddleware[IO] = - new EndpointSpecificMiddleware.Simple[IO] { + ): ServerEndpointMiddleware[IO] = + new ServerEndpointMiddleware.Simple[IO] { private val mid: HttpApp[IO] => HttpApp[IO] = middleware(authChecker) def prepareWithHints( serviceHints: Hints, diff --git a/modules/guides/src/smithy4s/guides/AuthClient.scala b/modules/guides/src/smithy4s/guides/AuthClient.scala index e2cd30087..4c245c872 100644 --- a/modules/guides/src/smithy4s/guides/AuthClient.scala +++ b/modules/guides/src/smithy4s/guides/AuthClient.scala @@ -39,24 +39,24 @@ object AuthClient { object Middleware { - private def middleware(bearerToken: String): HttpApp[IO] => HttpApp[IO] = { - inputApp => - HttpApp[IO] { request => + private def middleware(bearerToken: String): Client[IO] => Client[IO] = { + inputClient => + Client[IO] { request => val newRequest = request.withHeaders( Authorization(Credentials.Token(AuthScheme.Bearer, bearerToken)) ) - inputApp(newRequest) + inputClient.run(newRequest) } } - def apply(bearerToken: String): EndpointSpecificMiddleware[IO] = - new EndpointSpecificMiddleware.Simple[IO] { + def apply(bearerToken: String): ClientEndpointMiddleware[IO] = + new ClientEndpointMiddleware.Simple[IO] { private val mid = middleware(bearerToken) def prepareWithHints( serviceHints: Hints, endpointHints: Hints - ): HttpApp[IO] => HttpApp[IO] = { + ): Client[IO] => Client[IO] = { serviceHints.get[smithy.api.HttpBearerAuth] match { case Some(_) => endpointHints.get[smithy.api.Auth] match { diff --git a/modules/http4s/src/smithy4s/http4s/ClientEndpointMiddleware.scala b/modules/http4s/src/smithy4s/http4s/ClientEndpointMiddleware.scala new file mode 100644 index 000000000..84f865662 --- /dev/null +++ b/modules/http4s/src/smithy4s/http4s/ClientEndpointMiddleware.scala @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2022 Disney Streaming + * + * Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://disneystreaming.github.io/TOST-1.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smithy4s +package http4s + +import org.http4s.client.Client + +// format: off +trait ClientEndpointMiddleware[F[_]] { + def prepare[Alg[_[_, _, _, _, _]]](service: Service[Alg])( + endpoint: Endpoint[service.Operation, _, _, _, _, _] + ): Client[F] => Client[F] +} +// format: on + +object ClientEndpointMiddleware { + + trait Simple[F[_]] extends ClientEndpointMiddleware[F] { + def prepareWithHints( + serviceHints: Hints, + endpointHints: Hints + ): Client[F] => Client[F] + + final def prepare[Alg[_[_, _, _, _, _]]](service: Service[Alg])( + endpoint: Endpoint[service.Operation, _, _, _, _, _] + ): Client[F] => Client[F] = + prepareWithHints(service.hints, endpoint.hints) + } + + def noop[F[_]]: ClientEndpointMiddleware[F] = + new ClientEndpointMiddleware[F] { + override def prepare[Alg[_[_, _, _, _, _]]](service: Service[Alg])( + endpoint: Endpoint[service.Operation, _, _, _, _, _] + ): Client[F] => Client[F] = identity + } + +} diff --git a/modules/http4s/src/smithy4s/http4s/EndpointSpecificMiddleware.scala b/modules/http4s/src/smithy4s/http4s/ServerEndpointMiddleware.scala similarity index 87% rename from modules/http4s/src/smithy4s/http4s/EndpointSpecificMiddleware.scala rename to modules/http4s/src/smithy4s/http4s/ServerEndpointMiddleware.scala index 75cd6dbd7..ca3557a2f 100644 --- a/modules/http4s/src/smithy4s/http4s/EndpointSpecificMiddleware.scala +++ b/modules/http4s/src/smithy4s/http4s/ServerEndpointMiddleware.scala @@ -20,16 +20,16 @@ package http4s import org.http4s.HttpApp // format: off -trait EndpointSpecificMiddleware[F[_]] { +trait ServerEndpointMiddleware[F[_]] { def prepare[Alg[_[_, _, _, _, _]]](service: Service[Alg])( endpoint: Endpoint[service.Operation, _, _, _, _, _] ): HttpApp[F] => HttpApp[F] } // format: on -object EndpointSpecificMiddleware { +object ServerEndpointMiddleware { - trait Simple[F[_]] extends EndpointSpecificMiddleware[F] { + trait Simple[F[_]] extends ServerEndpointMiddleware[F] { def prepareWithHints( serviceHints: Hints, endpointHints: Hints @@ -44,8 +44,8 @@ object EndpointSpecificMiddleware { private[http4s] type EndpointMiddleware[F[_], Op[_, _, _, _, _]] = Endpoint[Op, _, _, _, _, _] => HttpApp[F] => HttpApp[F] - def noop[F[_]]: EndpointSpecificMiddleware[F] = - new EndpointSpecificMiddleware[F] { + def noop[F[_]]: ServerEndpointMiddleware[F] = + new ServerEndpointMiddleware[F] { override def prepare[Alg[_[_, _, _, _, _]]](service: Service[Alg])( endpoint: Endpoint[service.Operation, _, _, _, _, _] ): HttpApp[F] => HttpApp[F] = identity diff --git a/modules/http4s/src/smithy4s/http4s/SimpleProtocolBuilder.scala b/modules/http4s/src/smithy4s/http4s/SimpleProtocolBuilder.scala index f7cacea55..184fac486 100644 --- a/modules/http4s/src/smithy4s/http4s/SimpleProtocolBuilder.scala +++ b/modules/http4s/src/smithy4s/http4s/SimpleProtocolBuilder.scala @@ -49,7 +49,7 @@ abstract class SimpleProtocolBuilder[P](val codecs: CodecAPI)(implicit service, impl, PartialFunction.empty, - EndpointSpecificMiddleware.noop[F] + ServerEndpointMiddleware.noop[F] ) } @@ -67,7 +67,7 @@ abstract class SimpleProtocolBuilder[P](val codecs: CodecAPI)(implicit service, impl, PartialFunction.empty, - EndpointSpecificMiddleware.noop[F] + ServerEndpointMiddleware.noop[F] ) } @@ -79,15 +79,14 @@ abstract class SimpleProtocolBuilder[P](val codecs: CodecAPI)(implicit client: Client[F], val service: smithy4s.Service[Alg], uri: Uri = uri"http://localhost:8080", - middleware: EndpointSpecificMiddleware[F] = - EndpointSpecificMiddleware.noop[F] + middleware: ClientEndpointMiddleware[F] = ClientEndpointMiddleware.noop[F] ) { def uri(uri: Uri): ClientBuilder[Alg, F] = new ClientBuilder[Alg, F](this.client, this.service, uri, this.middleware) def middleware( - mid: EndpointSpecificMiddleware[F] + mid: ClientEndpointMiddleware[F] ): ClientBuilder[Alg, F] = new ClientBuilder[Alg, F](this.client, this.service, this.uri, mid) @@ -119,7 +118,7 @@ abstract class SimpleProtocolBuilder[P](val codecs: CodecAPI)(implicit service: smithy4s.Service[Alg], impl: FunctorAlgebra[Alg, F], errorTransformation: PartialFunction[Throwable, F[Throwable]], - middleware: EndpointSpecificMiddleware[F] + middleware: ServerEndpointMiddleware[F] )(implicit F: EffectCompat[F] ) { @@ -138,7 +137,7 @@ abstract class SimpleProtocolBuilder[P](val codecs: CodecAPI)(implicit new RouterBuilder(service, impl, fe, middleware) def middleware( - mid: EndpointSpecificMiddleware[F] + mid: ServerEndpointMiddleware[F] ): RouterBuilder[Alg, F] = new RouterBuilder[Alg, F](service, impl, errorTransformation, mid) diff --git a/modules/http4s/src/smithy4s/http4s/SmithyHttp4sReverseRouter.scala b/modules/http4s/src/smithy4s/http4s/SmithyHttp4sReverseRouter.scala index efb6e61e4..784ad135a 100644 --- a/modules/http4s/src/smithy4s/http4s/SmithyHttp4sReverseRouter.scala +++ b/modules/http4s/src/smithy4s/http4s/SmithyHttp4sReverseRouter.scala @@ -28,7 +28,7 @@ class SmithyHttp4sReverseRouter[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _], F[_]]( service: smithy4s.Service.Aux[Alg, Op], client: Client[F], entityCompiler: EntityCompiler[F], - middleware: EndpointSpecificMiddleware[F] + middleware: ClientEndpointMiddleware[F] )(implicit effect: EffectCompat[F]) extends FunctorInterpreter[Op, F] { // format: on diff --git a/modules/http4s/src/smithy4s/http4s/SmithyHttp4sRouter.scala b/modules/http4s/src/smithy4s/http4s/SmithyHttp4sRouter.scala index 458b17dd3..47cc0ec9e 100644 --- a/modules/http4s/src/smithy4s/http4s/SmithyHttp4sRouter.scala +++ b/modules/http4s/src/smithy4s/http4s/SmithyHttp4sRouter.scala @@ -32,7 +32,7 @@ class SmithyHttp4sRouter[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _], F[_]]( impl: FunctorInterpreter[Op, F], errorTransformation: PartialFunction[Throwable, F[Throwable]], entityCompiler: EntityCompiler[F], - middleware: EndpointSpecificMiddleware[F] + middleware: ServerEndpointMiddleware[F] )(implicit effect: EffectCompat[F]) { private val pathParamsKey = diff --git a/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sClientEndpoint.scala b/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sClientEndpoint.scala index 83c53a4fa..a2fea9599 100644 --- a/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sClientEndpoint.scala +++ b/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sClientEndpoint.scala @@ -29,7 +29,6 @@ import scodec.bits.ByteVector import smithy4s.kinds._ import smithy4s.http._ import smithy4s.schema.SchemaAlt -import org.http4s.HttpApp /** * A construct that encapsulates interprets and a low-level @@ -48,7 +47,7 @@ private[http4s] object SmithyHttp4sClientEndpoint { client: Client[F], endpoint: Endpoint[Op, I, E, O, SI, SO], compilerContext: CompilerContext[F], - middleware: HttpApp[F] => HttpApp[F] + middleware: Client[F] => Client[F] ): Either[ HttpEndpoint.HttpEndpointError, SmithyHttp4sClientEndpoint[F, Op, I, E, O, SI, SO] @@ -83,12 +82,11 @@ private[http4s] class SmithyHttp4sClientEndpointImpl[F[_], Op[_, _, _, _, _], I, endpoint: Endpoint[Op, I, E, O, SI, SO], httpEndpoint: HttpEndpoint[I], compilerContext: CompilerContext[F], - middleware: HttpApp[F] => HttpApp[F] + middleware: Client[F] => Client[F] )(implicit effect: EffectCompat[F]) extends SmithyHttp4sClientEndpoint[F, Op, I, E, O, SI, SO] { // format: on - private val transformedClient: Client[F] = - Client.fromHttpApp[F](middleware(client.toHttpApp)) + private val transformedClient: Client[F] = middleware(client) def send(input: I): F[O] = { transformedClient diff --git a/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala b/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala index a23322893..a49892dfb 100644 --- a/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala +++ b/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala @@ -57,7 +57,7 @@ private[http4s] object SmithyHttp4sServerEndpoint { endpoint: Endpoint[Op, I, E, O, SI, SO], compilerContext: CompilerContext[F], errorTransformation: PartialFunction[Throwable, F[Throwable]], - middleware: EndpointSpecificMiddleware.EndpointMiddleware[F, Op], + middleware: ServerEndpointMiddleware.EndpointMiddleware[F, Op], pathParamsKey: Key[PathParams] ): Either[ HttpEndpoint.HttpEndpointError, @@ -95,7 +95,7 @@ private[http4s] class SmithyHttp4sServerEndpointImpl[F[_], Op[_, _, _, _, _], I, httpEndpoint: HttpEndpoint[I], compilerContext: CompilerContext[F], errorTransformation: PartialFunction[Throwable, F[Throwable]], - middleware: EndpointSpecificMiddleware.EndpointMiddleware[F, Op], + middleware: ServerEndpointMiddleware.EndpointMiddleware[F, Op], pathParamsKey: Key[PathParams] )(implicit F: EffectCompat[F]) extends SmithyHttp4sServerEndpoint[F] { // format: on diff --git a/modules/http4s/test/src/smithy4s/http4s/EndpointSpecificMiddlewareSpec.scala b/modules/http4s/test/src/smithy4s/http4s/EndpointSpecificMiddlewareSpec.scala index abff591a3..d9f2e6955 100644 --- a/modules/http4s/test/src/smithy4s/http4s/EndpointSpecificMiddlewareSpec.scala +++ b/modules/http4s/test/src/smithy4s/http4s/EndpointSpecificMiddlewareSpec.scala @@ -27,8 +27,9 @@ import org.http4s._ import fs2.Collector import org.http4s.client.Client import cats.Eq +import cats.effect.Resource -object EndpointSpecificMiddlewareSpec extends SimpleIOSuite { +object ServerEndpointMiddlewareSpec extends SimpleIOSuite { private implicit val greetingEq: Eq[Greeting] = Eq.fromUniversalEquals private implicit val throwableEq: Eq[Throwable] = Eq.fromUniversalEquals @@ -88,7 +89,9 @@ object EndpointSpecificMiddlewareSpec extends SimpleIOSuite { val service = SimpleRestJsonBuilder .routes(HelloImpl) - .middleware(new TestMiddleware(shouldFail = shouldFailInMiddleware)) + .middleware( + new TestServerMiddleware(shouldFail = shouldFailInMiddleware) + ) .make .toOption .get @@ -116,7 +119,9 @@ object EndpointSpecificMiddlewareSpec extends SimpleIOSuite { val http4sClient = Client.fromHttpApp(serviceNoMiddleware) SimpleRestJsonBuilder(HelloWorldService) .client(http4sClient) - .middleware(new TestMiddleware(shouldFail = shouldFailInMiddleware)) + .middleware( + new TestClientMiddleware(shouldFail = shouldFailInMiddleware) + ) .use .toOption .get @@ -131,8 +136,8 @@ object EndpointSpecificMiddlewareSpec extends SimpleIOSuite { ) } - private final class TestMiddleware(shouldFail: Boolean) - extends EndpointSpecificMiddleware.Simple[IO] { + private final class TestServerMiddleware(shouldFail: Boolean) + extends ServerEndpointMiddleware.Simple[IO] { def prepareWithHints( serviceHints: Hints, endpointHints: Hints @@ -157,4 +162,32 @@ object EndpointSpecificMiddlewareSpec extends SimpleIOSuite { } } + private final class TestClientMiddleware(shouldFail: Boolean) + extends ClientEndpointMiddleware.Simple[IO] { + def prepareWithHints( + serviceHints: Hints, + endpointHints: Hints + ): Client[IO] => Client[IO] = { inputClient => + Client[IO] { request => + val hasTag: (Hints, String) => Boolean = (hints, tagName) => + hints.get[smithy.api.Tags].exists(_.value.contains(tagName)) + // check for tags in hints to test that proper hints are sent into the prepare method + if ( + hasTag(serviceHints, "testServiceTag") && + hasTag(endpointHints, "testOperationTag") + ) { + if (shouldFail) { + Resource.eval(IO.raiseError(new GenericServerError(Some("failed")))) + } else { + inputClient.run(request) + } + } else { + Resource.eval( + IO.raiseError(new Exception("didn't find tags in hints")) + ) + } + } + } + } + }