Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Schema based header codecs, unified with query codecs (#3232) #3270

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ jobs:
tar xf targets.tar
rm targets.tar

- uses: coursier/setup-action@v1
with:
apps: sbt

- name: Release
env:
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
Expand Down
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ ThisBuild / githubWorkflowTargetTags ++= Seq("v*")
ThisBuild / githubWorkflowPublishTargetBranches += RefPredicate.StartsWith(Ref.Tag("v"))
ThisBuild / githubWorkflowPublish :=
Seq(
WorkflowStep.Use(UseRef.Public("coursier", "setup-action", "v1"), Map("apps" -> "sbt")),
WorkflowStep.Sbt(
List("ci-release"),
name = Some("Release"),
Expand Down
11 changes: 11 additions & 0 deletions project/MimaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ object MimaSettings {
mimaBinaryIssueFilters ++= Seq(
exclude[Problem]("zio.http.internal.*"),
exclude[Problem]("zio.http.codec.internal.*"),
exclude[Problem]("zio.http.codec.HttpCodec$Query$QueryType$Record$"),
exclude[Problem]("zio.http.codec.HttpCodec$Query$QueryType$Record"),
exclude[Problem]("zio.http.codec.HttpCodec$Query$QueryType$Primitive$"),
exclude[Problem]("zio.http.codec.HttpCodec$Query$QueryType$Primitive"),
exclude[Problem]("zio.http.codec.HttpCodec$Query$QueryType$Collection$"),
exclude[Problem]("zio.http.codec.HttpCodec$Query$QueryType$Collection"),
exclude[Problem]("zio.http.codec.HttpCodec$Query$QueryType$"),
exclude[Problem]("zio.http.codec.HttpCodec$Query$QueryType"),
exclude[Problem]("zio.http.endpoint.openapi.OpenAPIGen#AtomizedMetaCodecs.apply"),
exclude[Problem]("zio.http.endpoint.openapi.OpenAPIGen#AtomizedMetaCodecs.this"),
exclude[Problem]("zio.http.endpoint.openapi.OpenAPIGen#AtomizedMetaCodecs.copy"),
),
mimaFailOnProblem := failOnProblem
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class ServerInboundHandlerBenchmark {
private val largeString = random.alphanumeric.take(100000).mkString

private val baseUrl = "http://localhost:8080"
private val headers = Headers(Header.ContentType(MediaType.text.`plain`).untyped)
private val headers = Headers(Header.ContentType(MediaType.text.`plain`))

private val arrayEndpoint = "array"
private val arrayResponse = ZIO.succeed(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package zio.http.endpoint.cli

import zio.http._
import zio.http.codec.HttpCodec.Query.QueryType
import zio.http.codec._
import zio.http.endpoint._

Expand Down Expand Up @@ -112,13 +111,11 @@ private[cli] object CliEndpoint {
}
CliEndpoint(body = HttpOptions.Body(name, codec.defaultMediaType, codec.defaultSchema) :: List())

case HttpCodec.Header(name, textCodec, _) if textCodec.isInstanceOf[TextCodec.Constant] =>
CliEndpoint(headers =
HttpOptions.HeaderConstant(name, textCodec.asInstanceOf[TextCodec.Constant].string) :: List(),
)
case HttpCodec.Header(name, textCodec, _) =>
CliEndpoint(headers = HttpOptions.Header(name, textCodec) :: List())
case HttpCodec.Method(codec, _) =>
case HttpCodec.Header(headerType, _) =>
CliEndpoint(headers = HttpOptions.Header(headerType.name, TextCodec.string) :: List())
case HttpCodec.HeaderCustom(codec, _) =>
CliEndpoint(headers = HttpOptions.Header(codec.name.get, TextCodec.string) :: List())
case HttpCodec.Method(codec, _) =>
codec.asInstanceOf[SimpleCodec[_, _]] match {
case SimpleCodec.Specified(method: Method) =>
CliEndpoint(methods = method)
Expand All @@ -128,22 +125,16 @@ private[cli] object CliEndpoint {
case HttpCodec.Path(pathCodec, _) =>
CliEndpoint(url = HttpOptions.Path(pathCodec) :: List())

case HttpCodec.Query(queryType, _) =>
queryType match {
case QueryType.Primitive(name, codec) =>
CliEndpoint(url = HttpOptions.Query(name, codec) :: List())
case record @ QueryType.Record(_) =>
val queryOptions = record.fieldAndCodecs.map { case (field, codec) =>
HttpOptions.Query(field.name, codec)
}
CliEndpoint(url = queryOptions.toList)
case QueryType.Collection(_, elements, _) =>
val queryOptions =
HttpOptions.Query(elements.name, elements.codec)
CliEndpoint(url = queryOptions :: List())
}

case HttpCodec.Status(_, _) => CliEndpoint.empty
case HttpCodec.Query(codec, _) =>
if (codec.isPrimitive)
CliEndpoint(url = HttpOptions.Query(codec) :: List())
else if (codec.isRecord)
CliEndpoint(url = codec.recordFields.map { case (_, codec) =>
HttpOptions.Query(codec)
}.toList)
else
CliEndpoint(url = HttpOptions.Query(codec) :: List())
case HttpCodec.Status(_, _) => CliEndpoint.empty

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import zio.schema._
import zio.schema.annotation.description

import zio.http._
import zio.http.codec.HttpCodec.SchemaCodec
import zio.http.codec._

/*
Expand Down Expand Up @@ -264,10 +265,9 @@ private[cli] object HttpOptions {

}

final case class Query(override val name: String, codec: BinaryCodecWithSchema[_], doc: Doc = Doc.empty)
extends URLOptions {
final case class Query(codec: SchemaCodec[_], doc: Doc = Doc.empty) extends URLOptions {
self =>

override val name = codec.name.get
override val tag = "?" + name
def options: Options[_] = optionsFromSchema(codec)(name)

Expand All @@ -293,7 +293,7 @@ private[cli] object HttpOptions {

}

private[cli] def optionsFromSchema[A](codec: BinaryCodecWithSchema[A]): String => Options[A] =
private[cli] def optionsFromSchema[A](codec: SchemaCodec[A]): String => Options[A] =
codec.schema match {
case Schema.Primitive(standardType, _) =>
standardType match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@ import zio.http.codec._
*/

object AuxGen {
lazy val anyTextCodec: Gen[Any, TextCodec[_]] =
Gen.oneOf(
Gen.fromIterable(List(TextCodec.boolean, TextCodec.int, TextCodec.string, TextCodec.uuid)),
Gen.alphaNumericStringBounded(1, 30).map(TextCodec.constant(_)),
)
lazy val anyTextCodec: Gen[Any, TextCodec[_]] = Gen.const(TextCodec.string)

lazy val anyMediaType: Gen[Any, MediaType] = Gen.fromIterable(MediaType.allMediaTypes)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ object CliSpec extends ZIOSpecDefault {

val bodyStream = ContentCodec.contentStream[BigInt]("bodyStream")

val headerCodec = HttpCodec.Header("header", TextCodec.string)
val headerCodec = HttpCodec.headerAs[String]("header")

val path1 = PathCodec.bool("path1")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,20 @@ object CommandGen {
case _: HttpOptions.Constant => false
case _ => true
}.map {
case HttpOptions.Path(pathCodec, _) =>
pathCodec.segments.toList.flatMap { case segment =>
case HttpOptions.Path(pathCodec, _) =>
pathCodec.segments.toList.flatMap { segment =>
getSegment(segment) match {
case (_, "") => Nil
case (name, "boolean") => s"[${getName(name, "")}]" :: Nil
case (name, codec) => s"${getName(name, "")} $codec" :: Nil
}
}
case HttpOptions.Query(name, codec, _) =>
getType(codec) match {
case "" => s"[${getName(name, "")}]" :: Nil
case codec => s"${getName(name, "")} $codec" :: Nil
case HttpOptions.Query(codec, _) if codec.isPrimitive =>
getType(codec.schema) match {
case "" => s"[${getName(codec.name.get, "")}]" :: Nil
case tpy => s"${getName(codec.name.get, "")} $tpy" :: Nil
}
case _ => Nil
case _ => Nil
}.foldRight(List[String]())(_ ++ _)

val headersOptions = cliEndpoint.headers.filter {
Expand Down Expand Up @@ -121,8 +121,8 @@ object CommandGen {
case _ => ""
}

def getType[A](codec: BinaryCodecWithSchema[A]): String =
codec.schema match {
def getType[A](schema: Schema[A]): String =
schema match {
case Schema.Primitive(standardType, _) =>
standardType match {
case StandardType.UnitType => ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import zio.test._

import zio.schema.Schema

import zio.http.Header.HeaderType
import zio.http._
import zio.http.codec.HttpCodec.SchemaCodec
import zio.http.codec._
import zio.http.endpoint._
import zio.http.endpoint.cli.AuxGen._
Expand Down Expand Up @@ -78,10 +80,9 @@ object EndpointGen {
lazy val anyHeader: Gen[Any, CliReprOf[Codec[_]]] =
Gen.alphaNumericStringBounded(1, 30).zip(anyTextCodec).map { case (name, codec) =>
CliRepr(
HttpCodec.Header(name, codec),
HttpCodec.Header(Header.Custom(name, "").headerType), // todo use schema bases header
codec match {
case TextCodec.Constant(value) => CliEndpoint(headers = HttpOptions.HeaderConstant(name, value) :: Nil)
case _ => CliEndpoint(headers = HttpOptions.Header(name, codec) :: Nil)
case _ => CliEndpoint(headers = HttpOptions.Header(name, codec) :: Nil)
},
)
}
Expand All @@ -102,10 +103,10 @@ object EndpointGen {
lazy val anyQuery: Gen[Any, CliReprOf[Codec[_]]] =
Gen.alphaNumericStringBounded(1, 30).zip(anyStandardType).map { case (name, schema0) =>
val schema = schema0.asInstanceOf[Schema[Any]]
val codec = BinaryCodecWithSchema(TextBinaryCodec.fromSchema(schema), schema)
val codec = SchemaCodec(Some(name), schema)
CliRepr(
HttpCodec.Query(HttpCodec.Query.QueryType.Primitive(name, codec)),
CliEndpoint(url = HttpOptions.Query(name, codec) :: Nil),
HttpCodec.Query(codec),
CliEndpoint(url = HttpOptions.Query(codec) :: Nil),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import zio.test.Gen
import zio.schema.Schema

import zio.http._
import zio.http.codec.HttpCodec.SchemaCodec
import zio.http.codec._
import zio.http.endpoint.cli.AuxGen._
import zio.http.endpoint.cli.CliRepr._
Expand All @@ -32,10 +33,10 @@ object OptionsGen {
.optionsFromTextCodec(textCodec)(name)
.map(value => textCodec.encode(value))

def encodeOptions[A](name: String, codec: BinaryCodecWithSchema[A]): Options[String] =
def encodeOptions[A](name: String, codec: SchemaCodec[A]): Options[String] =
HttpOptions
.optionsFromSchema(codec)(name)
.map(value => codec.codec(CodecConfig.defaultConfig).encode(value).asString)
.map(value => codec.stringCodec.encode(value))

lazy val anyBodyOption: Gen[Any, CliReprOf[Options[Retriever]]] =
Gen
Expand All @@ -50,18 +51,12 @@ object OptionsGen {
}

lazy val anyHeaderOption: Gen[Any, CliReprOf[Options[Headers]]] =
Gen.alphaNumericStringBounded(1, 30).zip(anyTextCodec).map {
case (name, TextCodec.Constant(value)) =>
CliRepr(
Options.Empty.map(_ => Headers(name, value)),
CliEndpoint(headers = HttpOptions.HeaderConstant(name, value) :: Nil),
)
case (name, codec) =>
CliRepr(
encodeOptions(name, codec)
.map(value => Headers(name, value)),
CliEndpoint(headers = HttpOptions.Header(name, codec) :: Nil),
)
Gen.alphaNumericStringBounded(1, 30).zip(anyTextCodec).map { case (name, codec) =>
CliRepr(
encodeOptions(name, codec)
.map(value => Headers(name, value)),
CliEndpoint(headers = HttpOptions.Header(name, codec) :: Nil),
)
}

lazy val anyURLOption: Gen[Any, CliReprOf[Options[String]]] =
Expand All @@ -83,14 +78,12 @@ object OptionsGen {
},
Gen
.alphaNumericStringBounded(1, 30)
.zip(anyStandardType.map { s =>
val schema = s.asInstanceOf[Schema[Any]]
BinaryCodecWithSchema(TextBinaryCodec.fromSchema(schema), schema)
})
.map { case (name, codec) =>
.zip(anyStandardType)
.map { case (name, schema) =>
val codec = SchemaCodec(Some(name), schema)
CliRepr(
encodeOptions(name, codec),
CliEndpoint(url = HttpOptions.Query(name, codec) :: Nil),
CliEndpoint(url = HttpOptions.Query(codec) :: Nil),
)
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ object CodeGenSpec extends ZIOSpecDefault {
Endpoint(Method.GET / "api" / "v1" / "users")
.header(HeaderCodec.accept)
.header(HeaderCodec.contentType)
.header(HeaderCodec.name[String]("Token"))
.header(HeaderCodec.headerAs[String]("Token"))
val openAPI = OpenAPIGen.fromEndpoints(endpoint)

codeGenFromOpenAPI(openAPI) { testDir =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ object AuthSpec extends ZIOSpecDefault {
.catchAllCause(c => ZIO.logInfoCause(c)) <* ZIO.sleep(1.seconds)
response <- response
} yield assertTrue(response == "admin")
} @@ flaky,
},
test("Auth basic or bearer with context and endpoint client") {
val endpoint =
Endpoint(Method.GET / "multiAuth")
Expand Down Expand Up @@ -212,7 +212,7 @@ object AuthSpec extends ZIOSpecDefault {
.catchAllCause(c => ZIO.logInfoCause(c)) <* ZIO.sleep(1.seconds)
response <- response
} yield assertTrue(response == "admin")
} @@ TestAspect.flaky,
},
test("Auth with context and endpoint client with path parameter") {
val endpoint =
Endpoint(Method.GET / int("a")).out[String](MediaType.text.`plain`).auth(AuthType.Basic)
Expand All @@ -237,7 +237,7 @@ object AuthSpec extends ZIOSpecDefault {
response <- response
} yield assertTrue(response == "admin")
},
).provideShared(Client.default, Server.default) @@ TestAspect.withLiveClock,
).provideShared(Client.default, Server.default) @@ TestAspect.withLiveClock @@ TestAspect.flaky,
test("Require Basic Auth, but get Bearer Auth") {
val endpoint = Endpoint(Method.GET / "test").out[String](MediaType.text.`plain`).auth(AuthType.Basic)
val routes =
Expand Down
Loading
Loading