Skip to content

Commit ad6dac9

Browse files
authored
Encode opt query params correctly in open api (#3127) (#3152)
Add tests for optional headers and payload as well
1 parent 40c4b6f commit ad6dac9

File tree

2 files changed

+367
-6
lines changed

2 files changed

+367
-6
lines changed

zio-http/jvm/src/test/scala/zio/http/endpoint/openapi/OpenAPIGenSpec.scala

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,13 @@ object OpenAPIGenSpec extends ZIOSpecDefault {
180180
.out[SimpleOutputBody]
181181
.outError[NotFoundError](Status.NotFound)
182182

183+
private val queryParamOptEndpoint =
184+
Endpoint(GET / "withQuery")
185+
.in[SimpleInputBody]
186+
.query(HttpCodec.query[String]("query").optional)
187+
.out[SimpleOutputBody]
188+
.outError[NotFoundError](Status.NotFound)
189+
183190
private val queryParamCollectionEndpoint =
184191
Endpoint(GET / "withQuery")
185192
.in[SimpleInputBody]
@@ -194,6 +201,19 @@ object OpenAPIGenSpec extends ZIOSpecDefault {
194201
.out[SimpleOutputBody]
195202
.outError[NotFoundError](Status.NotFound)
196203

204+
private val optionalHeaderEndpoint =
205+
Endpoint(GET / "withHeader")
206+
.in[SimpleInputBody]
207+
.header(HttpCodec.contentType.optional)
208+
.out[SimpleOutputBody]
209+
.outError[NotFoundError](Status.NotFound)
210+
211+
private val optionalPayloadEndpoint =
212+
Endpoint(GET / "withPayload")
213+
.inCodec(HttpCodec.content[Payload].optional)
214+
.out[SimpleOutputBody]
215+
.outError[NotFoundError](Status.NotFound)
216+
197217
private val alternativeInputEndpoint =
198218
Endpoint(GET / "inputAlternative")
199219
.inCodec(
@@ -493,6 +513,134 @@ object OpenAPIGenSpec extends ZIOSpecDefault {
493513
|}""".stripMargin
494514
assertTrue(json == toJsonAst(expectedJson))
495515
},
516+
test("with optional query parameter") {
517+
val generated = OpenAPIGen.fromEndpoints("Simple Endpoint", "1.0", queryParamOptEndpoint)
518+
val json = toJsonAst(generated)
519+
val expectedJson = """{
520+
| "openapi" : "3.1.0",
521+
| "info" : {
522+
| "title" : "Simple Endpoint",
523+
| "version" : "1.0"
524+
| },
525+
| "paths" : {
526+
| "/withQuery" : {
527+
| "get" : {
528+
| "parameters" : [
529+
|
530+
| {
531+
| "name" : "query",
532+
| "in" : "query",
533+
| "schema" :
534+
| {
535+
| "type" :[
536+
| "string",
537+
| "null"
538+
| ]
539+
| },
540+
| "allowReserved" : false,
541+
| "style" : "form"
542+
| }
543+
| ],
544+
| "requestBody" :
545+
| {
546+
| "content" : {
547+
| "application/json" : {
548+
| "schema" :
549+
| {
550+
| "$ref" : "#/components/schemas/SimpleInputBody"
551+
| }
552+
| }
553+
| },
554+
| "required" : true
555+
| },
556+
| "responses" : {
557+
| "200" :
558+
| {
559+
| "content" : {
560+
| "application/json" : {
561+
| "schema" :
562+
| {
563+
| "$ref" : "#/components/schemas/SimpleOutputBody"
564+
| }
565+
| }
566+
| }
567+
| },
568+
| "404" :
569+
| {
570+
| "content" : {
571+
| "application/json" : {
572+
| "schema" :
573+
| {
574+
| "$ref" : "#/components/schemas/NotFoundError"
575+
| }
576+
| }
577+
| }
578+
| }
579+
| }
580+
| }
581+
| }
582+
| },
583+
| "components" : {
584+
| "schemas" : {
585+
| "NotFoundError" :
586+
| {
587+
| "type" :
588+
| "object",
589+
| "properties" : {
590+
| "message" : {
591+
| "type" :
592+
| "string"
593+
| }
594+
| },
595+
| "required" : [
596+
| "message"
597+
| ]
598+
| },
599+
| "SimpleInputBody" :
600+
| {
601+
| "type" :
602+
| "object",
603+
| "properties" : {
604+
| "name" : {
605+
| "type" :
606+
| "string"
607+
| },
608+
| "age" : {
609+
| "type" :
610+
| "integer",
611+
| "format" : "int32"
612+
| }
613+
| },
614+
| "required" : [
615+
| "name",
616+
| "age"
617+
| ]
618+
| },
619+
| "SimpleOutputBody" :
620+
| {
621+
| "type" :
622+
| "object",
623+
| "properties" : {
624+
| "userName" : {
625+
| "type" :
626+
| "string"
627+
| },
628+
| "score" : {
629+
| "type" :
630+
| "integer",
631+
| "format" : "int32"
632+
| }
633+
| },
634+
| "required" : [
635+
| "userName",
636+
| "score"
637+
| ]
638+
| }
639+
| }
640+
| }
641+
|}""".stripMargin
642+
assertTrue(json == toJsonAst(expectedJson))
643+
},
496644
test("with query parameter with multiple values") {
497645
val generated = OpenAPIGen.fromEndpoints("Simple Endpoint", "1.0", queryParamCollectionEndpoint)
498646
val json = toJsonAst(generated)
@@ -749,6 +897,214 @@ object OpenAPIGenSpec extends ZIOSpecDefault {
749897
|}""".stripMargin
750898
assertTrue(json == toJsonAst(expectedJson))
751899
},
900+
test("optional header") {
901+
val generated = OpenAPIGen.fromEndpoints("Simple Endpoint", "1.0", optionalHeaderEndpoint)
902+
val json = toJsonAst(generated)
903+
val expectedJson = """{
904+
| "openapi" : "3.1.0",
905+
| "info" : {
906+
| "title" : "Simple Endpoint",
907+
| "version" : "1.0"
908+
| },
909+
| "paths" : {
910+
| "/withHeader" : {
911+
| "get" : {
912+
| "parameters" : [
913+
| {
914+
| "name" : "content-type",
915+
| "in" : "header",
916+
| "schema" : {
917+
| "type" : [
918+
| "string",
919+
| "null"
920+
| ]
921+
| },
922+
| "style" : "simple"
923+
| }
924+
| ],
925+
| "requestBody" : {
926+
| "content" : {
927+
| "application/json" : {
928+
| "schema" : {
929+
| "$ref" : "#/components/schemas/SimpleInputBody",
930+
| "description" : ""
931+
| }
932+
| }
933+
| },
934+
| "required" : true
935+
| },
936+
| "responses" : {
937+
| "200" : {
938+
| "content" : {
939+
| "application/json" : {
940+
| "schema" : {
941+
| "$ref" : "#/components/schemas/SimpleOutputBody"
942+
| }
943+
| }
944+
| }
945+
| },
946+
| "404" : {
947+
| "content" : {
948+
| "application/json" : {
949+
| "schema" : {
950+
| "$ref" : "#/components/schemas/NotFoundError"
951+
| }
952+
| }
953+
| }
954+
| }
955+
| }
956+
| }
957+
| }
958+
| },
959+
| "components" : {
960+
| "schemas" : {
961+
| "NotFoundError" : {
962+
| "type" : "object",
963+
| "properties" : {
964+
| "message" : {
965+
| "type" : "string"
966+
| }
967+
| },
968+
| "required" : [
969+
| "message"
970+
| ]
971+
| },
972+
| "SimpleInputBody" : {
973+
| "type" : "object",
974+
| "properties" : {
975+
| "name" : {
976+
| "type" : "string"
977+
| },
978+
| "age" : {
979+
| "type" : "integer",
980+
| "format" : "int32"
981+
| }
982+
| },
983+
| "required" : [
984+
| "name",
985+
| "age"
986+
| ]
987+
| },
988+
| "SimpleOutputBody" : {
989+
| "type" : "object",
990+
| "properties" : {
991+
| "userName" : {
992+
| "type" : "string"
993+
| },
994+
| "score" : {
995+
| "type" : "integer",
996+
| "format" : "int32"
997+
| }
998+
| },
999+
| "required" : [
1000+
| "userName",
1001+
| "score"
1002+
| ]
1003+
| }
1004+
| }
1005+
| }
1006+
|}""".stripMargin
1007+
assertTrue(json == toJsonAst(expectedJson))
1008+
},
1009+
test("optional payload") {
1010+
val generated = OpenAPIGen.fromEndpoints("Simple Endpoint", "1.0", optionalPayloadEndpoint)
1011+
val json = toJsonAst(generated)
1012+
val expected = """{
1013+
| "openapi" : "3.1.0",
1014+
| "info" : {
1015+
| "title" : "Simple Endpoint",
1016+
| "version" : "1.0"
1017+
| },
1018+
| "paths" : {
1019+
| "/withPayload" : {
1020+
| "get" : {
1021+
| "requestBody" : {
1022+
| "content" : {
1023+
| "application/json" : {
1024+
| "schema" : {
1025+
| "anyOf" : [
1026+
| {
1027+
| "type" : "null"
1028+
| },
1029+
| {
1030+
| "$ref" : "#/components/schemas/Payload"
1031+
| }
1032+
| ],
1033+
| "description" : ""
1034+
| }
1035+
| }
1036+
| },
1037+
| "required" : false
1038+
| },
1039+
| "responses" : {
1040+
| "200" : {
1041+
| "content" : {
1042+
| "application/json" : {
1043+
| "schema" : {
1044+
| "$ref" : "#/components/schemas/SimpleOutputBody"
1045+
| }
1046+
| }
1047+
| }
1048+
| },
1049+
| "404" : {
1050+
| "content" : {
1051+
| "application/json" : {
1052+
| "schema" : {
1053+
| "$ref" : "#/components/schemas/NotFoundError"
1054+
| }
1055+
| }
1056+
| }
1057+
| }
1058+
| }
1059+
| }
1060+
| }
1061+
| },
1062+
| "components" : {
1063+
| "schemas" : {
1064+
| "NotFoundError" : {
1065+
| "type" : "object",
1066+
| "properties" : {
1067+
| "message" : {
1068+
| "type" : "string"
1069+
| }
1070+
| },
1071+
| "required" : [
1072+
| "message"
1073+
| ]
1074+
| },
1075+
| "Payload" : {
1076+
| "type" : "object",
1077+
| "properties" : {
1078+
| "content" : {
1079+
| "type" : "string"
1080+
| }
1081+
| },
1082+
| "required" : [
1083+
| "content"
1084+
| ],
1085+
| "description" : "A simple payload"
1086+
| },
1087+
| "SimpleOutputBody" : {
1088+
| "type" : "object",
1089+
| "properties" : {
1090+
| "userName" : {
1091+
| "type" : "string"
1092+
| },
1093+
| "score" : {
1094+
| "type" : "integer",
1095+
| "format" : "int32"
1096+
| }
1097+
| },
1098+
| "required" : [
1099+
| "userName",
1100+
| "score"
1101+
| ]
1102+
| }
1103+
| }
1104+
| }
1105+
|}""".stripMargin
1106+
assertTrue(json == toJsonAst(expected))
1107+
},
7521108
test("alternative input") {
7531109
val generated = OpenAPIGen.fromEndpoints("Simple Endpoint", "1.0", alternativeInputEndpoint)
7541110
val json = toJsonAst(generated)

0 commit comments

Comments
 (0)