Last active
August 25, 2023 12:03
-
-
Save mikeshepherd/34ac82d3aba34be1428e7402ff521ae4 to your computer and use it in GitHub Desktop.
Reproduction of unexpected behaviour with No-Content responses in smithy4s-http4s
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//> 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