diff --git a/zio-http/jvm/src/test/resources/endpoint/openapi/multiple-methods-on-same-path.json b/zio-http/jvm/src/test/resources/endpoint/openapi/multiple-methods-on-same-path.json index 8ec0699ba6..a8be6d5d5a 100644 --- a/zio-http/jvm/src/test/resources/endpoint/openapi/multiple-methods-on-same-path.json +++ b/zio-http/jvm/src/test/resources/endpoint/openapi/multiple-methods-on-same-path.json @@ -1,60 +1,72 @@ { - "openapi": "3.1.0", - "info": { - "title": "Multiple Methods on Same Path", - "version": "1.0" + "openapi" : "3.1.0", + "info" : { + "title" : "Multiple Methods on Same Path", + "version" : "1.0" }, - "paths": { - "/test": { - "get": { - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "null" + "paths" : { + "/test" : { + "get" : { + "requestBody" : + { + "content" : { + "application/json" : { + "schema" : + { + "type" : + "null" } } }, - "required": false + "required" : false }, - "responses": { - "200": { - "content": { - "text/plain": { - "schema": { - "type": "string" + "responses" : { + "200" : + { + "content" : { + "text/plain" : { + "schema" : + { + "type" : + "string" } } } } - }, - "deprecated": false + } }, - "post": { - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "string" + "post" : { + "requestBody" : + { + "content" : { + "application/json" : { + "schema" : + { + "type" : + "string" } } }, - "required": true + "required" : true }, - "responses": { - "201": { - "content": { - "text/plain": { - "schema": { - "type": "string" + "responses" : { + "201" : + { + "content" : { + "text/plain" : { + "schema" : + { + "type" : + "string" } } } } - }, - "deprecated": false + } } } }, - "components": {} + "components" : { + + } } diff --git a/zio-http/jvm/src/test/scala/zio/http/endpoint/openapi/OpenAPIGenSpec.scala b/zio-http/jvm/src/test/scala/zio/http/endpoint/openapi/OpenAPIGenSpec.scala index 119a3e35e8..35f3ba91d1 100644 --- a/zio-http/jvm/src/test/scala/zio/http/endpoint/openapi/OpenAPIGenSpec.scala +++ b/zio-http/jvm/src/test/scala/zio/http/endpoint/openapi/OpenAPIGenSpec.scala @@ -170,14 +170,12 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "name" : "id", | "in" : "path", | "required" : true, - | "deprecated" : false, | "schema" : | { | "type" : | "integer", | "format" : "int32" | }, - | "explode" : false, | "style" : "simple" | }, | @@ -186,14 +184,12 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "in" : "path", | "description" : "user id\n\n", | "required" : true, - | "deprecated" : false, | "schema" : | { | "type" : | "string", | "format" : "uuid" | }, - | "explode" : false, | "style" : "simple" | }, | @@ -201,13 +197,11 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "name" : "name", | "in" : "path", | "required" : true, - | "deprecated" : false, | "schema" : | { | "type" : | "string" | }, - | "explode" : false, | "style" : "simple" | } | ], @@ -249,49 +243,44 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | } | } - | }, - | "deprecated" : false + | } | } | } | }, | "components" : { | "schemas" : { - | "SimpleInputBody" : + | "NotFoundError" : | { | "type" : | "object", | "properties" : { - | "name" : { + | "message" : { | "type" : | "string" - | }, - | "age" : { - | "type" : - | "integer", - | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ - | "name", - | "age" + | "message" | ] | }, - | "NotFoundError" : + | "SimpleInputBody" : | { | "type" : | "object", | "properties" : { - | "message" : { + | "name" : { | "type" : | "string" + | }, + | "age" : { + | "type" : + | "integer", + | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ - | "message" + | "name", + | "age" | ] | }, | "SimpleOutputBody" : @@ -309,8 +298,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "userName", | "score" @@ -334,17 +321,16 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "/withQuery" : { | "get" : { | "parameters" : [ - | { + | + | { | "name" : "query", | "in" : "query", | "required" : true, - | "deprecated" : false, | "schema" : | { - | "type" : - | "string" - | }, - | "explode" : false, + | "type" : + | "string" + | }, | "allowReserved" : false, | "style" : "form" | } @@ -353,7 +339,10 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | { | "content" : { | "application/json" : { - | "schema" : {"$ref": "#/components/schemas/SimpleInputBody"} + | "schema" : + | { + | "$ref" : "#/components/schemas/SimpleInputBody" + | } | } | }, | "required" : true @@ -363,7 +352,10 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | { | "content" : { | "application/json" : { - | "schema" : {"$ref": "#/components/schemas/SimpleOutputBody"} + | "schema" : + | { + | "$ref" : "#/components/schemas/SimpleOutputBody" + | } | } | } | }, @@ -371,53 +363,51 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | { | "content" : { | "application/json" : { - | "schema" : {"$ref": "#/components/schemas/NotFoundError"} + | "schema" : + | { + | "$ref" : "#/components/schemas/NotFoundError" + | } + | } | } | } | } - | }, - | "deprecated" : false + | } | } - | } | }, | "components" : { | "schemas" : { - | "SimpleInputBody" : + | "NotFoundError" : | { | "type" : | "object", | "properties" : { - | "name" : { + | "message" : { | "type" : | "string" - | }, - | "age" : { - | "type" : - | "integer", - | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ - | "name", - | "age" + | "message" | ] | }, - | "NotFoundError" : + | "SimpleInputBody" : | { | "type" : | "object", | "properties" : { - | "message" : { + | "name" : { | "type" : | "string" + | }, + | "age" : { + | "type" : + | "integer", + | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ - | "message" + | "name", + | "age" | ] | }, | "SimpleOutputBody" : @@ -435,8 +425,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "userName", | "score" @@ -464,19 +452,20 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | { | "content" : { | "application/json" : { - | "schema" : { - | "anyOf" : [ - | { - | "$ref": "#/components/schemas/OtherSimpleInputBody", - | "description" : "other input\n\n" - | }, - | { - | "$ref": "#/components/schemas/SimpleInputBody", - | "description" : "simple input\n\n" - | } - | ], - | "description" : "takes either of the two input bodies\n\n" - | } + | "schema" : + | { + | "anyOf" : [ + | { + | "$ref" : "#/components/schemas/OtherSimpleInputBody", + | "description" : "other input\n\n" + | }, + | { + | "$ref" : "#/components/schemas/SimpleInputBody", + | "description" : "simple input\n\n" + | } + | ], + | "description" : "takes either of the two input bodies\n\n" + | } | } | }, | "required" : true @@ -486,7 +475,10 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | { | "content" : { | "application/json" : { - | "schema" : {"$ref": "#/components/schemas/SimpleOutputBody"} + | "schema" : + | { + | "$ref" : "#/components/schemas/SimpleOutputBody" + | } | } | } | }, @@ -494,17 +486,33 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | { | "content" : { | "application/json" : { - | "schema" : {"$ref": "#/components/schemas/NotFoundError"} + | "schema" : + | { + | "$ref" : "#/components/schemas/NotFoundError" + | } + | } | } | } | } - | }, - | "deprecated" : false + | } | } - | } | }, | "components" : { | "schemas" : { + | "NotFoundError" : + | { + | "type" : + | "object", + | "properties" : { + | "message" : { + | "type" : + | "string" + | } + | }, + | "required" : [ + | "message" + | ] + | }, | "OtherSimpleInputBody" : | { | "type" : @@ -520,8 +528,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "fullName", | "shoeSize" @@ -542,29 +548,11 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name", | "age" | ] | }, - | "NotFoundError" : - | { - | "type" : - | "object", - | "properties" : { - | "message" : { - | "type" : - | "string" - | } - | }, - | "additionalProperties" : - | true, - | "required" : [ - | "message" - | ] - | }, | "SimpleOutputBody" : | { | "type" : @@ -580,8 +568,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "userName", | "score" @@ -616,7 +602,10 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | { | "content" : { | "application/json" : { - | "schema" : {"$ref": "#/components/schemas/SimpleInputBody"} + | "schema" : + | { + | "$ref" : "#/components/schemas/SimpleInputBody" + | } | } | }, | "required" : true @@ -626,28 +615,43 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | { | "content" : { | "application/json" : { - | "schema" : { "anyOf" : [ - | { - | "$ref": "#/components/schemas/SimpleOutputBody", - | "description" : "simple output\n\n" - | }, - | { - | "$ref": "#/components/schemas/NotFoundError", - | "description" : "not found\n\n" - | } - | ], - | "description" : "alternative outputs\n\n" - | } + | "schema" : + | { + | "anyOf" : [ + | { + | "$ref" : "#/components/schemas/SimpleOutputBody", + | "description" : "simple output\n\n" + | }, + | { + | "$ref" : "#/components/schemas/NotFoundError", + | "description" : "not found\n\n" + | } + | ], + | "description" : "alternative outputs\n\n" + | } | } | } | } - | }, - | "deprecated" : false + | } | } | } | }, | "components" : { | "schemas" : { + | "NotFoundError" : + | { + | "type" : + | "object", + | "properties" : { + | "message" : { + | "type" : + | "string" + | } + | }, + | "required" : [ + | "message" + | ] + | }, | "SimpleInputBody" : | { | "type" : @@ -663,8 +667,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name", | "age" @@ -685,28 +687,10 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "userName", | "score" | ] - | }, - | "NotFoundError" : - | { - | "type" : - | "object", - | "properties" : { - | "message" : { - | "type" : - | "string" - | } - | }, - | "additionalProperties" : - | true, - | "required" : [ - | "message" - | ] | } | } | } @@ -812,13 +796,26 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | } | } - | }, - | "deprecated" : false + | } | } | } | }, | "components" : { | "schemas" : { + | "NotFoundError" : + | { + | "type" : + | "object", + | "properties" : { + | "message" : { + | "type" : + | "string" + | } + | }, + | "required" : [ + | "message" + | ] + | }, | "SimpleInputBody" : | { | "type" : @@ -834,8 +831,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name", | "age" @@ -856,28 +851,10 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "userName", | "score" | ] - | }, - | "NotFoundError" : - | { - | "type" : - | "object", - | "properties" : { - | "message" : { - | "type" : - | "string" - | } - | }, - | "additionalProperties" : - | true, - | "required" : [ - | "message" - | ] | } | } | } @@ -897,8 +874,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { .outCodec( HttpCodec .content[SimpleOutputBody] ?? Doc.p("simple output") | - HttpCodec - .content[NotFoundError] ?? Doc.p("not found"), + HttpCodec.content[NotFoundError] ?? Doc.p("not found"), ) val generated = OpenAPIGen.fromEndpoints("Simple Endpoint", "1.0", endpoint) @@ -919,13 +895,11 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "name" : "query", | "in" : "query", | "required" : true, - | "deprecated" : false, | "schema" : | { | "type" : | "string" | }, - | "explode" : false, | "allowReserved" : false, | "style" : "form" | } @@ -974,13 +948,26 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | } | } - | }, - | "deprecated" : false + | } | } | } | }, | "components" : { | "schemas" : { + | "NotFoundError" : + | { + | "type" : + | "object", + | "properties" : { + | "message" : { + | "type" : + | "string" + | } + | }, + | "required" : [ + | "message" + | ] + | }, | "OtherSimpleInputBody" : | { | "type" : @@ -996,8 +983,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "fullName", | "shoeSize" @@ -1018,8 +1003,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name", | "age" @@ -1040,28 +1023,10 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "userName", | "score" | ] - | }, - | "NotFoundError" : - | { - | "type" : - | "object", - | "properties" : { - | "message" : { - | "type" : - | "string" - | } - | }, - | "additionalProperties" : - | true, - | "required" : [ - | "message" - | ] | } | } | } @@ -1152,8 +1117,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | } | } - | }, - | "deprecated" : false + | } | } | } | }, @@ -1174,8 +1138,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name", | "size" @@ -1204,23 +1166,71 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "version" : "1.0" | }, | "paths" : { + | "/inputAlternative" : { + | "get" : { + | "requestBody" : + | { + | "content" : { + | "application/json" : { + | "schema" : + | { + | "anyOf" : [ + | { + | "$ref" : "#/components/schemas/OtherSimpleInputBody", + | "description" : "other input\n\n" + | }, + | { + | "$ref" : "#/components/schemas/SimpleInputBody", + | "description" : "simple input\n\n" + | } + | ], + | "description" : "takes either of the two input bodies\n\n" + | } + | } + | }, + | "required" : true + | }, + | "responses" : { + | "200" : + | { + | "content" : { + | "application/json" : { + | "schema" : + | { + | "$ref" : "#/components/schemas/SimpleOutputBody" + | } + | } + | } + | }, + | "404" : + | { + | "content" : { + | "application/json" : { + | "schema" : + | { + | "$ref" : "#/components/schemas/NotFoundError" + | } + | } + | } + | } + | } + | } + | }, | "/static/{id}/{uuid}/{name}" : { | "get" : { - | "description": "get path\n\n", + | "description" : "get path\n\n", | "parameters" : [ | | { | "name" : "id", | "in" : "path", | "required" : true, - | "deprecated" : false, | "schema" : | { | "type" : | "integer", | "format" : "int32" | }, - | "explode" : false, | "style" : "simple" | }, | @@ -1229,13 +1239,12 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "in" : "path", | "description" : "user id\n\n", | "required" : true, - | "deprecated" : false, | "schema" : | { - | "type" : "string", + | "type" : + | "string", | "format" : "uuid" | }, - | "explode" : false, | "style" : "simple" | }, | @@ -1243,13 +1252,11 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "name" : "name", | "in" : "path", | "required" : true, - | "deprecated" : false, | "schema" : | { | "type" : | "string" | }, - | "explode" : false, | "style" : "simple" | } | ], @@ -1291,8 +1298,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | } | } - | }, - | "deprecated" : false + | } | } | }, | "/withQuery" : { @@ -1303,13 +1309,11 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "name" : "query", | "in" : "query", | "required" : true, - | "deprecated" : false, | "schema" : | { | "type" : | "string" | }, - | "explode" : false, | "allowReserved" : false, | "style" : "form" | } @@ -1349,144 +1353,84 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | } | } - | }, - | "deprecated" : false - | } - | }, - | "/inputAlternative" : { - | "get" : { - | "requestBody" : - | { - | "content" : { - | "application/json" : { - | "schema" : - | { - | "anyOf" : [ - | { - | "$ref" : "#/components/schemas/OtherSimpleInputBody", - | "description" : "other input\n\n" - | }, - | { - | "$ref" : "#/components/schemas/SimpleInputBody", - | "description" : "simple input\n\n" - | } - | ], - | "description" : "takes either of the two input bodies\n\n" - | } - | } - | }, - | "required" : true - | }, - | "responses" : { - | "200" : - | { - | "content" : { - | "application/json" : { - | "schema" : - | { - | "$ref" : "#/components/schemas/SimpleOutputBody" - | } - | } - | } - | }, - | "404" : - | { - | "content" : { - | "application/json" : { - | "schema" : - | { - | "$ref" : "#/components/schemas/NotFoundError" - | } - | } - | } - | } - | }, - | "deprecated" : false + | } | } | } | }, | "components" : { | "schemas" : { - | "SimpleInputBody" : + | "NotFoundError" : | { | "type" : | "object", | "properties" : { - | "name" : { + | "message" : { | "type" : | "string" - | }, - | "age" : { - | "type" : - | "integer", - | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ - | "name", - | "age" + | "message" | ] | }, - | "NotFoundError" : + | "OtherSimpleInputBody" : | { | "type" : | "object", | "properties" : { - | "message" : { + | "fullName" : { | "type" : | "string" + | }, + | "shoeSize" : { + | "type" : + | "integer", + | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ - | "message" + | "fullName", + | "shoeSize" | ] | }, - | "SimpleOutputBody" : + | "SimpleInputBody" : | { | "type" : | "object", | "properties" : { - | "userName" : { + | "name" : { | "type" : | "string" | }, - | "score" : { + | "age" : { | "type" : | "integer", | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ - | "userName", - | "score" + | "name", + | "age" | ] | }, - | "OtherSimpleInputBody" : + | "SimpleOutputBody" : | { | "type" : | "object", | "properties" : { - | "fullName" : { + | "userName" : { | "type" : | "string" | }, - | "shoeSize" : { + | "score" : { | "type" : | "integer", | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ - | "fullName", - | "shoeSize" + | "userName", + | "score" | ] | } | } @@ -1518,8 +1462,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | }, | "required" : true - | }, - | "deprecated" : false + | } | } | } | }, @@ -1535,8 +1478,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "string" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name" | ] @@ -1571,8 +1512,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | }, | "required" : true - | }, - | "deprecated" : false + | } | } | } | }, @@ -1590,9 +1530,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "description" : "If not set, this field defaults to the value of the default annotation.", | "default" : 42 | } - | }, - | "additionalProperties" : - | true + | } | } | } | } @@ -1624,8 +1562,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | }, | "required" : true - | }, - | "deprecated" : false + | } | } | } | }, @@ -1646,8 +1583,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name", | "size" @@ -1666,9 +1601,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "size" : 42 | } | } - | }, - | "additionalProperties" : - | true + | } | } | } | } @@ -1699,8 +1632,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | }, | "required" : true - | }, - | "deprecated" : false + | } | } | } | }, @@ -1721,8 +1653,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name" | ] @@ -1756,8 +1686,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | }, | "required" : true - | }, - | "deprecated" : false + | } | } | } | }, @@ -1778,51 +1707,45 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name", | "size" | ] | }, - | "WithOptionalField" : + | "NestedProduct" : | { | "type" : | "object", | "properties" : { - | "name" : { - | "type" : - | "string" + | "imageMetadata" : { + | "$ref" : "#/components/schemas/ImageMetadata" | }, - | "age" : { - | "type" : - | "integer", - | "format" : "int32" + | "withOptionalField" : { + | "$ref" : "#/components/schemas/WithOptionalField" | } | }, - | "additionalProperties" : - | true, | "required" : [ - | "name" + | "imageMetadata", + | "withOptionalField" | ] | }, - | "NestedProduct" : + | "WithOptionalField" : | { | "type" : | "object", | "properties" : { - | "imageMetadata" : { - | "$ref" : "#/components/schemas/ImageMetadata" + | "name" : { + | "type" : + | "string" | }, - | "withOptionalField" : { - | "$ref" : "#/components/schemas/WithOptionalField" + | "age" : { + | "type" : + | "integer", + | "format" : "int32" | } | }, - | "additionalProperties" : - | true, | "required" : [ - | "imageMetadata", - | "withOptionalField" + | "name" | ] | } | } @@ -1854,8 +1777,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | }, | "required" : true - | }, - | "deprecated" : false + | } | } | } | }, @@ -1901,8 +1823,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | }, | "required" : true - | }, - | "deprecated" : false + | } | } | } | }, @@ -1912,41 +1833,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | { | "type" : | "object", - | "properties" : {}, - | "additionalProperties" : - | true - | }, - | "Two" : - | { - | "type" : - | "object", - | "properties" : { - | "name" : { - | "type" : - | "string" - | } - | }, - | "additionalProperties" : - | true, - | "required" : [ - | "name" - | ] - | }, - | "Three" : - | { - | "type" : - | "object", - | "properties" : { - | "name" : { - | "type" : - | "string" - | } - | }, - | "additionalProperties" : - | true, - | "required" : [ - | "name" - | ] + | "properties" : {} | }, | "SealedTraitDefaultDiscriminator" : | { @@ -1994,6 +1881,34 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | ] | } | ] + | }, + | "Three" : + | { + | "type" : + | "object", + | "properties" : { + | "name" : { + | "type" : + | "string" + | } + | }, + | "required" : [ + | "name" + | ] + | }, + | "Two" : + | { + | "type" : + | "object", + | "properties" : { + | "name" : { + | "type" : + | "string" + | } + | }, + | "required" : [ + | "name" + | ] | } | } | } @@ -2025,8 +1940,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | }, | "required" : true - | }, - | "deprecated" : false + | } | } | } | }, @@ -2036,11 +1950,31 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | { | "type" : | "object", - | "properties" : {}, - | "additionalProperties" : - | true + | "properties" : {} | }, - | "Two" : + | "SealedTraitCustomDiscriminator" : + | { + | "oneOf" : [ + | { + | "$ref" : "#/components/schemas/One" + | }, + | { + | "$ref" : "#/components/schemas/Two" + | }, + | { + | "$ref" : "#/components/schemas/Three" + | } + | ], + | "discriminator" : { + | "propertyName" : "type", + | "mapping" : { + | "One" : "#/components/schemas/One}", + | "Two" : "#/components/schemas/Two}", + | "three" : "#/components/schemas/Three}" + | } + | } + | }, + | "Three" : | { | "type" : | "object", @@ -2050,13 +1984,11 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "string" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name" | ] | }, - | "Three" : + | "Two" : | { | "type" : | "object", @@ -2066,33 +1998,9 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "string" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name" | ] - | }, - | "SealedTraitCustomDiscriminator" : - | { - | "oneOf" : [ - | { - | "$ref" : "#/components/schemas/One" - | }, - | { - | "$ref" : "#/components/schemas/Two" - | }, - | { - | "$ref" : "#/components/schemas/Three" - | } - | ], - | "discriminator" : { - | "propertyName" : "type", - | "mapping" : { - | "One" : "#/components/schemas/One}", - | "Two" : "#/components/schemas/Two}", - | "three" : "#/components/schemas/Three}" - | } - | } | } | } | } @@ -2123,8 +2031,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | }, | "required" : true - | }, - | "deprecated" : false + | } | } | } | }, @@ -2134,11 +2041,23 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | { | "type" : | "object", - | "properties" : {}, - | "additionalProperties" : - | true + | "properties" : {} | }, - | "Two" : + | "SealedTraitNoDiscriminator" : + | { + | "oneOf" : [ + | { + | "$ref" : "#/components/schemas/One" + | }, + | { + | "$ref" : "#/components/schemas/Two" + | }, + | { + | "$ref" : "#/components/schemas/Three" + | } + | ] + | }, + | "Three" : | { | "type" : | "object", @@ -2148,13 +2067,11 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "string" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name" | ] | }, - | "Three" : + | "Two" : | { | "type" : | "object", @@ -2164,30 +2081,13 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "string" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name" | ] - | }, - | "SealedTraitNoDiscriminator" : - | { - | "oneOf" : [ - | { - | "$ref" : "#/components/schemas/One" - | }, - | { - | "$ref" : "#/components/schemas/Two" - | }, - | { - | "$ref" : "#/components/schemas/Three" - | } - | ] | } | } | } - |} - |""".stripMargin + |}""".stripMargin assertTrue(json == toJsonAst(expected)) }, test("sealed trait with nested sealed trait") { @@ -2215,8 +2115,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | }, | "required" : true - | }, - | "deprecated" : false + | } | } | } | }, @@ -2240,9 +2139,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | { | "type" : | "object", - | "properties" : {}, - | "additionalProperties" : - | true + | "properties" : {} | }, | "NestedThree" : | { @@ -2254,8 +2151,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "string" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name" | ] @@ -2269,8 +2164,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "$ref" : "#/components/schemas/SealedTraitNoDiscriminator" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name" | ] @@ -2285,8 +2178,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "string" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name" | ] @@ -2301,8 +2192,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "string" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "name" | ] @@ -2311,9 +2200,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | { | "type" : | "object", - | "properties" : {}, - | "additionalProperties" : - | true + | "properties" : {} | }, | "SimpleNestedSealedTrait" : | { @@ -2368,75 +2255,84 @@ object OpenAPIGenSpec extends ZIOSpecDefault { ) val json = toJsonAst(openApi) val expectedJson = """{ - | "openapi": "3.1.0", - | "info": { - | "title": "Combined input examples", - | "version": "1.0" + | "openapi" : "3.1.0", + | "info" : { + | "title" : "Combined input examples", + | "version" : "1.0" | }, - | "paths": { - | "/root/{name}": { - | "get": { - | "parameters": [ - | { - | "name": "name", - | "in": "path", - | "required": true, - | "deprecated": false, - | "schema": { - | "type": "string" + | "paths" : { + | "/root/{name}" : { + | "get" : { + | "parameters" : [ + | + | { + | "name" : "name", + | "in" : "path", + | "required" : true, + | "schema" : + | { + | "type" : + | "string" | }, - | "explode": false, - | "examples": { - | "hi": { - | "value": "name_value" + | "examples" : { + | "hi" : + | { + | "value" : "name_value" | } | }, - | "style": "simple" + | "style" : "simple" | } | ], - | "requestBody": { - | "content": { - | "application/json": { - | "schema": { - | "$ref": "#/components/schemas/Payload" + | "requestBody" : + | { + | "content" : { + | "application/json" : { + | "schema" : + | { + | "$ref" : "#/components/schemas/Payload" | }, - | "examples": { - | "hi": { - | "value": { - | "content": "input" + | "examples" : { + | "hi" : + | { + | "value" : { + | "content" : "input" | } | } | } | } | }, - | "required": true + | "required" : true | }, - | "responses": { - | "200": { - | "content": { - | "application/json": { - | "schema": { - | "type": "string" + | "responses" : { + | "200" : + | { + | "content" : { + | "application/json" : { + | "schema" : + | { + | "type" : + | "string" | } | } | } | } - | }, - | "deprecated": false + | } | } | } | }, - | "components": { - | "schemas": { - | "Payload": { - | "type": "object", - | "properties": { - | "content": { - | "type": "string" + | "components" : { + | "schemas" : { + | "Payload" : + | { + | "type" : + | "object", + | "properties" : { + | "content" : { + | "type" : + | "string" | } | }, - | "additionalProperties": true, - | "required": [ + | "required" : [ | "content" | ] | } @@ -2473,13 +2369,11 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "name" : "name", | "in" : "path", | "required" : true, - | "deprecated" : false, | "schema" : | { | "type" : | "string" | }, - | "explode" : false, | "examples" : { | "hi" : | { @@ -2539,8 +2433,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | } | } | } - | }, - | "deprecated" : false + | } | } | } | }, @@ -2556,8 +2449,6 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "string" | } | }, - | "additionalProperties" : - | true, | "required" : [ | "content" | ] diff --git a/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/JsonSchema.scala b/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/JsonSchema.scala index 1f4d937852..29bf4e5ce4 100644 --- a/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/JsonSchema.scala +++ b/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/JsonSchema.scala @@ -159,7 +159,8 @@ sealed trait JsonSchema extends Product with Serializable { self => def description: Option[String] = self.toSerializableSchema.description def nullable(nullable: Boolean): JsonSchema = - JsonSchema.AnnotatedSchema(self, JsonSchema.MetaData.Nullable(nullable)) + if (nullable) JsonSchema.AnnotatedSchema(self, JsonSchema.MetaData.Nullable(true)) + else self def discriminator(discriminator: OpenAPI.Discriminator): JsonSchema = JsonSchema.AnnotatedSchema(self, JsonSchema.MetaData.Discriminator(discriminator)) @@ -896,7 +897,7 @@ object JsonSchema { override protected[openapi] def toSerializableSchema: SerializableJsonSchema = { val additionalProperties = this.additionalProperties match { - case Left(true) => Some(BoolOrSchema.BooleanWrapper(true)) + case Left(true) => None case Left(false) => Some(BoolOrSchema.BooleanWrapper(false)) case Right(schema) => Some(BoolOrSchema.SchemaWrapper(schema.toSerializableSchema)) } diff --git a/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/OpenAPI.scala b/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/OpenAPI.scala index e3999a83d4..cde1ea52ac 100644 --- a/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/OpenAPI.scala +++ b/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/OpenAPI.scala @@ -18,6 +18,7 @@ package zio.http.endpoint.openapi import java.net.URI +import scala.collection.immutable.ListMap import scala.util.matching.Regex import zio.Chunk @@ -71,7 +72,7 @@ final case class OpenAPI( openapi: String, info: OpenAPI.Info, servers: List[OpenAPI.Server] = List.empty, - paths: Map[OpenAPI.Path, OpenAPI.PathItem] = Map.empty, + paths: ListMap[OpenAPI.Path, OpenAPI.PathItem] = ListMap.empty, components: Option[OpenAPI.Components], security: List[SecurityRequirement] = List.empty, tags: List[OpenAPI.Tag] = List.empty, @@ -88,28 +89,31 @@ final case class OpenAPI( externalDocs = externalDocs, ) - private def mergePaths(paths: Map[OpenAPI.Path, OpenAPI.PathItem]*): Map[OpenAPI.Path, OpenAPI.PathItem] = - paths - .foldRight[Seq[(OpenAPI.Path, OpenAPI.PathItem)]](Seq.empty)((z, p) => z.toSeq ++ p) - .groupBy(_._1) - .map { case (path, pathItems) => - val pathItem = pathItems.map(_._2).reduce { (i, j) => - i.copy( - get = i.get.orElse(j.get), - put = i.put.orElse(j.put), - post = i.post.orElse(j.post), - delete = i.delete.orElse(j.delete), - options = i.options.orElse(j.options), - head = i.head.orElse(j.head), - patch = i.patch.orElse(j.patch), - trace = i.trace.orElse(j.trace), - ) + private def mergePaths(paths: ListMap[OpenAPI.Path, OpenAPI.PathItem]*): ListMap[OpenAPI.Path, OpenAPI.PathItem] = + ListMap( + paths + .flatMap(_.toSeq) + .groupBy(_._1) + .map { case (path, pathItems) => + val pathItem = pathItems.map(_._2).reduce { (i, j) => + i.copy( + get = i.get.orElse(j.get), + put = i.put.orElse(j.put), + post = i.post.orElse(j.post), + delete = i.delete.orElse(j.delete), + options = i.options.orElse(j.options), + head = i.head.orElse(j.head), + patch = i.patch.orElse(j.patch), + trace = i.trace.orElse(j.trace), + ) + } + (path, pathItem) } - (path, pathItem) - } + .toSeq: _*, + ) def path(path: OpenAPI.Path, pathItem: OpenAPI.PathItem): OpenAPI = - copy(paths = mergePaths(Map(path -> pathItem), paths)) + copy(paths = mergePaths(ListMap(path -> pathItem), paths)) def toJson: String = JsonCodec @@ -149,7 +153,7 @@ object OpenAPI { version = "", ), servers = List.empty, - paths = Map.empty, + paths = ListMap.empty, components = None, security = List.empty, tags = List.empty, @@ -164,14 +168,14 @@ object OpenAPI { p => p.text, ) - implicit def pathMapSchema: Schema[Map[Path, PathItem]] = + implicit def pathMapSchema: Schema[ListMap[Path, PathItem]] = DeriveSchema .gen[Map[String, PathItem]] .transformOrFail( m => { - val it = m.iterator - var transformed = Map.empty[Path, PathItem] - var error: Left[String, Map[Path, PathItem]] = null + val it = m.iterator + var transformed = ListMap.empty[Path, PathItem] + var error: Left[String, ListMap[Path, PathItem]] = null while (it.hasNext && error == null) { val (k, v) = it.next() Path.fromString(k) match { @@ -182,30 +186,30 @@ object OpenAPI { if (error != null) error else Right(transformed) }, - (m: Map[Path, PathItem]) => Right(m.map { case (k, v) => k.name -> v }), + (m: Map[Path, PathItem]) => Right(ListMap(m.toSeq.sortBy(_._1.name).map { case (k, v) => k.name -> v }: _*)), ) implicit def keyMapSchema[T](implicit schema: Schema[T], - ): Schema[Map[Key, T]] = + ): Schema[ListMap[Key, T]] = Schema .map[String, T] .transformOrFail( m => { - val it = m.iterator - var transformed = Map.empty[Key, T] - var error: Left[String, Map[Key, T]] = null + val it = m.iterator + var transformed = Vector.empty[(Key, T)] + var error: Left[String, ListMap[Key, T]] = null while (it.hasNext && error == null) { val (k, v) = it.next() Key.fromString(k) match { - case Some(key) => transformed += key -> v + case Some(key) => transformed :+= key -> v case None => error = Left(s"Invalid key: $k") } } if (error != null) error - else Right(transformed) + else Right(ListMap(transformed.sortBy(_._1.name): _*)) }, - (m: Map[Key, T]) => Right(m.map { case (k, v) => k.name -> v }), + (m: Map[Key, T]) => Right(ListMap(m.toSeq.sortBy(_._1.name).map { case (k, v) => k.name -> v }: _*)), ) implicit def statusMapSchema[T](implicit @@ -378,15 +382,15 @@ object OpenAPI { * An object to hold reusable Callback Objects. */ final case class Components( - schemas: Map[Key, ReferenceOr[JsonSchema]] = Map.empty, - responses: Map[Key, ReferenceOr[Response]] = Map.empty, - parameters: Map[Key, ReferenceOr[Parameter]] = Map.empty, - examples: Map[Key, ReferenceOr[Example]] = Map.empty, - requestBodies: Map[Key, ReferenceOr[RequestBody]] = Map.empty, - headers: Map[Key, ReferenceOr[Header]] = Map.empty, - securitySchemes: Map[Key, ReferenceOr[SecurityScheme]] = Map.empty, - links: Map[Key, ReferenceOr[Link]] = Map.empty, - callbacks: Map[Key, ReferenceOr[Callback]] = Map.empty, + schemas: ListMap[Key, ReferenceOr[JsonSchema]] = ListMap.empty, + responses: ListMap[Key, ReferenceOr[Response]] = ListMap.empty, + parameters: ListMap[Key, ReferenceOr[Parameter]] = ListMap.empty, + examples: ListMap[Key, ReferenceOr[Example]] = ListMap.empty, + requestBodies: ListMap[Key, ReferenceOr[RequestBody]] = ListMap.empty, + headers: ListMap[Key, ReferenceOr[Header]] = ListMap.empty, + securitySchemes: ListMap[Key, ReferenceOr[SecurityScheme]] = ListMap.empty, + links: ListMap[Key, ReferenceOr[Link]] = ListMap.empty, + callbacks: ListMap[Key, ReferenceOr[Callback]] = ListMap.empty, ) { def ++(other: Components): Components = Components( schemas = schemas ++ other.schemas, @@ -424,13 +428,6 @@ object OpenAPI { } } - /** - * Holds the relative paths to the individual endpoints and their operations. - * The path is appended to the URL from the Server Object in order to - * construct the full URL. The Paths MAY be empty, due to ACL constraints. - */ - type Paths = Map[Path, PathItem] - /** * The path is appended (no relative URL resolution) to the expanded URL from * the Server Object's url field in order to construct the full URL. Path @@ -622,7 +619,7 @@ object OpenAPI { requestBody: Option[ReferenceOr[RequestBody]], responses: Map[StatusOrDefault, ReferenceOr[Response]] = Map.empty, callbacks: Map[String, ReferenceOr[Callback]] = Map.empty, - deprecated: Boolean = false, + deprecated: Option[Boolean] = None, security: List[SecurityRequirement] = List.empty, servers: List[Server] = List.empty, ) @@ -634,10 +631,10 @@ object OpenAPI { name: String, in: String, description: Option[Doc], - required: Boolean = false, - deprecated: Boolean = false, + required: Option[Boolean] = None, + deprecated: Option[Boolean] = None, schema: Option[ReferenceOr[JsonSchema]], - explode: Boolean = false, + explode: Option[Boolean] = None, examples: Map[String, ReferenceOr[Example]] = Map.empty, allowReserved: Option[Boolean], style: Option[String], @@ -701,10 +698,10 @@ object OpenAPI { name, "query", description, - required, - deprecated, + if (required) Some(required) else None, + if (deprecated) Some(deprecated) else None, schema, - explode, + if (explode) Some(explode) else None, examples, Some(allowReserved), style = Some(style match { @@ -742,10 +739,10 @@ object OpenAPI { name, "header", description, - required, - deprecated, + if (required) Some(required) else None, + if (deprecated) Some(deprecated) else None, definition, - explode, + if (explode) Some(explode) else None, examples, allowReserved = None, style = Some("simple"), @@ -778,10 +775,10 @@ object OpenAPI { name, "path", description, - required = true, - deprecated, + required = Some(true), + if (deprecated) Some(deprecated) else None, definition, - explode, + if (explode) Some(explode) else None, examples, allowReserved = None, style = Some(style match { @@ -817,10 +814,10 @@ object OpenAPI { name, "cookie", description, - required, - deprecated, + if (required) Some(required) else None, + if (deprecated) Some(deprecated) else None, definition, - explode, + if (explode) Some(explode) else None, examples, allowReserved = None, style = Some("form"), @@ -830,13 +827,26 @@ object OpenAPI { final case class Header( description: Option[Doc], - required: Boolean = false, - deprecated: Boolean = false, - allowEmptyValue: Boolean = false, + required: Option[Boolean] = None, + deprecated: Option[Boolean] = None, + allowEmptyValue: Option[Boolean] = None, schema: Option[JsonSchema], ) object Header { + def apply( + description: Option[Doc], + required: Boolean, + deprecated: Boolean, + allowEmptyValue: Boolean, + schema: Option[JsonSchema], + ): Header = Header( + description, + if (required) Some(required) else None, + if (deprecated) Some(deprecated) else None, + if (allowEmptyValue) Some(allowEmptyValue) else None, + schema, + ) implicit val schema: Schema[Header] = DeriveSchema.gen[Header] } diff --git a/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/OpenAPIGen.scala b/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/OpenAPIGen.scala index 66e586910c..ab353e0650 100644 --- a/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/OpenAPIGen.scala +++ b/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/OpenAPIGen.scala @@ -2,6 +2,7 @@ package zio.http.endpoint.openapi import java.util.UUID +import scala.collection.immutable.ListMap import scala.collection.{immutable, mutable} import zio.Chunk @@ -17,6 +18,7 @@ import zio.http.codec.HttpCodec.Metadata import zio.http.codec._ import zio.http.endpoint._ import zio.http.endpoint.openapi.JsonSchema.SchemaStyle +import zio.http.endpoint.openapi.OpenAPI.{Path, PathItem} object OpenAPIGen { private val PathWildcard = "pathWildcard" @@ -482,7 +484,7 @@ object OpenAPIGen { // there is no status for inputs. So we just take the first one (default) val ins = schemaByStatusAndMediaType(endpoint.input.alternatives.map(_._1), referenceType).values.headOption - def path: OpenAPI.Paths = { + def path: Map[Path, PathItem] = { val path = buildPath(endpoint.input) val method0 = method(inAtoms.method) // Endpoint has only one doc. But open api has a summery and a description @@ -558,23 +560,6 @@ object OpenAPIGen { .reduceOption(_ intersect _) .flatMap(_.reduceOption(_ + _)) .getOrElse(Doc.empty) -// { -// def loop(codec: PathCodec[_]): Doc = codec match { -// case PathCodec.Annotated(codec, annotations) => -// annotations -// .collect { case PathCodec.MetaData.Documented(doc) => doc } -// .reduceOption(_ + _) -// .getOrElse(Doc.empty) + loop(codec) -// case PathCodec.Segment(_) => -// Doc.empty -// case PathCodec.Concat(left, right, _) => -// loop(left) + loop(right) -// case PathCodec.TransformOrFail(api, _, _) => -// loop(api) -// } -// -// loop(endpoint.route.pathCodec) -// } def requestBody: Option[OpenAPI.ReferenceOr[OpenAPI.RequestBody]] = ins.map { mediaTypes => @@ -683,15 +668,15 @@ object OpenAPIGen { } def components = OpenAPI.Components( - schemas = componentSchemas, - responses = Map.empty, - parameters = Map.empty, - examples = Map.empty, - requestBodies = Map.empty, - headers = Map.empty, - securitySchemes = Map.empty, - links = Map.empty, - callbacks = Map.empty, + schemas = ListMap(componentSchemas.toSeq.sortBy(_._1.name): _*), + responses = ListMap.empty, + parameters = ListMap.empty, + examples = ListMap.empty, + requestBodies = ListMap.empty, + headers = ListMap.empty, + securitySchemes = ListMap.empty, + links = ListMap.empty, + callbacks = ListMap.empty, ) def segmentToJson(codec: SegmentCodec[_], value: Any): Json = { @@ -767,7 +752,7 @@ object OpenAPIGen { version = "", ), servers = Nil, - paths = path, + paths = ListMap(path.toSeq.sortBy(_._1.name): _*), components = Some(components), security = Nil, tags = Nil,