Skip to content

Commit

Permalink
Merge pull request #138 from Primetalk/fix/136
Browse files Browse the repository at this point in the history
Decode ListMap[String, MediaType|Encoding|String|Reference[A]]
  • Loading branch information
adamw authored Jan 25, 2024
2 parents 9510ae7 + 7e4e02d commit a69322d
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,29 @@ trait InternalSttpOpenAPICirceDecoders extends JsonSchemaCirceDecoders {
implicit val referenceDecoder: Decoder[Reference] = deriveDecoder[Reference]
implicit def decodeReferenceOr[A: Decoder]: Decoder[ReferenceOr[A]] = referenceDecoder.either(Decoder[A])

def listMapStringADecoder[A: Decoder]: Decoder[ListMap[String, A]] =
Decoder.decodeOption(Decoder.decodeMapLike[String, A, ListMap]).map(_.getOrElse(ListMap.empty))

def listADecoder[A: Decoder]: Decoder[List[A]] =
Decoder.decodeOption(Decoder.decodeList[A]).map(_.getOrElse(Nil))

implicit def listMapStringReferenceOrADecoder[A: Decoder]: Decoder[ListMap[String, ReferenceOr[A]]] =
listMapStringADecoder
implicit def listMapStringMediaTypeDecoder: Decoder[ListMap[String, MediaType]] =
listMapStringADecoder
implicit def listMapStringEncodingDecoder: Decoder[ListMap[String, Encoding]] =
listMapStringADecoder
implicit def listMapStringStringDecoder: Decoder[ListMap[String, String]] =
listMapStringADecoder

implicit val externalDocumentationDecoder: Decoder[ExternalDocumentation] = withExtensions(
deriveDecoder[ExternalDocumentation]
)
implicit val tagDecoder: Decoder[Tag] = withExtensions(deriveDecoder[Tag])

implicit val oauthFlowDecoder: Decoder[OAuthFlow] = {
// #79: all OAuth flow object MUST include a scopes field, but it MAY be empty.
implicit def listMapDecoder: Decoder[ListMap[String, String]] =
Decoder.decodeOption(Decoder.decodeMapLike[String, String, ListMap]).map(_.getOrElse(ListMap.empty))
// #79: all OAuth flow object MUST include a scopes field, but it MAY be empty.
implicit val oauthFlowDecoder: Decoder[OAuthFlow] = withExtensions(deriveDecoder[OAuthFlow])

withExtensions(deriveDecoder[OAuthFlow])
}
implicit val oauthFlowsDecoder: Decoder[OAuthFlows] = withExtensions(deriveDecoder[OAuthFlows])
implicit val securitySchemeDecoder: Decoder[SecurityScheme] = withExtensions(deriveDecoder[SecurityScheme])

Expand All @@ -34,7 +45,7 @@ trait InternalSttpOpenAPICirceDecoders extends JsonSchemaCirceDecoders {
implicit val infoDecoder: Decoder[Info] = withExtensions(deriveDecoder[Info])

implicit val serverVariableDecoder: Decoder[ServerVariable] = deriveDecoder[ServerVariable]
implicit val serverDecoder: Decoder[Server] = deriveDecoder[Server]
implicit val serverDecoder: Decoder[Server] = withExtensions(deriveDecoder[Server])
implicit val linkDecoder: Decoder[Link] = deriveDecoder[Link]

implicit val parameterInDecoder: Decoder[ParameterIn] = Decoder.decodeString.emap {
Expand All @@ -61,15 +72,8 @@ trait InternalSttpOpenAPICirceDecoders extends JsonSchemaCirceDecoders {
implicit val mediaTypeDecoder: Decoder[MediaType] = withExtensions(deriveDecoder[MediaType])
implicit val requestBodyDecoder: Decoder[RequestBody] = withExtensions(deriveDecoder[RequestBody])

implicit val responseDecoder: Decoder[Response] = {
implicit def listMapDecoder[A: Decoder]: Decoder[ListMap[String, ReferenceOr[A]]] =
Decoder.decodeOption(Decoder.decodeMapLike[String, ReferenceOr[A], ListMap]).map(_.getOrElse(ListMap.empty))

implicit def listMapMediaTypeDecoder: Decoder[ListMap[String, MediaType]] =
Decoder.decodeOption(Decoder.decodeMapLike[String, MediaType, ListMap]).map(_.getOrElse(ListMap.empty))
implicit val responseDecoder: Decoder[Response] = withExtensions(deriveDecoder[Response])

withExtensions(deriveDecoder[Response])
}
implicit val responsesKeyDecoder: KeyDecoder[ResponsesKey] = {
val ResponseRange = "(1|2|3|4|5)XX".r
val ResponseCode = "([1|2|3|4|5]\\d\\d)".r
Expand All @@ -92,17 +96,15 @@ trait InternalSttpOpenAPICirceDecoders extends JsonSchemaCirceDecoders {
implicit val parameterDecoder: Decoder[Parameter] = withExtensions(deriveDecoder[Parameter])
implicit val callbackDecoder: Decoder[Callback] = deriveDecoder[Callback]
implicit val operationDecoder: Decoder[Operation] = {
implicit def listMapDecoder[A: Decoder]: Decoder[ListMap[String, ReferenceOr[A]]] =
Decoder.decodeOption(Decoder.decodeMapLike[String, ReferenceOr[A], ListMap]).map(_.getOrElse(ListMap.empty))

implicit def listReference[A: Decoder]: Decoder[List[A]] =
Decoder.decodeOption(Decoder.decodeList[A]).map(_.getOrElse(Nil))
listADecoder

withExtensions(deriveDecoder[Operation])
}
implicit val pathItemDecoder: Decoder[PathItem] = {
implicit def listReference[A: Decoder]: Decoder[List[A]] =
Decoder.decodeOption(Decoder.decodeList[A]).map(_.getOrElse(Nil))
listADecoder

withExtensions(deriveDecoder[PathItem])
}
Expand Down
33 changes: 32 additions & 1 deletion openapi-circe/src/test/resources/petstore/basic-petstore.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,45 @@
},
"version": "1.0.1"
},
"servers": [
{
"url": "http://petstore.swagger.io/v1"
}
],
"paths": {
"/pets": {
"get": {
"operationId": "getPets",
"description": "Gets all pets",
"responses": {
"200": {
"description": "Success"
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Pet"
}
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Pet": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int32"
},
"name": {
"type": "string"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ class EncoderTest extends AnyFunSuite with ResourcePlatform with SttpOpenAPICirc

def refOr[A](a: A): ReferenceOr[A] = Right(a)

def arrayOf(s: SchemaLike) = Schema(`type` = Some(SchemaType.Array), items = Some(s))

def ref(s: String): SchemaLike = Schema($ref = Some(s))

test("petstore serialize") {
val withPathItem = petstore.addPathItem(
"/pets",
Expand All @@ -45,12 +49,28 @@ class EncoderTest extends AnyFunSuite with ResourcePlatform with SttpOpenAPICirc
Operation(
operationId = Some("getPets"),
description = Some("Gets all pets")
).addResponse(200, Response(description = "Success"))
).addResponse(200, Response(
description = "Success",
content = ListMap("application/json" ->
MediaType(schema = Some(arrayOf(ref("#/components/schemas/Pet"))))
)
))
)
)
)

val serialized = withPathItem.asJson
val petSchema = Schema(
`type` = Some(SchemaType.Object),
properties =
ListMap("id" -> Schema(`type` = Some(SchemaType.Integer), format = Some("int32")),
"name" -> Schema(`type` = Some(SchemaType.String))
)
)
val withComponents = withPathItem.components(Components(schemas = ListMap(
"Pet" -> petSchema
)))
val server = Server(url = "http://petstore.swagger.io/v1")
val withServer = withComponents.servers(List(server))
val serialized = withServer.asJson
val Right(json) = readJson("/petstore/basic-petstore.json"): @unchecked

assert(serialized === json)
Expand Down

0 comments on commit a69322d

Please sign in to comment.