Skip to content

Commit

Permalink
AnySchema.Encoding is a json encoding issue
Browse files Browse the repository at this point in the history
  • Loading branch information
hamnis committed Sep 30, 2022
1 parent 924c2f3 commit 967aac3
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 49 deletions.
6 changes: 3 additions & 3 deletions apispec-model/src/main/scala/sttp/apispec/Schema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ sealed trait SchemaLike
sealed trait AnySchema extends SchemaLike

object AnySchema {
sealed trait Encoding
sealed trait Encoding extends Product with Serializable
object Encoding {
case object Object extends Encoding
case object Boolean extends Encoding
}

case class Anything(encoding: Encoding = Encoding.Boolean) extends AnySchema
case class Nothing(encoding: Encoding = Encoding.Boolean) extends AnySchema
case object Anything extends AnySchema
case object Nothing extends AnySchema
}

// todo: xml
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ import io.circe.{Encoder, KeyEncoder, Json, JsonObject}

import scala.collection.immutable.ListMap

package object circe extends SttpAsyncAPICirceEncoders
package object circe extends SttpAsyncAPICirceEncoders {
val anyObjectEncoding: AnySchema.Encoding = AnySchema.Encoding.Boolean
}

package circe {
trait SttpAsyncAPICirceEncoders {
// note: these are strict val-s, order matters!

def anyObjectEncoding: AnySchema.Encoding

implicit def encoderReferenceOr[T: Encoder]: Encoder[ReferenceOr[T]] = {
case Left(Reference(ref, summary, description)) =>
Json
Expand Down Expand Up @@ -59,12 +63,19 @@ package circe {
Encoder.encodeString.contramap(_.value)

implicit val encoderAnySchema: Encoder[AnySchema] = Encoder.instance {
case AnySchema.Anything(AnySchema.Encoding.Object) => Json.obj()
case AnySchema.Anything(AnySchema.Encoding.Boolean) => Json.True
case AnySchema.Nothing(AnySchema.Encoding.Object) => Json.obj(
"not" := Json.obj()
)
case AnySchema.Nothing(AnySchema.Encoding.Boolean) => Json.False
case AnySchema.Anything =>
anyObjectEncoding match {
case AnySchema.Encoding.Object => Json.obj()
case AnySchema.Encoding.Boolean => Json.True
}
case AnySchema.Nothing =>
anyObjectEncoding match {
case AnySchema.Encoding.Object =>
Json.obj(
"not" := Json.obj()
)
case AnySchema.Encoding.Boolean => Json.False
}
}
implicit val encoderSchema: Encoder[Schema] =
deriveEncoder[Schema].mapJsonObject(obj => expandExtensions(obj).remove("$schema"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package sttp.apispec.openapi

package object circe extends SttpOpenAPICirceEncoders with SttpOpenAPICirceDecoders
import sttp.apispec.AnySchema

package object circe extends SttpOpenAPICirceEncoders with SttpOpenAPICirceDecoders {
val anyObjectEncoding: AnySchema.Encoding = AnySchema.Encoding.Boolean
}

package circe {
trait SttpOpenAPICirceEncoders extends internal.InternalSttpOpenAPICirceEncoders
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ trait InternalSttpOpenAPICirceDecoders {
}
implicit val anySchemaDecoder: Decoder[AnySchema] = Decoder.instance { c =>
def fromBool(b: Boolean) =
if (b) AnySchema.Anything(AnySchema.Encoding.Boolean) else AnySchema.Nothing(AnySchema.Encoding.Boolean)
if (b) AnySchema.Anything else AnySchema.Nothing

def fromObject(obj: JsonObject) = {
val target = JsonObject("not" := Json.obj())

if (obj.isEmpty) {
AnySchema.Anything(AnySchema.Encoding.Object).some
AnySchema.Anything.some
} else if (obj == target) {
AnySchema.Nothing(AnySchema.Encoding.Object).some
AnySchema.Nothing.some
} else
none[AnySchema]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import io.circe.{Encoder, KeyEncoder, Json, JsonObject}
import scala.collection.immutable.ListMap

trait InternalSttpOpenAPICirceEncoders {
def anyObjectEncoding: AnySchema.Encoding
// note: these are strict val-s, order matters!
implicit def encoderReferenceOr[T: Encoder]: Encoder[ReferenceOr[T]] = {
case Left(Reference(ref, summary, description)) =>
Expand Down Expand Up @@ -104,12 +105,19 @@ trait InternalSttpOpenAPICirceEncoders {
.mapJsonObject(expandExtensions)

implicit val encoderAnySchema: Encoder[AnySchema] = Encoder.instance {
case AnySchema.Anything(AnySchema.Encoding.Object) => Json.obj()
case AnySchema.Anything(AnySchema.Encoding.Boolean) => Json.True
case AnySchema.Nothing(AnySchema.Encoding.Object) => Json.obj(
"not" := Json.obj()
)
case AnySchema.Nothing(AnySchema.Encoding.Boolean) => Json.False
case AnySchema.Anything =>
anyObjectEncoding match {
case AnySchema.Encoding.Object => Json.obj()
case AnySchema.Encoding.Boolean => Json.True
}
case AnySchema.Nothing =>
anyObjectEncoding match {
case AnySchema.Encoding.Object =>
Json.obj(
"not" := Json.obj()
)
case AnySchema.Encoding.Boolean => Json.False
}
}

implicit val encoderSchemaLike: Encoder[SchemaLike] = Encoder.instance {
Expand Down
13 changes: 13 additions & 0 deletions openapi-circe/src/test/resources/spec/3.1/any_and_nothing1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"openapi": "3.1.0",
"info": {
"title": "API",
"version": "1.0.0"
},
"components": {
"schemas": {
"anything_boolean": true,
"nothing_boolean": false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
},
"components": {
"schemas": {
"anything_boolean": true,
"nothing_boolean": false,
"anything_object": {},
"nothing_object": {
"not": {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,24 @@ class DecoderTest extends AnyFunSuite with ResourcePlatform {
assert(openapi.info.description === Some("This is a sample server for a pet store."))
}

test("spec any nothing schema") {
val Right(openapi) = readJson("/spec/3.1/any_and_nothing.json").flatMap(_.as[OpenAPI])
test("spec any nothing schema boolean") {
val Right(openapi) = readJson("/spec/3.1/any_and_nothing1.json").flatMap(_.as[OpenAPI])

assert(openapi.info.title === "API")
val schemas = openapi.components.getOrElse(Components.Empty).schemas
assert(schemas.nonEmpty)
assert(schemas("anything_boolean") === Right(AnySchema.Anything(AnySchema.Encoding.Boolean)))
assert(schemas("nothing_boolean") === Right(AnySchema.Nothing(AnySchema.Encoding.Boolean)))
assert(schemas("anything_object") === Right(AnySchema.Anything(AnySchema.Encoding.Object)))
assert(schemas("nothing_object") === Right(AnySchema.Nothing(AnySchema.Encoding.Object)))
assert(schemas("anything_boolean") === Right(AnySchema.Anything))
assert(schemas("nothing_boolean") === Right(AnySchema.Nothing))
}

test("spec any nothing schema object") {
val Right(openapi) = readJson("/spec/3.1/any_and_nothing2.json").flatMap(_.as[OpenAPI])

assert(openapi.info.title === "API")
val schemas = openapi.components.getOrElse(Components.Empty).schemas
assert(schemas.nonEmpty)
assert(schemas("anything_object") === Right(AnySchema.Anything))
assert(schemas("nothing_object") === Right(AnySchema.Nothing))
}

test("all schemas types") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,25 +116,4 @@ class EncoderTest extends AnyFunSuite with ResourcePlatform {

assert(openApiJson.spaces2SortKeys == json.spaces2SortKeys)
}

test("any and nothing") {
val components = Components(
schemas = ListMap(
"anything_boolean" -> refOr(AnySchema.Anything(AnySchema.Encoding.Boolean)),
"nothing_boolean" -> refOr(AnySchema.Nothing(AnySchema.Encoding.Boolean)),
"anything_object" -> refOr(AnySchema.Anything(AnySchema.Encoding.Object)),
"nothing_object" -> refOr(AnySchema.Nothing(AnySchema.Encoding.Object)),
)
)

val openapi = OpenAPI(
info = Info(title = "API", version = "1.0.0"),
components = Some(components)
)

val openApiJson = openapi.asJson
val Right(json) = readJson("/spec/3.1/any_and_nothing.json")

assert(openApiJson.spaces2SortKeys == json.spaces2SortKeys)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package sttp.apispec.openapi.circe.overridden

import io.circe.syntax._
import sttp.apispec._
import sttp.apispec.openapi._
import org.scalatest.funsuite.AnyFunSuite
import scala.collection.immutable.ListMap
import sttp.apispec.openapi.circe.ResourcePlatform

class EncoderTest extends AnyFunSuite with ResourcePlatform {
def refOr[A](a: A): ReferenceOr[A] = Right(a)

test("any boolean") {
import sttp.apispec.openapi.circe._

val components = Components(
schemas = ListMap(
"anything_boolean" -> refOr(AnySchema.Anything),
"nothing_boolean" -> refOr(AnySchema.Nothing),
)
)

val openapi = OpenAPI(
info = Info(title = "API", version = "1.0.0"),
components = Some(components)
)

val openApiJson = openapi.asJson
val Right(json) = readJson("/spec/3.1/any_and_nothing1.json")

assert(openApiJson.spaces2SortKeys == json.spaces2SortKeys)
}

test("any object") {
object obj extends sttp.apispec.openapi.circe.SttpOpenAPICirceEncoders {
val anyObjectEncoding: AnySchema.Encoding = AnySchema.Encoding.Object
}

import obj._

val components = Components(
schemas = ListMap(
"anything_object" -> refOr(AnySchema.Anything),
"nothing_object" -> refOr(AnySchema.Nothing),
)
)

val openapi = OpenAPI(
info = Info(title = "API", version = "1.0.0"),
components = Some(components)
)

val openApiJson = openapi.asJson
val Right(json) = readJson("/spec/3.1/any_and_nothing2.json")

assert(openApiJson.spaces2SortKeys == json.spaces2SortKeys)
}

}

0 comments on commit 967aac3

Please sign in to comment.