Skip to content

Instantly share code, notes, and snippets.

@mikeshepherd
Last active August 25, 2023 12:03
Show Gist options
  • Save mikeshepherd/34ac82d3aba34be1428e7402ff521ae4 to your computer and use it in GitHub Desktop.
Save mikeshepherd/34ac82d3aba34be1428e7402ff521ae4 to your computer and use it in GitHub Desktop.
Reproduction of unexpected behaviour with No-Content responses in smithy4s-http4s
//> using scala "2.13.10"
//> using option "-feature"
//> using lib "software.amazon.smithy:smithy-model:1.28.1"
//> using lib "com.disneystreaming.smithy4s::smithy4s-http4s:0.17.9"
//> using lib "org.http4s::http4s-ember-client:0.23.23"
//> using lib "org.http4s::http4s-ember-server:0.23.23"
import cats.effect._
import cats.implicits._
import com.comcast.ip4s._
import org.http4s._
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.ember.client.EmberClientBuilder
import org.http4s.ember.server.EmberServerBuilder
import org.typelevel.ci._
import scodec.bits.ByteVector
import smithy4s.http4s.SimpleRestJsonBuilder
import example._
object HelloWorld extends IOApp.Simple {
val impl = new ContentLengthExampleService[IO] {
override def example(): IO[ContentLengthExampleOutput] = IO.pure(ContentLengthExampleOutput())
}
val routes = SimpleRestJsonBuilder.routes(impl).resource.map(_.orNotFound)
val serverOverTcp = {
val server = routes.flatMap(
EmberServerBuilder
.default[IO]
.withHost(ipv4"0.0.0.0")
.withPort(port"8080")
.withHttpApp(_)
.build
)
val client = EmberClientBuilder
.default[IO]
.build
(server, client).mapN { (_, client) => client }
}
val serverOverCode =
routes.map(Client.fromHttpApp(_))
val uri = Uri.unsafeFromString("http://localhost:8080")
val request = Request[IO](Method.GET, uri = uri / "example")
val logResponse = (response: Response[IO]) =>
IO.println(s"Got content-length: [${response.headers.get(ci"Content-Length")}]") *>
response.as[ByteVector].flatMap(body => IO.println(s"Got actual length [${body.length}]"))
// Content-Length header 2, Actual content length 0
val overTcp = IO.println("Running over tcp") *> serverOverTcp.use(_.run(request).use(logResponse))
// Content-Length header 2, Actual content length 2
val overCode = IO.println("Running in code") *> serverOverCode.use(_.run(request).use(logResponse))
val run: IO[Unit] = (overTcp.attempt, overCode.attempt).tupled.void
}
// Generated smithy code
/**
$version: "2"
namespace example
use alloy#simpleRestJson
@simpleRestJson
service ContentLengthExampleService {
version: "v1",
operations: [Example]
}
@http(method: "GET", uri: "/example", code: 204)
operation Example {
input: ContentLengthExampleInput,
output: ContentLengthExampleOutput
}
structure ContentLengthExampleInput {
}
structure ContentLengthExampleOutput {
}
**/
object example {
import smithy4s.Endpoint
import smithy4s.Hints
import smithy4s.Schema
import smithy4s.Service
import smithy4s.ShapeId
import smithy4s.ShapeTag
import smithy4s.StreamingSchema
import smithy4s.Transformation
import smithy4s.schema.Schema.constant
import smithy4s.kinds.PolyFunction5
import smithy4s.kinds.toPolyFunction5.const5
case class ContentLengthExampleInput()
object ContentLengthExampleInput extends ShapeTag.Companion[ContentLengthExampleInput] {
val id: ShapeId = ShapeId("example", "ContentLengthExampleInput")
val hints: Hints = Hints.empty
implicit val schema: Schema[ContentLengthExampleInput] =
constant(ContentLengthExampleInput()).withId(id).addHints(hints)
}
case class ContentLengthExampleOutput()
object ContentLengthExampleOutput extends ShapeTag.Companion[ContentLengthExampleOutput] {
val id: ShapeId = ShapeId("example", "ContentLengthExampleOutput")
val hints: Hints = Hints.empty
implicit val schema: Schema[ContentLengthExampleOutput] =
constant(ContentLengthExampleOutput()).withId(id).addHints(hints)
}
trait ContentLengthExampleServiceGen[F[_, _, _, _, _]] {
self =>
def example()
: F[ContentLengthExampleInput, Nothing, ContentLengthExampleOutput, Nothing, Nothing]
def transform: Transformation.PartiallyApplied[ContentLengthExampleServiceGen[F]] =
Transformation.of[ContentLengthExampleServiceGen[F]](this)
}
object ContentLengthExampleServiceGen
extends Service.Mixin[ContentLengthExampleServiceGen, ContentLengthExampleServiceOperation] {
val id: ShapeId = ShapeId("example", "ContentLengthExampleService")
val version: String = "v1"
val hints: Hints = Hints(
alloy.SimpleRestJson()
)
def apply[F[_]](implicit F: Impl[F]): F.type = F
object ErrorAware {
def apply[F[_, _]](implicit F: ErrorAware[F]): F.type = F
type Default[F[+_, +_]] = Constant[smithy4s.kinds.stubs.Kind2[F]#toKind5]
}
val endpoints: List[smithy4s.Endpoint[ContentLengthExampleServiceOperation, _, _, _, _, _]] =
List(
ContentLengthExampleServiceOperation.Example
)
def endpoint[I, E, O, SI, SO](op: ContentLengthExampleServiceOperation[I, E, O, SI, SO]) =
op.endpoint
class Constant[P[-_, +_, +_, +_, +_]](value: P[Any, Nothing, Nothing, Nothing, Nothing])
extends ContentLengthExampleServiceOperation.Transformed[
ContentLengthExampleServiceOperation,
P
](reified, const5(value))
type Default[F[+_]] = Constant[smithy4s.kinds.stubs.Kind1[F]#toKind5]
def reified: ContentLengthExampleServiceGen[ContentLengthExampleServiceOperation] =
ContentLengthExampleServiceOperation.reified
def mapK5[P[_, _, _, _, _], P1[_, _, _, _, _]](
alg: ContentLengthExampleServiceGen[P],
f: PolyFunction5[P, P1]
): ContentLengthExampleServiceGen[P1] =
new ContentLengthExampleServiceOperation.Transformed(alg, f)
def fromPolyFunction[P[_, _, _, _, _]](
f: PolyFunction5[ContentLengthExampleServiceOperation, P]
): ContentLengthExampleServiceGen[P] =
new ContentLengthExampleServiceOperation.Transformed(reified, f)
def toPolyFunction[P[_, _, _, _, _]](
impl: ContentLengthExampleServiceGen[P]
): PolyFunction5[ContentLengthExampleServiceOperation, P] =
ContentLengthExampleServiceOperation.toPolyFunction(impl)
}
sealed trait ContentLengthExampleServiceOperation[
Input,
Err,
Output,
StreamedInput,
StreamedOutput
] {
def run[F[_, _, _, _, _]](
impl: ContentLengthExampleServiceGen[F]
): F[Input, Err, Output, StreamedInput, StreamedOutput]
def endpoint: (
Input,
Endpoint[
ContentLengthExampleServiceOperation,
Input,
Err,
Output,
StreamedInput,
StreamedOutput
]
)
}
object ContentLengthExampleServiceOperation {
object reified extends ContentLengthExampleServiceGen[ContentLengthExampleServiceOperation] {
def example() = Example(ContentLengthExampleInput())
}
class Transformed[P[_, _, _, _, _], P1[_, _, _, _, _]](
alg: ContentLengthExampleServiceGen[P],
f: PolyFunction5[P, P1]
) extends ContentLengthExampleServiceGen[P1] {
def example() =
f[ContentLengthExampleInput, Nothing, ContentLengthExampleOutput, Nothing, Nothing](
alg.example()
)
}
def toPolyFunction[P[_, _, _, _, _]](
impl: ContentLengthExampleServiceGen[P]
): PolyFunction5[ContentLengthExampleServiceOperation, P] =
new PolyFunction5[ContentLengthExampleServiceOperation, P] {
def apply[I, E, O, SI, SO](
op: ContentLengthExampleServiceOperation[I, E, O, SI, SO]
): P[I, E, O, SI, SO] = op.run(impl)
}
case class Example(input: ContentLengthExampleInput)
extends ContentLengthExampleServiceOperation[
ContentLengthExampleInput,
Nothing,
ContentLengthExampleOutput,
Nothing,
Nothing
] {
def run[F[_, _, _, _, _]](
impl: ContentLengthExampleServiceGen[F]
): F[ContentLengthExampleInput, Nothing, ContentLengthExampleOutput, Nothing, Nothing] =
impl.example()
def endpoint: (
ContentLengthExampleInput,
smithy4s.Endpoint[
ContentLengthExampleServiceOperation,
ContentLengthExampleInput,
Nothing,
ContentLengthExampleOutput,
Nothing,
Nothing
]
) = (input, Example)
}
object Example
extends smithy4s.Endpoint[
ContentLengthExampleServiceOperation,
ContentLengthExampleInput,
Nothing,
ContentLengthExampleOutput,
Nothing,
Nothing
] {
val id: ShapeId = ShapeId("example", "Example")
val input: Schema[ContentLengthExampleInput] =
ContentLengthExampleInput.schema.addHints(smithy4s.internals.InputOutput.Input.widen)
val output: Schema[ContentLengthExampleOutput] =
ContentLengthExampleOutput.schema.addHints(smithy4s.internals.InputOutput.Output.widen)
val streamedInput: StreamingSchema[Nothing] = StreamingSchema.nothing
val streamedOutput: StreamingSchema[Nothing] = StreamingSchema.nothing
val hints: Hints = Hints(
smithy.api.Http(
method = smithy.api.NonEmptyString("GET"),
uri = smithy.api.NonEmptyString("/example"),
code = 204
)
)
def wrap(input: ContentLengthExampleInput) = Example(input)
}
}
type ContentLengthExampleService[F[_]] =
smithy4s.kinds.FunctorAlgebra[ContentLengthExampleServiceGen, F]
val ContentLengthExampleService = ContentLengthExampleServiceGen
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment