Skip to content

Commit

Permalink
Merge branch 'main' into generate-media-types
Browse files Browse the repository at this point in the history
  • Loading branch information
jdegoes authored Jun 19, 2024
2 parents f4aa09a + 3bc7796 commit d617981
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 14 deletions.
47 changes: 33 additions & 14 deletions zio-http-gen/src/main/scala/zio/http/gen/openapi/EndpointGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -721,9 +721,9 @@ final case class EndpointGen(config: Config) {
case JsonSchema.Integer(_) => None
case JsonSchema.String(_, _) => None // this could maybe be im proved to generate a string type with validation
case JsonSchema.Boolean => None
case JsonSchema.OneOfSchema(schemas) if schemas.exists(_.isPrimitive) =>
case JsonSchema.OneOfSchema(schemas) if schemas.exists(_.isPrimitive) =>
throw new Exception("OneOf schemas with primitive types are not supported")
case JsonSchema.OneOfSchema(schemas) =>
case JsonSchema.OneOfSchema(schemas) =>
val discriminatorInfo =
annotations.collectFirst { case JsonSchema.MetaData.Discriminator(discriminator) => discriminator }
val discriminator: Option[String] = discriminatorInfo.map(_.propertyName)
Expand Down Expand Up @@ -783,7 +783,7 @@ final case class EndpointGen(config: Config) {
),
),
)
case JsonSchema.AllOfSchema(schemas) =>
case JsonSchema.AllOfSchema(schemas) =>
val genericFieldIndex = Iterator.from(0)
val unvalidatedFields = schemas.map(_.withoutAnnotations).flatMap {
case schema @ JsonSchema.Object(_, _, _) =>
Expand Down Expand Up @@ -828,9 +828,9 @@ final case class EndpointGen(config: Config) {
enums = Nil,
),
)
case JsonSchema.AnyOfSchema(schemas) if schemas.exists(_.isPrimitive) =>
case JsonSchema.AnyOfSchema(schemas) if schemas.exists(_.isPrimitive) =>
throw new Exception("AnyOf schemas with primitive types are not supported")
case JsonSchema.AnyOfSchema(schemas) =>
case JsonSchema.AnyOfSchema(schemas) =>
val discriminatorInfo =
annotations.collectFirst { case JsonSchema.MetaData.Discriminator(discriminator) => discriminator }
val discriminator: Option[String] = discriminatorInfo.map(_.propertyName)
Expand Down Expand Up @@ -887,12 +887,15 @@ final case class EndpointGen(config: Config) {
),
),
)
case JsonSchema.Number(_) => None
case JsonSchema.ArrayType(None) => None
case JsonSchema.ArrayType(Some(schema)) =>
case JsonSchema.Number(_) => None
case JsonSchema.ArrayType(None) => None
case JsonSchema.ArrayType(Some(schema)) =>
schemaToCode(schema, openAPI, name, annotations)
// TODO use additionalProperties
case obj @ JsonSchema.Object(properties, _, _) =>
case JsonSchema.Object(properties, additionalProperties, _)
if properties.nonEmpty && additionalProperties.isRight =>
// Can't be an object and a map at the same time
throw new Exception("Object with properties and additionalProperties is not supported")
case obj @ JsonSchema.Object(properties, additionalProperties, _) if additionalProperties.isLeft =>
val unvalidatedFields = fieldsOfObject(openAPI, annotations)(obj)
val fields = validateFields(unvalidatedFields)
val nested =
Expand Down Expand Up @@ -925,8 +928,10 @@ final case class EndpointGen(config: Config) {
enums = Nil,
),
)

case JsonSchema.Enum(enums) =>
case JsonSchema.Object(_, _, _) =>
// properties.isEmpty && additionalProperties.isRight
throw new IllegalArgumentException("Top-level maps are not supported")
case JsonSchema.Enum(enums) =>
Some(
Code.File(
List("component", name.capitalize + ".scala"),
Expand All @@ -947,8 +952,8 @@ final case class EndpointGen(config: Config) {
),
),
)
case JsonSchema.Null => throw new Exception("Null query parameters are not supported")
case JsonSchema.AnyJson => throw new Exception("AnyJson query parameters are not supported")
case JsonSchema.Null => throw new Exception("Null query parameters are not supported")
case JsonSchema.AnyJson => throw new Exception("AnyJson query parameters are not supported")
}
}

Expand Down Expand Up @@ -1038,6 +1043,20 @@ final case class EndpointGen(config: Config) {
Some(Code.Primitive.ScalaString.seq),
)
tpe.map(Code.Field(name, _))
case JsonSchema.Object(properties, additionalProperties, _)
if properties.nonEmpty && additionalProperties.isRight =>
// Can't be an object and a map at the same time
throw new Exception("Object with properties and additionalProperties is not supported")
case JsonSchema.Object(properties, additionalProperties, _)
if properties.isEmpty && additionalProperties.isRight =>
Some(
Code.Field(
name,
Code.Collection.Map(
schemaToField(additionalProperties.toOption.get, openAPI, name, annotations).get.fieldType,
),
),
)
case JsonSchema.Object(_, _, _) =>
Some(Code.Field(name, Code.TypeRef(name.capitalize)))
case JsonSchema.Enum(_) =>
Expand Down
13 changes: 13 additions & 0 deletions zio-http-gen/src/test/resources/AnimalWithMap.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package test.component

import zio.schema._

case class Animals(
total: Int,
counts: Map[String, Int],
)
object Animals {

implicit val codec: Schema[Animals] = DeriveSchema.gen[Animals]

}
76 changes: 76 additions & 0 deletions zio-http-gen/src/test/scala/zio/http/gen/scala/CodeGenSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -717,5 +717,81 @@ object CodeGenSpec extends ZIOSpecDefault {
"/EndpointsWithOverlappingPath.scala",
)
},
test("Additional properties") {
val json = """{
| "info": {
| "title": "Animals Service",
| "version": "0.0.1"
| },
| "servers": [
| {
| "url": "http://127.0.0.1:5000/"
| }
| ],
| "tags": [
| {
| "name": "Animals_API"
| }
| ],
| "paths": {
| "/api/v1/zoo": {
| "get": {
| "operationId": "get_animals",
| "tags": [
| "Animals_API"
| ],
| "description": "Get all animals count",
| "responses": {
| "200": {
| "content": {
| "application/json": {
| "schema": {
| "$ref": "#/components/schemas/Animals"
| }
| }
| }
| }
| }
| }
| }
| },
| "openapi": "3.0.3",
| "components": {
| "schemas": {
| "Animals": {
| "type": "object",
| "required": [
| "total",
| "counts"
| ],
| "properties": {
| "total": {
| "type": "integer",
| "format": "int32"
| },
| "counts": {
| "type": "object",
| "additionalProperties": {
| "type": "integer",
| "format": "int32"
| }
| }
| }
| }
| }
| }
|}""".stripMargin
val openAPI = OpenAPI.fromJson(json).toOption.get
val code = EndpointGen.fromOpenAPI(openAPI)
val tempDir = Files.createTempDirectory("codegen")

CodeGen.writeFiles(code, java.nio.file.Paths.get(tempDir.toString, "test"), "test", Some(scalaFmtPath))

fileShouldBe(
tempDir,
"test/component/Animals.scala",
"/AnimalWithMap.scala",
)
},
) @@ java11OrNewer @@ flaky @@ blocking // Downloading scalafmt on CI is flaky
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ sealed trait JsonSchema extends Product with Serializable { self =>

def isCollection: Boolean = self match {
case _: JsonSchema.ArrayType => true
case obj: JsonSchema.Object => obj.properties.isEmpty && obj.additionalProperties.isRight
case _ => false
}

Expand Down

2 comments on commit d617981

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 : Performance Benchmarks (SimpleEffectBenchmarkServer)

concurrency: 256
requests/sec: 331219

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 : Performance Benchmarks (PlainTextBenchmarkServer)

concurrency: 256
requests/sec: 327518

Please sign in to comment.