From 9719cfdecf4c632451d94631f3a73b7fdafce240 Mon Sep 17 00:00:00 2001 From: David Francoeur Date: Mon, 17 Oct 2022 11:49:03 -0400 Subject: [PATCH 1/9] Fix to send empty body, and consume it --- .../http4s/internals/SmithyHttp4sClientEndpoint.scala | 5 +++-- .../http4s/internals/SmithyHttp4sServerEndpoint.scala | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sClientEndpoint.scala b/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sClientEndpoint.scala index 5e726e6b4..0bece78e4 100644 --- a/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sClientEndpoint.scala +++ b/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sClientEndpoint.scala @@ -25,9 +25,10 @@ import org.http4s.Request import org.http4s.Response import org.http4s.Uri import org.http4s.client.Client +import scodec.bits.ByteVector +import smithy4s.kinds._ import smithy4s.http._ import smithy4s.schema.SchemaAlt -import smithy4s.kinds._ /** * A construct that encapsulates interprets and a low-level @@ -103,7 +104,7 @@ private[smithy4s] class SmithyHttp4sClientEndpointImpl[F[_], Op[_, _, _, _, _], val baseRequest = Request[F](method, uri, headers = headers) if (inputHasBody) { baseRequest.withEntity(input) - } else baseRequest + } else baseRequest.withEntity(ByteVector.empty) } private def outputFromResponse(response: Response[F]): F[O] = diff --git a/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala b/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala index 6c630de29..042ba1f80 100644 --- a/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala +++ b/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala @@ -127,7 +127,10 @@ private[smithy4s] class SmithyHttp4sServerEndpointImpl[F[_], Op[_, _, _, _, _], private val extractInput: (Metadata, Request[F]) ==> I = { inputMetadataDecoder.total match { case Some(totalDecoder) => - Kleisli(totalDecoder.decode(_: Metadata).liftTo[F]).local(_._1) + Kleisli { case (metadata, request) => + request.body.compile.drain *> + totalDecoder.decode(metadata).liftTo[F] + } case None => // NB : only compiling the input codec if the data cannot be // totally extracted from the metadata. From 1d87c2009efa66db5c111a9cc2d51c0c09265604 Mon Sep 17 00:00:00 2001 From: David Francoeur Date: Mon, 17 Oct 2022 12:21:55 -0400 Subject: [PATCH 2/9] Add a e2e test for exercising http client/server --- build.sbt | 4 +- .../http4s/Http4sEmberPizzaClientSpec.scala | 99 +++++++++++++++++++ .../tests/PizzaAdminServiceImpl.scala | 3 + .../src/smithy4s/tests/PizzaClientSpec.scala | 7 +- sampleSpecs/pizza.smithy | 18 +++- 5 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 modules/http4s/test/src-jvm/smithy4s/http4s/Http4sEmberPizzaClientSpec.scala diff --git a/build.sbt b/build.sbt index 1f222cddd..db7077dd5 100644 --- a/build.sbt +++ b/build.sbt @@ -611,7 +611,9 @@ lazy val http4s = projectMatrix Dependencies.Http4s.client.value, Dependencies.Alloy.core % Test, Dependencies.Http4s.circe.value % Test, - Dependencies.Weaver.cats.value % Test + Dependencies.Weaver.cats.value % Test, + Dependencies.Http4s.emberClient.value % Test, + Dependencies.Http4s.emberServer.value % Test ) }, moduleName := { diff --git a/modules/http4s/test/src-jvm/smithy4s/http4s/Http4sEmberPizzaClientSpec.scala b/modules/http4s/test/src-jvm/smithy4s/http4s/Http4sEmberPizzaClientSpec.scala new file mode 100644 index 000000000..2b9d42a00 --- /dev/null +++ b/modules/http4s/test/src-jvm/smithy4s/http4s/Http4sEmberPizzaClientSpec.scala @@ -0,0 +1,99 @@ +/* + * 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.http4s + +import cats.effect.IO +import cats.effect.Resource +import cats.effect.syntax.resource._ +import cats.implicits._ +import com.comcast.ip4s._ +import com.comcast.ip4s.Port +import org.http4s.ember.client.EmberClientBuilder +import org.http4s.ember.server.EmberServerBuilder +import org.http4s.HttpApp +import org.http4s.Uri +import smithy4s.example._ +import smithy4s.example.PizzaAdminService +import weaver._ + +object Http4sEmberPizzaClientSpec extends IOSuite { + type Res = PizzaAdminService[IO] + + override def sharedResource: Resource[IO, Res] = { + SimpleRestJsonBuilder + .routes(dummyImpl) + .resource + .flatMap(r => retryResource(server(r.orNotFound))) + .flatMap { port => makeClient(port) } + } + + def makeClient(port: Int): Resource[IO, PizzaAdminService[IO]] = + EmberClientBuilder.default[IO].build.flatMap { client => + SimpleRestJsonBuilder(PizzaAdminService) + .client(client) + .uri(Uri.unsafeFromString(s"http://localhost:$port")) + .resource + } + + def server(app: HttpApp[IO]): Resource[IO, Int] = + cats.effect.std.Random + .scalaUtilRandom[IO] + .flatMap(_.betweenInt(50000, 60000)) + .toResource + .flatMap(port => + Port + .fromInt(port) + .toRight(new Exception(s"Invalid port: $port")) + .liftTo[IO] + .toResource + ) + .flatMap { port => + EmberServerBuilder + .default[IO] + .withHost(host"localhost") + .withPort(port) + .withHttpApp(app) + .build + .map(_ => port.value) + } + + test("empty body") { client => + (client.book("name") *> client.book("name2")).as(success) + } + + private val dummyImpl = new PizzaAdminService[IO]() { + // format: off + override def addMenuItem(restaurant: String, menuItem: MenuItem): IO[AddMenuItemResult] = ??? + override def getMenu(restaurant: String): IO[GetMenuResult] = ??? + override def version(): IO[VersionOutput] = ??? + override def health(query: Option[String]): IO[HealthResponse] = ??? + override def headerEndpoint(uppercaseHeader: Option[String], capitalizedHeader: Option[String], lowercaseHeader: Option[String], mixedHeader: Option[String]): IO[HeaderEndpointData] = ??? + override def roundTrip(label: String, header: Option[String], query: Option[String], body: Option[String]): IO[RoundTripData] = ??? + override def getEnum(aa: TheEnum): IO[GetEnumOutput] = ??? + override def getIntEnum(aa: EnumResult): IO[GetIntEnumOutput] = ??? + override def customCode(code: Int): IO[CustomCodeOutput] = ??? + override def book(name: String, town: Option[String]): IO[BookOutput] = IO.pure(BookOutput("name")) + // format: on + } + + def retryResource[A]( + resource: Resource[IO, A], + max: Int = 10 + ): Resource[IO, A] = + if (max <= 0) resource + else resource.orElse(retryResource(resource, max - 1)) +} diff --git a/modules/tests/src/smithy4s/tests/PizzaAdminServiceImpl.scala b/modules/tests/src/smithy4s/tests/PizzaAdminServiceImpl.scala index af888562a..8e15ce8ff 100644 --- a/modules/tests/src/smithy4s/tests/PizzaAdminServiceImpl.scala +++ b/modules/tests/src/smithy4s/tests/PizzaAdminServiceImpl.scala @@ -37,6 +37,9 @@ object PizzaAdminServiceImpl { class PizzaAdminServiceImpl(ref: Compat.Ref[IO, State]) extends PizzaAdminService[IO] { + def book(name: String, town: Option[String]): IO[BookOutput] = + IO.pure(BookOutput(message = s"Booked for $name")) + def getEnum(theEnum: TheEnum): IO[GetEnumOutput] = IO.pure(GetEnumOutput(result = Some(theEnum.value))) diff --git a/modules/tests/src/smithy4s/tests/PizzaClientSpec.scala b/modules/tests/src/smithy4s/tests/PizzaClientSpec.scala index 2a65f4dd9..fcf68d4ff 100644 --- a/modules/tests/src/smithy4s/tests/PizzaClientSpec.scala +++ b/modules/tests/src/smithy4s/tests/PizzaClientSpec.scala @@ -28,7 +28,6 @@ import org.http4s.dsl.io._ import org.typelevel.ci.CIString import smithy4s.Timestamp import smithy4s.example._ -import cats.Show import weaver._ abstract class PizzaClientSpec extends IOSuite { @@ -174,8 +173,7 @@ abstract class PizzaClientSpec extends IOSuite { expected: E )(implicit loc: SourceLocation, - ct: scala.reflect.ClassTag[E], - show: Show[E] = Show.fromToString[E] + ct: scala.reflect.ClassTag[E] ) = { clientTest(name) { (client, backend, log) => for { @@ -302,6 +300,9 @@ abstract class PizzaClientSpec extends IOSuite { case request @ (GET -> Root / "custom-code" / IntVar(code)) => storeAndReturn(s"customCode$code", request) + case POST -> Root / "book" / _ => + val body = Json.obj("message" -> Json.fromString("test")) + Ok(body) } .orNotFound } diff --git a/sampleSpecs/pizza.smithy b/sampleSpecs/pizza.smithy index e2f8be66d..4a5a4fac2 100644 --- a/sampleSpecs/pizza.smithy +++ b/sampleSpecs/pizza.smithy @@ -8,7 +8,7 @@ use alloy#simpleRestJson service PizzaAdminService { version: "1.0.0", errors: [GenericServerError, GenericClientError], - operations: [AddMenuItem, GetMenu, Version, Health, HeaderEndpoint, RoundTrip, GetEnum, GetIntEnum, CustomCode] + operations: [AddMenuItem, GetMenu, Version, Health, HeaderEndpoint, RoundTrip, GetEnum, GetIntEnum, CustomCode, Book] } @http(method: "POST", uri: "/restaurant/{restaurant}/menu/item", code: 201) @@ -289,3 +289,19 @@ structure CustomCodeOutput { @httpResponseCode code: Integer } + +@http(method: "POST", uri: "/book/{name}", code: 200) +operation Book { + input := { + @httpLabel + @required + name: String, + + @httpQuery("town") + town: String + }, + output := { + @required + message: String + } +} From 3f2e8bfe931bd3a2d199c0e529eac5060f12f637 Mon Sep 17 00:00:00 2001 From: David Francoeur Date: Mon, 17 Oct 2022 12:32:34 -0400 Subject: [PATCH 3/9] Cats 3 only --- .../http4s/Http4sEmberPizzaClientSpec.scala | 99 ------------------- 1 file changed, 99 deletions(-) delete mode 100644 modules/http4s/test/src-jvm/smithy4s/http4s/Http4sEmberPizzaClientSpec.scala diff --git a/modules/http4s/test/src-jvm/smithy4s/http4s/Http4sEmberPizzaClientSpec.scala b/modules/http4s/test/src-jvm/smithy4s/http4s/Http4sEmberPizzaClientSpec.scala deleted file mode 100644 index 2b9d42a00..000000000 --- a/modules/http4s/test/src-jvm/smithy4s/http4s/Http4sEmberPizzaClientSpec.scala +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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.http4s - -import cats.effect.IO -import cats.effect.Resource -import cats.effect.syntax.resource._ -import cats.implicits._ -import com.comcast.ip4s._ -import com.comcast.ip4s.Port -import org.http4s.ember.client.EmberClientBuilder -import org.http4s.ember.server.EmberServerBuilder -import org.http4s.HttpApp -import org.http4s.Uri -import smithy4s.example._ -import smithy4s.example.PizzaAdminService -import weaver._ - -object Http4sEmberPizzaClientSpec extends IOSuite { - type Res = PizzaAdminService[IO] - - override def sharedResource: Resource[IO, Res] = { - SimpleRestJsonBuilder - .routes(dummyImpl) - .resource - .flatMap(r => retryResource(server(r.orNotFound))) - .flatMap { port => makeClient(port) } - } - - def makeClient(port: Int): Resource[IO, PizzaAdminService[IO]] = - EmberClientBuilder.default[IO].build.flatMap { client => - SimpleRestJsonBuilder(PizzaAdminService) - .client(client) - .uri(Uri.unsafeFromString(s"http://localhost:$port")) - .resource - } - - def server(app: HttpApp[IO]): Resource[IO, Int] = - cats.effect.std.Random - .scalaUtilRandom[IO] - .flatMap(_.betweenInt(50000, 60000)) - .toResource - .flatMap(port => - Port - .fromInt(port) - .toRight(new Exception(s"Invalid port: $port")) - .liftTo[IO] - .toResource - ) - .flatMap { port => - EmberServerBuilder - .default[IO] - .withHost(host"localhost") - .withPort(port) - .withHttpApp(app) - .build - .map(_ => port.value) - } - - test("empty body") { client => - (client.book("name") *> client.book("name2")).as(success) - } - - private val dummyImpl = new PizzaAdminService[IO]() { - // format: off - override def addMenuItem(restaurant: String, menuItem: MenuItem): IO[AddMenuItemResult] = ??? - override def getMenu(restaurant: String): IO[GetMenuResult] = ??? - override def version(): IO[VersionOutput] = ??? - override def health(query: Option[String]): IO[HealthResponse] = ??? - override def headerEndpoint(uppercaseHeader: Option[String], capitalizedHeader: Option[String], lowercaseHeader: Option[String], mixedHeader: Option[String]): IO[HeaderEndpointData] = ??? - override def roundTrip(label: String, header: Option[String], query: Option[String], body: Option[String]): IO[RoundTripData] = ??? - override def getEnum(aa: TheEnum): IO[GetEnumOutput] = ??? - override def getIntEnum(aa: EnumResult): IO[GetIntEnumOutput] = ??? - override def customCode(code: Int): IO[CustomCodeOutput] = ??? - override def book(name: String, town: Option[String]): IO[BookOutput] = IO.pure(BookOutput("name")) - // format: on - } - - def retryResource[A]( - resource: Resource[IO, A], - max: Int = 10 - ): Resource[IO, A] = - if (max <= 0) resource - else resource.orElse(retryResource(resource, max - 1)) -} From 0b666ae8c4e1fe7ae1f1feb28082df93ede29ecb Mon Sep 17 00:00:00 2001 From: David Francoeur Date: Mon, 17 Oct 2022 12:50:53 -0400 Subject: [PATCH 4/9] Include the test... --- .../http4s/Http4sEmberPizzaClientSpec.scala | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 modules/http4s/test/src-jvm-ce3/smithy4s/http4s/Http4sEmberPizzaClientSpec.scala diff --git a/modules/http4s/test/src-jvm-ce3/smithy4s/http4s/Http4sEmberPizzaClientSpec.scala b/modules/http4s/test/src-jvm-ce3/smithy4s/http4s/Http4sEmberPizzaClientSpec.scala new file mode 100644 index 000000000..2b9d42a00 --- /dev/null +++ b/modules/http4s/test/src-jvm-ce3/smithy4s/http4s/Http4sEmberPizzaClientSpec.scala @@ -0,0 +1,99 @@ +/* + * 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.http4s + +import cats.effect.IO +import cats.effect.Resource +import cats.effect.syntax.resource._ +import cats.implicits._ +import com.comcast.ip4s._ +import com.comcast.ip4s.Port +import org.http4s.ember.client.EmberClientBuilder +import org.http4s.ember.server.EmberServerBuilder +import org.http4s.HttpApp +import org.http4s.Uri +import smithy4s.example._ +import smithy4s.example.PizzaAdminService +import weaver._ + +object Http4sEmberPizzaClientSpec extends IOSuite { + type Res = PizzaAdminService[IO] + + override def sharedResource: Resource[IO, Res] = { + SimpleRestJsonBuilder + .routes(dummyImpl) + .resource + .flatMap(r => retryResource(server(r.orNotFound))) + .flatMap { port => makeClient(port) } + } + + def makeClient(port: Int): Resource[IO, PizzaAdminService[IO]] = + EmberClientBuilder.default[IO].build.flatMap { client => + SimpleRestJsonBuilder(PizzaAdminService) + .client(client) + .uri(Uri.unsafeFromString(s"http://localhost:$port")) + .resource + } + + def server(app: HttpApp[IO]): Resource[IO, Int] = + cats.effect.std.Random + .scalaUtilRandom[IO] + .flatMap(_.betweenInt(50000, 60000)) + .toResource + .flatMap(port => + Port + .fromInt(port) + .toRight(new Exception(s"Invalid port: $port")) + .liftTo[IO] + .toResource + ) + .flatMap { port => + EmberServerBuilder + .default[IO] + .withHost(host"localhost") + .withPort(port) + .withHttpApp(app) + .build + .map(_ => port.value) + } + + test("empty body") { client => + (client.book("name") *> client.book("name2")).as(success) + } + + private val dummyImpl = new PizzaAdminService[IO]() { + // format: off + override def addMenuItem(restaurant: String, menuItem: MenuItem): IO[AddMenuItemResult] = ??? + override def getMenu(restaurant: String): IO[GetMenuResult] = ??? + override def version(): IO[VersionOutput] = ??? + override def health(query: Option[String]): IO[HealthResponse] = ??? + override def headerEndpoint(uppercaseHeader: Option[String], capitalizedHeader: Option[String], lowercaseHeader: Option[String], mixedHeader: Option[String]): IO[HeaderEndpointData] = ??? + override def roundTrip(label: String, header: Option[String], query: Option[String], body: Option[String]): IO[RoundTripData] = ??? + override def getEnum(aa: TheEnum): IO[GetEnumOutput] = ??? + override def getIntEnum(aa: EnumResult): IO[GetIntEnumOutput] = ??? + override def customCode(code: Int): IO[CustomCodeOutput] = ??? + override def book(name: String, town: Option[String]): IO[BookOutput] = IO.pure(BookOutput("name")) + // format: on + } + + def retryResource[A]( + resource: Resource[IO, A], + max: Int = 10 + ): Resource[IO, A] = + if (max <= 0) resource + else resource.orElse(retryResource(resource, max - 1)) +} From cfb1161e70384d47b0036e8f5793629b9bb7d369 Mon Sep 17 00:00:00 2001 From: David Francoeur Date: Mon, 17 Oct 2022 14:43:26 -0400 Subject: [PATCH 5/9] Remove Klesili when unused --- .../SmithyHttp4sServerEndpoint.scala | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala b/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala index 042ba1f80..20607809e 100644 --- a/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala +++ b/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala @@ -89,7 +89,7 @@ private[smithy4s] class SmithyHttp4sServerEndpointImpl[F[_], Op[_, _, _, _, _], def run(pathParams: PathParams, request: Request[F]): F[Response[F]] = { val run: F[O] = for { metadata <- getMetadata(pathParams, request) - input <- extractInput.run((metadata, request)) + input <- extractInput(metadata, request) output <- (impl(endpoint.wrap(input)): F[O]) } yield output @@ -121,29 +121,22 @@ private[smithy4s] class SmithyHttp4sServerEndpointImpl[F[_], Op[_, _, _, _, _], errorTransformation(other).flatMap(F.raiseError) } - - - // format: off - private val extractInput: (Metadata, Request[F]) ==> I = { + private def extractInput(metadata: Metadata, request: Request[F]): F[I] = { inputMetadataDecoder.total match { case Some(totalDecoder) => - Kleisli { case (metadata, request) => - request.body.compile.drain *> + request.body.compile.drain *> totalDecoder.decode(metadata).liftTo[F] - } case None => // NB : only compiling the input codec if the data cannot be // totally extracted from the metadata. - implicit val inputCodec = entityCompiler.compilePartialEntityDecoder(inputSchema, entityCache) - Kleisli { case (metadata, request) => - for { - metadataPartial <- inputMetadataDecoder.decode(metadata).liftTo[F] - bodyPartial <- request.as[BodyPartial[I]] - } yield metadataPartial.combine(bodyPartial) - } + implicit val inputCodec = + entityCompiler.compilePartialEntityDecoder(inputSchema, entityCache) + for { + metadataPartial <- inputMetadataDecoder.decode(metadata).liftTo[F] + bodyPartial <- request.as[BodyPartial[I]] + } yield metadataPartial.combine(bodyPartial) } } - // format: on private def putHeaders(m: Message[F], headers: Headers) = m.putHeaders(headers.headers) From 3680c3e8508136a3cdc4cc21803b5e1fc8d0f7bd Mon Sep 17 00:00:00 2001 From: David Francoeur Date: Mon, 17 Oct 2022 14:43:42 -0400 Subject: [PATCH 6/9] Use IO.stub instead of `???` --- .../http4s/Http4sEmberPizzaClientSpec.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/http4s/test/src-jvm-ce3/smithy4s/http4s/Http4sEmberPizzaClientSpec.scala b/modules/http4s/test/src-jvm-ce3/smithy4s/http4s/Http4sEmberPizzaClientSpec.scala index 2b9d42a00..4d41d3306 100644 --- a/modules/http4s/test/src-jvm-ce3/smithy4s/http4s/Http4sEmberPizzaClientSpec.scala +++ b/modules/http4s/test/src-jvm-ce3/smithy4s/http4s/Http4sEmberPizzaClientSpec.scala @@ -77,15 +77,15 @@ object Http4sEmberPizzaClientSpec extends IOSuite { private val dummyImpl = new PizzaAdminService[IO]() { // format: off - override def addMenuItem(restaurant: String, menuItem: MenuItem): IO[AddMenuItemResult] = ??? - override def getMenu(restaurant: String): IO[GetMenuResult] = ??? - override def version(): IO[VersionOutput] = ??? - override def health(query: Option[String]): IO[HealthResponse] = ??? - override def headerEndpoint(uppercaseHeader: Option[String], capitalizedHeader: Option[String], lowercaseHeader: Option[String], mixedHeader: Option[String]): IO[HeaderEndpointData] = ??? - override def roundTrip(label: String, header: Option[String], query: Option[String], body: Option[String]): IO[RoundTripData] = ??? - override def getEnum(aa: TheEnum): IO[GetEnumOutput] = ??? - override def getIntEnum(aa: EnumResult): IO[GetIntEnumOutput] = ??? - override def customCode(code: Int): IO[CustomCodeOutput] = ??? + override def addMenuItem(restaurant: String, menuItem: MenuItem): IO[AddMenuItemResult] = IO.stub + override def getMenu(restaurant: String): IO[GetMenuResult] = IO.stub + override def version(): IO[VersionOutput] = IO.stub + override def health(query: Option[String]): IO[HealthResponse] = IO.pure(HealthResponse("good")) + override def headerEndpoint(uppercaseHeader: Option[String], capitalizedHeader: Option[String], lowercaseHeader: Option[String], mixedHeader: Option[String]): IO[HeaderEndpointData] = IO.stub + override def roundTrip(label: String, header: Option[String], query: Option[String], body: Option[String]): IO[RoundTripData] = IO.stub + override def getEnum(aa: TheEnum): IO[GetEnumOutput] = IO.stub + override def getIntEnum(aa: EnumResult): IO[GetIntEnumOutput] = IO.stub + override def customCode(code: Int): IO[CustomCodeOutput] = IO.stub override def book(name: String, town: Option[String]): IO[BookOutput] = IO.pure(BookOutput("name")) // format: on } From 9962f80fddb4cf1dc01253e1e74b8056eb258f54 Mon Sep 17 00:00:00 2001 From: David Francoeur Date: Mon, 17 Oct 2022 20:38:36 -0400 Subject: [PATCH 7/9] Use a val instead of a def --- .../SmithyHttp4sServerEndpoint.scala | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala b/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala index 20607809e..72d2df206 100644 --- a/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala +++ b/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala @@ -121,20 +121,22 @@ private[smithy4s] class SmithyHttp4sServerEndpointImpl[F[_], Op[_, _, _, _, _], errorTransformation(other).flatMap(F.raiseError) } - private def extractInput(metadata: Metadata, request: Request[F]): F[I] = { + private val extractInput: (Metadata, Request[F]) => F[I] = { inputMetadataDecoder.total match { case Some(totalDecoder) => - request.body.compile.drain *> - totalDecoder.decode(metadata).liftTo[F] + (metadata, request) => + request.body.compile.drain *> + totalDecoder.decode(metadata).liftTo[F] case None => // NB : only compiling the input codec if the data cannot be // totally extracted from the metadata. - implicit val inputCodec = - entityCompiler.compilePartialEntityDecoder(inputSchema, entityCache) - for { - metadataPartial <- inputMetadataDecoder.decode(metadata).liftTo[F] - bodyPartial <- request.as[BodyPartial[I]] - } yield metadataPartial.combine(bodyPartial) + (metadata, request) => + implicit val inputCodec = + entityCompiler.compilePartialEntityDecoder(inputSchema, entityCache) + for { + metadataPartial <- inputMetadataDecoder.decode(metadata).liftTo[F] + bodyPartial <- request.as[BodyPartial[I]] + } yield metadataPartial.combine(bodyPartial) } } From b699d7ae3914a1afbb223b804a509a5abfec5239 Mon Sep 17 00:00:00 2001 From: David Francoeur Date: Wed, 19 Oct 2022 09:55:28 -0400 Subject: [PATCH 8/9] Keep the show instance for error tests --- modules/tests/src/smithy4s/tests/PizzaClientSpec.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/tests/src/smithy4s/tests/PizzaClientSpec.scala b/modules/tests/src/smithy4s/tests/PizzaClientSpec.scala index fcf68d4ff..80e9b386c 100644 --- a/modules/tests/src/smithy4s/tests/PizzaClientSpec.scala +++ b/modules/tests/src/smithy4s/tests/PizzaClientSpec.scala @@ -19,15 +19,16 @@ package smithy4s.tests import cats.data.Chain import cats.effect._ import cats.effect.std.UUIDGen +import cats.Show import cats.syntax.all._ import io.circe.Json -import org.http4s.HttpApp import org.http4s._ import org.http4s.circe._ import org.http4s.dsl.io._ +import org.http4s.HttpApp import org.typelevel.ci.CIString -import smithy4s.Timestamp import smithy4s.example._ +import smithy4s.Timestamp import weaver._ abstract class PizzaClientSpec extends IOSuite { @@ -173,7 +174,8 @@ abstract class PizzaClientSpec extends IOSuite { expected: E )(implicit loc: SourceLocation, - ct: scala.reflect.ClassTag[E] + ct: scala.reflect.ClassTag[E], + show: Show[E] = Show.fromToString[E] ) = { clientTest(name) { (client, backend, log) => for { From d1b21f0f6b105fe55ec4f5087eea60fcf8e0e0f8 Mon Sep 17 00:00:00 2001 From: David Francoeur Date: Thu, 10 Nov 2022 13:03:07 -0500 Subject: [PATCH 9/9] Compile the codec before building the function --- .../http4s/internals/SmithyHttp4sServerEndpoint.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala b/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala index 68bdb0e77..0333a758c 100644 --- a/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala +++ b/modules/http4s/src/smithy4s/http4s/internals/SmithyHttp4sServerEndpoint.scala @@ -141,9 +141,9 @@ private[http4s] class SmithyHttp4sServerEndpointImpl[F[_], Op[_, _, _, _, _], I, case None => // NB : only compiling the input codec if the data cannot be // totally extracted from the metadata. + implicit val inputCodec = + entityCompiler.compilePartialEntityDecoder(inputSchema, entityCache) (metadata, request) => - implicit val inputCodec = - entityCompiler.compilePartialEntityDecoder(inputSchema, entityCache) for { metadataPartial <- inputMetadataDecoder.decode(metadata).liftTo[F] bodyPartial <- request.as[BodyPartial[I]]