diff --git a/apispec-model/src/main/scala/sttp/apispec/Schema.scala b/apispec-model/src/main/scala/sttp/apispec/Schema.scala index 3bb8c4a..e1dc52f 100644 --- a/apispec-model/src/main/scala/sttp/apispec/Schema.scala +++ b/apispec-model/src/main/scala/sttp/apispec/Schema.scala @@ -132,8 +132,13 @@ case class Schema( */ def nullable: Schema = `type` match { case Some(types) => + val NullExample = ExampleSingleValue("null") if (types.contains(SchemaType.Null)) this // ensure idempotency - else copy(`type` = Some(types :+ SchemaType.Null)) + else copy( + `type` = Some(types :+ SchemaType.Null), + `enum` = `enum`.orElse(`const`.map(List(_))).map(vs => (vs :+ NullExample).distinct), + `const` = None + ) case None => // Representing nullable schemas (without explicit `type`) using `anyOf` is safer than `oneOf`. diff --git a/apispec-model/src/test/scala/sttp/apispec/SchemaTest.scala b/apispec-model/src/test/scala/sttp/apispec/SchemaTest.scala index e1c58c8..6ac0205 100644 --- a/apispec-model/src/test/scala/sttp/apispec/SchemaTest.scala +++ b/apispec-model/src/test/scala/sttp/apispec/SchemaTest.scala @@ -20,4 +20,22 @@ class SchemaTest extends AnyFunSuite { assert(schema.nullable == Schema(anyOf = List(Schema(SchemaType.String), Schema(SchemaType.Number), Schema.Null))) assert(schema.nullable.nullable == schema.nullable) // idempotency } + + test("nullable enum") { + val schema = Schema(`type` = Some(List(SchemaType.String)), `enum` = Some(List("a", "b").map(ExampleSingleValue(_)))) + assert(schema.nullable == Schema( + `type` = Some(List(SchemaType.String, SchemaType.Null)), + `enum` = Some(List("a", "b", "null").map(ExampleSingleValue(_))) + )) + assert(schema.nullable.nullable == schema.nullable) // idempotency + } + + test("nullable const") { + val schema = Schema(`type` = Some(List(SchemaType.String)), `const` = Some(ExampleSingleValue("a"))) + assert(schema.nullable == Schema( + `type` = Some(List(SchemaType.String, SchemaType.Null)), + `enum` = Some(List("a", "null").map(ExampleSingleValue(_))) + )) + assert(schema.nullable.nullable == schema.nullable) + } } diff --git a/apispec-model/src/test/scala/sttp/apispec/validation/SchemaComparatorTest.scala b/apispec-model/src/test/scala/sttp/apispec/validation/SchemaComparatorTest.scala index 8c0394d..932d2fc 100644 --- a/apispec-model/src/test/scala/sttp/apispec/validation/SchemaComparatorTest.scala +++ b/apispec-model/src/test/scala/sttp/apispec/validation/SchemaComparatorTest.scala @@ -217,11 +217,11 @@ abstract class SchemaComparatorTest(referencePrefix: String) extends AnyFunSuite } private def enums(values: Any*): List[ExampleSingleValue] = - values.toList.map(ExampleSingleValue) + values.toList.map(ExampleSingleValue(_)) private def enumSchema(values: String*): Schema = values.toList match { - case single :: Nil => stringSchema.copy(`enum` = Some(List(single).map(ExampleSingleValue))) - case multiple => stringSchema.copy(`enum` = Some(multiple.map(ExampleSingleValue))) + case single :: Nil => stringSchema.copy(`enum` = Some(List(single).map(ExampleSingleValue(_)))) + case multiple => stringSchema.copy(`enum` = Some(multiple.map(ExampleSingleValue(_)))) } test("compatible enum & const") { diff --git a/jsonschema-circe/src/main/scala/sttp/apispec/internal/JsonSchemaCirceEncoders.scala b/jsonschema-circe/src/main/scala/sttp/apispec/internal/JsonSchemaCirceEncoders.scala index c8869ff..558a7c7 100644 --- a/jsonschema-circe/src/main/scala/sttp/apispec/internal/JsonSchemaCirceEncoders.scala +++ b/jsonschema-circe/src/main/scala/sttp/apispec/internal/JsonSchemaCirceEncoders.scala @@ -5,14 +5,19 @@ import io.circe._ import io.circe.generic.semiauto.deriveEncoder import io.circe.parser.parse import io.circe.syntax._ +import sttp.apispec.internal.JsonSchemaCirceEncoders.ObjectEncoderOps import scala.collection.immutable.ListMap +import scala.language.implicitConversions trait JsonSchemaCirceEncoders { def anyObjectEncoding: AnySchema.Encoding def openApi30: Boolean = false + protected final implicit def objectEncoderOps[T](encoder: Encoder.AsObject[T]): ObjectEncoderOps[T] = + new ObjectEncoderOps(encoder) + implicit lazy val encoderSchema: Encoder[Schema] = Encoder.AsObject .instance { (s: Schema) => val nullSchema = Schema(`type` = Some(List(SchemaType.Null))) @@ -115,7 +120,7 @@ trait JsonSchemaCirceEncoders { ) ) } - .mapJsonObject(expandExtensions) + .dropNullsExpandExtensions // note: these are strict val-s, order matters! implicit val extensionValue: Encoder[ExtensionValue] = @@ -153,10 +158,10 @@ trait JsonSchemaCirceEncoders { Encoder.encodeString.contramap(_.value) implicit val encoderDiscriminator: Encoder[Discriminator] = - deriveEncoder[Discriminator] + deriveEncoder[Discriminator].dropNulls implicit val encoderExternalDocumentation: Encoder[ExternalDocumentation] = - deriveEncoder[ExternalDocumentation].mapJsonObject(expandExtensions) + deriveEncoder[ExternalDocumentation].dropNullsExpandExtensions implicit val encoderAnySchema: Encoder[AnySchema] = Encoder.instance { case AnySchema.Anything => @@ -191,6 +196,20 @@ trait JsonSchemaCirceEncoders { Json.obj(properties: _*) } + // just for backward compatibility + private[apispec] def expandExtensions(jsonObject: JsonObject): JsonObject = + JsonSchemaCirceEncoders.expandExtensions(jsonObject) + +} +object JsonSchemaCirceEncoders { + class ObjectEncoderOps[T](private val encoder: Encoder.AsObject[T]) extends AnyVal { + def dropNulls: Encoder.AsObject[T] = + encoder.mapJsonObject(_.filter { case (_, v) => !v.isNull }) + + def dropNullsExpandExtensions: Encoder.AsObject[T] = + dropNulls.mapJsonObject(expandExtensions) + } + /* Openapi extensions are arbitrary key-value data that could be added to some of models in specifications, such as `OpenAPI` itself, `License`, `Parameter`, etc. @@ -221,7 +240,7 @@ trait JsonSchemaCirceEncoders { x-foo: 42 ``` */ - private[apispec] def expandExtensions(jsonObject: JsonObject): JsonObject = { + private def expandExtensions(jsonObject: JsonObject): JsonObject = { val jsonWithoutExt = jsonObject.filterKeys(_ != "extensions") jsonObject("extensions") .flatMap(_.asObject) @@ -230,11 +249,10 @@ trait JsonSchemaCirceEncoders { allKeys.foldLeft(JsonObject.empty) { case (acc, key) => extObject(key).orElse(jsonWithoutExt(key)) match { case Some(value) => acc.add(key, value) - case None => acc + case None => acc } } } .getOrElse(jsonWithoutExt) } - } diff --git a/openapi-circe/src/main/scala/sttp/apispec/openapi/internal/InternalSttpOpenAPICirceEncoders.scala b/openapi-circe/src/main/scala/sttp/apispec/openapi/internal/InternalSttpOpenAPICirceEncoders.scala index 7ae3fb6..3e3484a 100644 --- a/openapi-circe/src/main/scala/sttp/apispec/openapi/internal/InternalSttpOpenAPICirceEncoders.scala +++ b/openapi-circe/src/main/scala/sttp/apispec/openapi/internal/InternalSttpOpenAPICirceEncoders.scala @@ -10,7 +10,7 @@ import sttp.apispec.internal.JsonSchemaCirceEncoders import scala.collection.immutable.ListMap trait InternalSttpOpenAPICirceEncoders extends JsonSchemaCirceEncoders { - implicit val encoderReference: Encoder[Reference] = deriveEncoder[Reference] + implicit val encoderReference: Encoder[Reference] = deriveEncoder[Reference].dropNulls implicit def encoderReferenceOr[T: Encoder]: Encoder[ReferenceOr[T]] = { case Left(Reference(ref, summary, description)) => Json @@ -27,25 +27,25 @@ trait InternalSttpOpenAPICirceEncoders extends JsonSchemaCirceEncoders { // #79: all OAuth flow object MUST include a scopes field, but it MAY be empty. implicit def encodeListMap: Encoder[ListMap[String, String]] = doEncodeListMap(nullWhenEmpty = false) - deriveEncoder[OAuthFlow].mapJsonObject(expandExtensions) + deriveEncoder[OAuthFlow].dropNullsExpandExtensions } - implicit val encoderOAuthFlows: Encoder[OAuthFlows] = deriveEncoder[OAuthFlows].mapJsonObject(expandExtensions) + implicit val encoderOAuthFlows: Encoder[OAuthFlows] = deriveEncoder[OAuthFlows].dropNullsExpandExtensions implicit val encoderSecurityScheme: Encoder[SecurityScheme] = - deriveEncoder[SecurityScheme].mapJsonObject(expandExtensions) + deriveEncoder[SecurityScheme].dropNullsExpandExtensions implicit val encoderHeader: Encoder[Header] = deriveEncoder[Header] - implicit val encoderExample: Encoder[Example] = deriveEncoder[Example].mapJsonObject(expandExtensions) - implicit val encoderResponse: Encoder[Response] = deriveEncoder[Response].mapJsonObject(expandExtensions) - implicit val encoderLink: Encoder[Link] = deriveEncoder[Link].mapJsonObject(expandExtensions) + implicit val encoderExample: Encoder[Example] = deriveEncoder[Example].dropNullsExpandExtensions + implicit val encoderResponse: Encoder[Response] = deriveEncoder[Response].dropNullsExpandExtensions + implicit val encoderLink: Encoder[Link] = deriveEncoder[Link].dropNullsExpandExtensions implicit val encoderCallback: Encoder[Callback] = Encoder.instance { callback => Json.obj(callback.pathItems.map { case (path, pathItem) => path -> pathItem.asJson }.toList: _*) } - implicit val encoderEncoding: Encoder[Encoding] = deriveEncoder[Encoding].mapJsonObject(expandExtensions) - implicit val encoderMediaType: Encoder[MediaType] = deriveEncoder[MediaType].mapJsonObject(expandExtensions) - implicit val encoderRequestBody: Encoder[RequestBody] = deriveEncoder[RequestBody].mapJsonObject(expandExtensions) + implicit val encoderEncoding: Encoder[Encoding] = deriveEncoder[Encoding].dropNullsExpandExtensions + implicit val encoderMediaType: Encoder[MediaType] = deriveEncoder[MediaType].dropNullsExpandExtensions + implicit val encoderRequestBody: Encoder[RequestBody] = deriveEncoder[RequestBody].dropNullsExpandExtensions implicit val encoderParameterStyle: Encoder[ParameterStyle] = { e => Encoder.encodeString(e.value) } implicit val encoderParameterIn: Encoder[ParameterIn] = { e => Encoder.encodeString(e.value) } - implicit val encoderParameter: Encoder[Parameter] = deriveEncoder[Parameter].mapJsonObject(expandExtensions) + implicit val encoderParameter: Encoder[Parameter] = deriveEncoder[Parameter].dropNullsExpandExtensions implicit val encoderResponseMap: Encoder[ListMap[ResponsesKey, ReferenceOr[Response]]] = (responses: ListMap[ResponsesKey, ReferenceOr[Response]]) => { val fields = responses.map { @@ -70,22 +70,21 @@ trait InternalSttpOpenAPICirceEncoders extends JsonSchemaCirceEncoders { implicit def encodeListMapForCallbacks: Encoder[ListMap[String, ReferenceOr[Callback]]] = doEncodeListMap(nullWhenEmpty = true) - deriveEncoder[Operation].mapJsonObject(expandExtensions) + deriveEncoder[Operation].dropNullsExpandExtensions } - implicit val encoderPathItem: Encoder[PathItem] = deriveEncoder[PathItem].mapJsonObject(expandExtensions) + implicit val encoderPathItem: Encoder[PathItem] = deriveEncoder[PathItem].dropNullsExpandExtensions implicit val encoderPaths: Encoder[Paths] = Encoder.instance { paths => val extensions = paths.extensions.asJsonObject val pathItems = paths.pathItems.asJson pathItems.asObject.map(_.deepMerge(extensions).asJson).getOrElse(pathItems) } - implicit val encoderComponents: Encoder[Components] = deriveEncoder[Components].mapJsonObject(expandExtensions) + implicit val encoderComponents: Encoder[Components] = deriveEncoder[Components].dropNullsExpandExtensions implicit val encoderServerVariable: Encoder[ServerVariable] = - deriveEncoder[ServerVariable].mapJsonObject(expandExtensions) - implicit val encoderServer: Encoder[Server] = deriveEncoder[Server].mapJsonObject(expandExtensions) - implicit val encoderTag: Encoder[Tag] = deriveEncoder[Tag].mapJsonObject(expandExtensions) - implicit val encoderInfo: Encoder[Info] = deriveEncoder[Info].mapJsonObject(expandExtensions) - implicit val encoderContact: Encoder[Contact] = deriveEncoder[Contact].mapJsonObject(expandExtensions) - implicit val encoderLicense: Encoder[License] = deriveEncoder[License].mapJsonObject(expandExtensions) - implicit val encoderOpenAPI: Encoder[OpenAPI] = - deriveEncoder[OpenAPI].mapJsonObject(expandExtensions).mapJson(_.deepDropNullValues) + deriveEncoder[ServerVariable].dropNullsExpandExtensions + implicit val encoderServer: Encoder[Server] = deriveEncoder[Server].dropNullsExpandExtensions + implicit val encoderTag: Encoder[Tag] = deriveEncoder[Tag].dropNullsExpandExtensions + implicit val encoderInfo: Encoder[Info] = deriveEncoder[Info].dropNullsExpandExtensions + implicit val encoderContact: Encoder[Contact] = deriveEncoder[Contact].dropNullsExpandExtensions + implicit val encoderLicense: Encoder[License] = deriveEncoder[License].dropNullsExpandExtensions + implicit val encoderOpenAPI: Encoder[OpenAPI] = deriveEncoder[OpenAPI].dropNullsExpandExtensions } diff --git a/openapi-circe/src/test/resources/securitySchema/security-schema-with-empty-scopes.json b/openapi-circe/src/test/resources/securitySchema/security-schema-with-empty-scopes.json deleted file mode 100644 index 7eb12bb..0000000 --- a/openapi-circe/src/test/resources/securitySchema/security-schema-with-empty-scopes.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "type" : "oauth2", - "description" : null, - "name" : null, - "in" : null, - "scheme" : null, - "bearerFormat" : null, - "flows" : { - "implicit" : null, - "password" : null, - "clientCredentials" : { - "authorizationUrl" : null, - "tokenUrl" : "openapi-circe-token", - "refreshUrl" : null, - "scopes" : { - - } - }, - "authorizationCode" : null - }, - "openIdConnectUrl" : null -} \ No newline at end of file diff --git a/openapi-circe/src/test/resources/securitySchema/security-schema-with-scopes.json b/openapi-circe/src/test/resources/securitySchema/security-schema-with-scopes.json deleted file mode 100644 index bc82845..0000000 --- a/openapi-circe/src/test/resources/securitySchema/security-schema-with-scopes.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "type" : "oauth2", - "description" : null, - "name" : null, - "in" : null, - "scheme" : null, - "bearerFormat" : null, - "flows" : { - "implicit" : null, - "password" : null, - "clientCredentials" : { - "authorizationUrl" : null, - "tokenUrl" : "openapi-circe-token", - "refreshUrl" : null, - "scopes" : { - "example" : "description" - } - }, - "authorizationCode" : null - }, - "openIdConnectUrl" : null -} \ No newline at end of file diff --git a/openapi-circe/src/test/resources/securityScheme/security-scheme-with-empty-scopes.json b/openapi-circe/src/test/resources/securityScheme/security-scheme-with-empty-scopes.json new file mode 100644 index 0000000..4c55238 --- /dev/null +++ b/openapi-circe/src/test/resources/securityScheme/security-scheme-with-empty-scopes.json @@ -0,0 +1,11 @@ +{ + "type" : "oauth2", + "flows" : { + "clientCredentials" : { + "tokenUrl" : "openapi-circe-token", + "scopes" : { + + } + } + } +} diff --git a/openapi-circe/src/test/resources/securityScheme/security-scheme-with-scopes.json b/openapi-circe/src/test/resources/securityScheme/security-scheme-with-scopes.json new file mode 100644 index 0000000..a2eeb6a --- /dev/null +++ b/openapi-circe/src/test/resources/securityScheme/security-scheme-with-scopes.json @@ -0,0 +1,11 @@ +{ + "type" : "oauth2", + "flows" : { + "clientCredentials" : { + "tokenUrl" : "openapi-circe-token", + "scopes" : { + "example" : "description" + } + } + } +} diff --git a/openapi-circe/src/test/resources/spec/3.0/schema.json b/openapi-circe/src/test/resources/spec/3.0/schema.json index 29b196c..a2ace49 100644 --- a/openapi-circe/src/test/resources/spec/3.0/schema.json +++ b/openapi-circe/src/test/resources/spec/3.0/schema.json @@ -15,6 +15,14 @@ "nullable": true, "description": "nullable string" }, + "nullable enum" : { + "description" : "nullable enum", + "enum" : [ + "a", + "b", + null + ] + }, "nullable reference": { "nullable": true, "allOf": [ @@ -37,6 +45,14 @@ "ex1" ] }, + "object with example" : { + "description" : "object with example", + "example" : { + "a" : 1, + "b" : null + }, + "type" : "object" + }, "min/max": { "minimum": 10, "maximum": 20, @@ -67,4 +83,4 @@ } } } -} \ No newline at end of file +} diff --git a/openapi-circe/src/test/resources/spec/3.1/schema.json b/openapi-circe/src/test/resources/spec/3.1/schema.json index cadd518..7cd7391 100644 --- a/openapi-circe/src/test/resources/spec/3.1/schema.json +++ b/openapi-circe/src/test/resources/spec/3.1/schema.json @@ -17,6 +17,14 @@ ], "description": "nullable string" }, + "nullable enum" : { + "description" : "nullable enum", + "enum" : [ + "a", + "b", + null + ] + }, "nullable reference": { "anyOf": [ { @@ -45,6 +53,16 @@ ] ] }, + "object with example" : { + "description" : "object with example", + "examples" : [ + { + "a" : 1, + "b" : null + } + ], + "type" : "object" + }, "min/max": { "minimum": 10, "maximum": 20, @@ -81,4 +99,4 @@ } } } -} \ No newline at end of file +} diff --git a/openapi-circe/src/test/scala/sttp/apispec/openapi/circe/DecoderTest.scala b/openapi-circe/src/test/scala/sttp/apispec/openapi/circe/DecoderTest.scala index 9fb49a0..b9d4aac 100644 --- a/openapi-circe/src/test/scala/sttp/apispec/openapi/circe/DecoderTest.scala +++ b/openapi-circe/src/test/scala/sttp/apispec/openapi/circe/DecoderTest.scala @@ -42,33 +42,33 @@ class DecoderTest extends AnyFunSuite with ResourcePlatform { val Right(openapi) = readJson("/spec/3.1/schema.json").flatMap(_.as[OpenAPI]): @unchecked assert(openapi.info.title === "API") val schemas = openapi.components.getOrElse(Components.Empty).schemas - assert(schemas.size === 11) + assert(schemas.size === 13) } test("all schemas types 3.0") { val Right(openapi) = readJson("/spec/3.0/schema.json").flatMap(_.as[OpenAPI]): @unchecked assert(openapi.info.title === "API") val schemas = openapi.components.getOrElse(Components.Empty).schemas - assert(schemas.size === 10) + assert(schemas.size === 12) } - test("decode security schema with not empty scopes") { + test("decode security scheme with not empty scopes") { val expectedScopes = Some(Some(ListMap("example" -> "description"))) val expectedToken = Some(Some(Some("openapi-circe-token"))) val Right(securityScheme) = - readJson("/securitySchema/security-schema-with-scopes.json").flatMap(_.as[SecurityScheme]): @unchecked + readJson("/securityScheme/security-scheme-with-scopes.json").flatMap(_.as[SecurityScheme]): @unchecked assert(securityScheme.flows.map(_.clientCredentials.map(_.tokenUrl)) === expectedToken) assert(securityScheme.flows.map(_.clientCredentials.map(_.scopes)) === expectedScopes) } - test("decode security schema with empty scopes") { + test("decode security scheme with empty scopes") { val expectedScopes = Some(Some(ListMap.empty[String, String])) val expectedToken = Some(Some(Some("openapi-circe-token"))) val Right(securityScheme) = - readJson("/securitySchema/security-schema-with-empty-scopes.json").flatMap(_.as[SecurityScheme]): @unchecked + readJson("/securityScheme/security-scheme-with-empty-scopes.json").flatMap(_.as[SecurityScheme]): @unchecked assert(securityScheme.flows.map(_.clientCredentials.map(_.tokenUrl)) === expectedToken) assert(securityScheme.flows.map(_.clientCredentials.map(_.scopes)) === expectedScopes) diff --git a/openapi-circe/src/test/scala/sttp/apispec/openapi/circe/threeone/EncoderTest.scala b/openapi-circe/src/test/scala/sttp/apispec/openapi/circe/threeone/EncoderTest.scala index 9ec4a87..85c3a72 100644 --- a/openapi-circe/src/test/scala/sttp/apispec/openapi/circe/threeone/EncoderTest.scala +++ b/openapi-circe/src/test/scala/sttp/apispec/openapi/circe/threeone/EncoderTest.scala @@ -4,7 +4,6 @@ import io.circe.syntax._ import org.scalatest.funsuite.AnyFunSuite import sttp.apispec._ import sttp.apispec.openapi._ -import sttp.apispec.openapi.circe.SttpOpenAPICirceEncoders import sttp.apispec.test._ import scala.collection.immutable.ListMap @@ -87,10 +86,13 @@ class EncoderTest extends AnyFunSuite with ResourcePlatform { schemaComponent("type 'null'")(Schema(SchemaType.Null)), schemaComponent("nullable string")(Schema(SchemaType.String, SchemaType.Null)), schemaComponent("nullable reference")(Schema.referenceTo("#/components/schemas/", "Foo").nullable), + schemaComponent("nullable enum")(Schema(`enum` = Some(List("a", "b", null).map(ExampleSingleValue(_))))), schemaComponent("single example")(Schema(SchemaType.String) .copy(examples = Some(List(ExampleSingleValue("exampleValue"))))), schemaComponent("multi valued example")(Schema(SchemaType.Array) .copy(examples = Some(List(ExampleMultipleValue(List("ex1", "ex1")))))), + schemaComponent("object with example")(Schema(SchemaType.Object) + .copy(examples = Some(List(ExampleSingleValue("""{"a": 1, "b": null}"""))))), schemaComponent("min/max")(Schema( minimum = Some(BigDecimal(10)), maximum = Some(BigDecimal(20)), @@ -116,7 +118,7 @@ class EncoderTest extends AnyFunSuite with ResourcePlatform { val schemas31 = ListMap( schemaComponent("multiple examples")(Schema(SchemaType.String) - .copy(examples = Some(List("ex1", "ex2").map(ExampleSingleValue)))), + .copy(examples = Some(List("ex1", "ex2").map(ExampleSingleValue(_))))), ) val openApiJson = fullSchemaOpenApi.copy( @@ -152,19 +154,19 @@ class EncoderTest extends AnyFunSuite with ResourcePlatform { ) - test("encode security schema with empty scopes") { + test("encode security scheme with empty scopes") { import sttp.apispec.openapi.circe._ - val Right(expectedSecuritySchema) = readJson("/securitySchema/security-schema-with-empty-scopes.json") + val Right(expectedSecurityScheme) = readJson("/securityScheme/security-scheme-with-empty-scopes.json") val securityScheme = Some(clientCredentialsSecurityScheme(ListMap.empty)) - assert(expectedSecuritySchema === securityScheme.asJson) + assert(expectedSecurityScheme === securityScheme.asJson) } - test("encode security schema with not empty scopes") { + test("encode security scheme with not empty scopes") { import sttp.apispec.openapi.circe._ - val Right(expectedSecuritySchema) = readJson("/securitySchema/security-schema-with-scopes.json") + val Right(expectedSecurityScheme) = readJson("/securityScheme/security-scheme-with-scopes.json") val securityScheme = Some(clientCredentialsSecurityScheme(ListMap("example" -> "description"))) - assert(expectedSecuritySchema === securityScheme.asJson) + assert(expectedSecurityScheme === securityScheme.asJson) } }