Skip to content

Commit

Permalink
Try to read oneOf, before handling empty schema type as Object (#2623) (
Browse files Browse the repository at this point in the history
  • Loading branch information
987Nabil authored Jan 28, 2024
1 parent a70a964 commit fbc3ddc
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 27 deletions.
38 changes: 20 additions & 18 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 @@ -40,6 +40,21 @@ final case class EndpointGen() {

def fromOpenAPI(openAPI: OpenAPI): Code.Files =
Code.Files {
val componentsCode = openAPI.components.toList.flatMap { components =>
components.schemas.flatMap { case (OpenAPI.Key(name), refOrSchema) =>
var annotations: Chunk[JsonSchema.MetaData] = Chunk.empty
val schema = refOrSchema match {
case ReferenceOr.Or(schema: JsonSchema) =>
annotations = schema.annotations
schema.withoutAnnotations
case ReferenceOr.Reference(ref, _, _) =>
val schema = resolveSchemaRef(openAPI, ref)
annotations = schema.annotations
schema.withoutAnnotations
}
schemaToCode(schema, openAPI, name, annotations)
}
}
openAPI.paths.map { case (path, pathItem) =>
val pathSegments = path.name.tail.replace('-', '_').split('/').toList
val packageName = pathSegments.init.mkString(".").replace("{", "").replace("}", "")
Expand Down Expand Up @@ -103,22 +118,7 @@ final case class EndpointGen() {
caseClasses = Nil,
enums = Nil,
)
}.toList ++
openAPI.components.toList.flatMap { components =>
components.schemas.flatMap { case (OpenAPI.Key(name), refOrSchema) =>
var annotations: Chunk[JsonSchema.MetaData] = Chunk.empty
val schema = refOrSchema match {
case ReferenceOr.Or(schema: JsonSchema) =>
annotations = schema.annotations
schema.withoutAnnotations
case ReferenceOr.Reference(ref, _, _) =>
val schema = resolveSchemaRef(openAPI, ref)
annotations = schema.annotations
schema.withoutAnnotations
}
schemaToCode(schema, openAPI, name, annotations)
}
}
}.toList ++ componentsCode
}

private def fieldName(op: OpenAPI.Operation, fallback: String) =
Expand Down Expand Up @@ -617,11 +617,13 @@ final case class EndpointGen() {
throw new Exception(s"Unexpected subtype $other for anyOf schema $schema")
}
.toList
val noDiscriminator = caseNames.isEmpty
Some(
Code.File(
List("component", name.capitalize + ".scala"),
pkgPath = List("component"),
imports = dataImports(caseClasses.flatMap(_.fields)),
imports = dataImports(caseClasses.flatMap(_.fields)) ++
(if (noDiscriminator || caseNames.nonEmpty) List(Code.Import("zio.schema.annotation._")) else Nil),
objects = Nil,
caseClasses = Nil,
enums = List(
Expand All @@ -630,7 +632,7 @@ final case class EndpointGen() {
cases = caseClasses,
caseNames = caseNames,
discriminator = discriminator,
noDiscriminator = caseNames.isEmpty,
noDiscriminator = noDiscriminator,
schema = true,
),
),
Expand Down
107 changes: 107 additions & 0 deletions zio-http-gen/src/test/scala/zio/http/gen/openapi/EndpointGenSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import zio.http.endpoint.openapi.JsonSchema.SchemaStyle.Inline
import zio.http.endpoint.openapi.{OpenAPI, OpenAPIGen}
import zio.http.gen.model._
import zio.http.gen.scala.Code
import zio.http.gen.scala.Code.Collection.Opt

object EndpointGenSpec extends ZIOSpecDefault {
override def spec: Spec[TestEnvironment with Scope, Any] =
Expand Down Expand Up @@ -1110,6 +1111,112 @@ object EndpointGenSpec extends ZIOSpecDefault {
)
assertTrue(scala.files.head == expected)
},
test("generates code for oneOf schemas read from json") {
val openapiJson = """
|{
| "openapi": "3.0.3",
| "info": {
| "title": "Example",
| "description": "Example API documentation",
| "version": "1.0.0"
| },
| "paths": {
| "/foo": {
| "post": {
| "requestBody": {
| "content": {
| "application/json": {
| "schema": {
| "$ref": "#/components/schemas/Bar"
| }
| }
| }
| },
| "responses": {
| "200": {
| "description": "Success"
| }
| }
| }
| }
| },
| "components": {
| "schemas": {
| "Bar": {
| "type": "object",
| "properties": {
| "field": {
| "oneOf": [
| {
| "$ref": "#/components/schemas/Baz"
| },
| {
| "$ref": "#/components/schemas/Qux"
| }
| ]
| }
| }
| },
| "Baz": {
| "type": "object",
| "properties": {
| "stringField": {
| "type": "string"
| }
| }
| },
| "Qux": {
| "type": "object",
| "properties": {
| "stringField": {
| "type": "string"
| }
| }
| }
| }
| }
|} """.stripMargin

val openAPI = OpenAPI.fromJson(openapiJson).toOption.get
val scala = EndpointGen.fromOpenAPI(openAPI)

val expected = Code.File(
path = List("component", "Bar.scala"),
pkgPath = List("component"),
imports = List(Code.Import.Absolute(path = "zio.schema._")),
objects = Nil,
caseClasses = List(
Code.CaseClass(
name = "Bar",
fields = List(
Code.Field(
name = "field",
fieldType = Opt(
elementType = Code.ScalaType.Or(
Code.TypeRef("Baz"),
Code.TypeRef("Qux"),
),
),
),
),
companionObject = Some(
Code.Object(
name = "Bar",
schema = true,
endpoints = Map(
),
objects = Nil,
caseClasses = Nil,
enums = Nil,
),
),
),
),
enums = Nil,
)

assertTrue(scala.files.tail.head == expected)
},
),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ private[openapi] case class SerializableJsonSchema(
else if (nullable && oneOf.isDefined)
copy(oneOf = Some(oneOf.get :+ SerializableJsonSchema(schemaType = Some(TypeOrTypes.Type("null")))))
else if (nullable && allOf.isDefined)
SerializableJsonSchema(oneOf =
SerializableJsonSchema(allOf =
Some(Chunk(this, SerializableJsonSchema(schemaType = Some(TypeOrTypes.Type("null"))))),
)
else if (nullable && anyOf.isDefined)
Expand Down Expand Up @@ -233,14 +233,6 @@ object JsonSchema {
JsonSchema.Boolean
case schema if schema.schemaType.contains(TypeOrTypes.Type("array")) =>
JsonSchema.ArrayType(schema.items.map(fromSerializableSchema))
case schema if schema.schemaType.contains(TypeOrTypes.Type("object")) || schema.schemaType.isEmpty =>
JsonSchema.Object(
schema.properties
.map(_.map { case (name, schema) => name -> fromSerializableSchema(schema) })
.getOrElse(Map.empty),
additionalProperties,
schema.required.getOrElse(Chunk.empty),
)
case schema if schema.enumValues.isDefined =>
JsonSchema.Enum(schema.enumValues.get.map(EnumValue.fromJson))
case schema if schema.oneOf.isDefined =>
Expand All @@ -251,6 +243,14 @@ object JsonSchema {
AnyOfSchema(schema.anyOf.get.map(fromSerializableSchema))
case schema if schema.schemaType.contains(TypeOrTypes.Type("null")) =>
JsonSchema.Null
case schema if schema.schemaType.contains(TypeOrTypes.Type("object")) || schema.schemaType.isEmpty =>
JsonSchema.Object(
schema.properties
.map(_.map { case (name, schema) => name -> fromSerializableSchema(schema) })
.getOrElse(Map.empty),
additionalProperties,
schema.required.getOrElse(Chunk.empty),
)
case _ =>
throw new IllegalArgumentException(s"Can't convert $schema")
}
Expand Down

0 comments on commit fbc3ddc

Please sign in to comment.