Skip to content

Commit

Permalink
Don't flatten nested case classes for JsonSchema (#2558)
Browse files Browse the repository at this point in the history
Correctly generate references instead of inlining schemas
  • Loading branch information
987Nabil authored Dec 13, 2023
1 parent 61fa20b commit f42676e
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -349,12 +349,11 @@ object JsonSchema {
val children = record.fields
.filterNot(_.annotations.exists(_.isInstanceOf[transientField]))
.flatMap { field =>
val key = nominal(field.schema, refType).orElse(nominal(field.schema, SchemaStyle.Compact))
val nested = fromZSchemaMulti(
field.schema,
refType,
)
key.map(k => nested.children + (k -> nested.root)).getOrElse(nested.children)
nested.rootRef.map(k => nested.children + (k -> nested.root)).getOrElse(nested.children)
}
.toMap
JsonSchemas(fromZSchema(record, SchemaStyle.Inline), ref, children)
Expand Down Expand Up @@ -496,7 +495,7 @@ object JsonSchema {
)
.addAll(nonTransientFields.map { field =>
field.name ->
fromZSchema(field.schema, refType)
fromZSchema(field.schema, SchemaStyle.Compact)
.deprecated(deprecated(field.schema))
.description(fieldDoc(field))
.default(fieldDefault(field))
Expand Down
172 changes: 142 additions & 30 deletions zio-http/src/test/scala/zio/http/endpoint/openapi/OpenAPIGenSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ object OpenAPIGenSpec extends ZIOSpecDefault {
implicit val withOptionalFieldSchema: Schema[WithOptionalField] =
DeriveSchema.gen[WithOptionalField]

final case class NestedProduct(imageMetadata: ImageMetadata, withOptionalField: WithOptionalField)
implicit val nestedProductSchema: Schema[NestedProduct] =
DeriveSchema.gen[NestedProduct]

sealed trait SimpleEnum
object SimpleEnum {
implicit val schema: Schema[SimpleEnum] = DeriveSchema.gen[SimpleEnum]
Expand Down Expand Up @@ -1631,31 +1635,35 @@ object OpenAPIGenSpec extends ZIOSpecDefault {
| },
| "components" : {
| "schemas" : {
| "ImageMetadata" :
| {
| "type" :
| "object",
| "properties" : {
| "name" : {
| "type" :
| "string"
| },
| "size" : {
| "type" :
| "integer",
| "format" : "int32"
| }
| },
| "additionalProperties" :
| true,
| "required" : [
| "name",
| "size"
| ]
| },
| "WithComplexDefaultValue" :
| {
| "type" :
| "object",
| "properties" : {
| "data" : {
| "type" :
| "object",
| "properties" : {
| "name" : {
| "type" :
| "string"
| },
| "size" : {
| "type" :
| "integer",
| "format" : "int32"
| }
| },
| "additionalProperties" :
| true,
| "required" : [
| "name",
| "size"
| ],
| "$ref" : "#/components/schemas/ImageMetadata",
| "description" : "If not set, this field defaults to the value of the default annotation.",
| "default" : {
| "name" : "default",
Expand Down Expand Up @@ -1728,6 +1736,105 @@ object OpenAPIGenSpec extends ZIOSpecDefault {
|}""".stripMargin
assertTrue(json == toJsonAst(expected))
},
test("nested product") {
val endpoint = Endpoint(GET / "static").in[NestedProduct]
val generated = OpenAPIGen.fromEndpoints("Simple Endpoint", "1.0", endpoint)
println(generated.toJsonPretty)
val json = toJsonAst(generated)
val expected = """{
| "openapi" : "3.1.0",
| "info" : {
| "title" : "Simple Endpoint",
| "version" : "1.0"
| },
| "paths" : {
| "/static" : {
| "get" : {
| "requestBody" :
| {
| "content" : {
| "application/json" : {
| "schema" :
| {
| "$ref" : "#/components/schemas/NestedProduct"
| }
| }
| },
| "required" : true
| },
| "deprecated" : false
| }
| }
| },
| "components" : {
| "schemas" : {
| "ImageMetadata" :
| {
| "type" :
| "object",
| "properties" : {
| "name" : {
| "type" :
| "string"
| },
| "size" : {
| "type" :
| "integer",
| "format" : "int32"
| }
| },
| "additionalProperties" :
| true,
| "required" : [
| "name",
| "size"
| ]
| },
| "WithOptionalField" :
| {
| "type" :
| "object",
| "properties" : {
| "name" : {
| "type" :
| "string"
| },
| "age" : {
| "type" :
| "integer",
| "format" : "int32"
| }
| },
| "additionalProperties" :
| true,
| "required" : [
| "name"
| ]
| },
| "NestedProduct" :
| {
| "type" :
| "object",
| "properties" : {
| "imageMetadata" : {
| "$ref" : "#/components/schemas/ImageMetadata"
| },
| "withOptionalField" : {
| "$ref" : "#/components/schemas/WithOptionalField"
| }
| },
| "additionalProperties" :
| true,
| "required" : [
| "imageMetadata",
| "withOptionalField"
| ]
| }
| }
| }
|}""".stripMargin
assertTrue(json == toJsonAst(expected))
},
test("enum") {
val endpoint = Endpoint(GET / "static").in[SimpleEnum]
val generated = OpenAPIGen.fromEndpoints("Simple Endpoint", "1.0", endpoint)
Expand Down Expand Up @@ -2091,6 +2198,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault {
test("sealed trait with nested sealed trait") {
val endpoint = Endpoint(GET / "static").in[SimpleNestedSealedTrait]
val generated = OpenAPIGen.fromEndpoints("Simple Endpoint", "1.0", endpoint)
println(generated.toJsonPretty)
val json = toJsonAst(generated)
val expectedJson =
"""{
Expand Down Expand Up @@ -2120,6 +2228,20 @@ object OpenAPIGenSpec extends ZIOSpecDefault {
| },
| "components" : {
| "schemas" : {
| "SealedTraitNoDiscriminator" :
| {
| "oneOf" : [
| {
| "$ref" : "#/components/schemas/One"
| },
| {
| "$ref" : "#/components/schemas/Two"
| },
| {
| "$ref" : "#/components/schemas/Three"
| }
| ]
| },
| "NestedOne" :
| {
| "type" :
Expand Down Expand Up @@ -2150,17 +2272,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault {
| "object",
| "properties" : {
| "name" : {
| "oneOf" : [
| {
| "$ref" : "#/components/schemas/One"
| },
| {
| "$ref" : "#/components/schemas/Two"
| },
| {
| "$ref" : "#/components/schemas/Three"
| }
| ]
| "$ref" : "#/components/schemas/SealedTraitNoDiscriminator"
| }
| },
| "additionalProperties" :
Expand Down

0 comments on commit f42676e

Please sign in to comment.