Skip to content

Commit

Permalink
make schema nullable instead of adding null-types into the apispec sc…
Browse files Browse the repository at this point in the history
…hema

These null-types are only supported in openapi 3.1.0. For 3.0.3, we need a different encoding.
Therefore, we should leave the distinction to sttp-apispec, instead of deciding here in tapir.
  • Loading branch information
cornerman committed Nov 21, 2023
1 parent 5350715 commit 659d27d
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ private[schema] class TSchemaToASchema(toSchemaReference: ToSchemaReference, mar
// the initial list of schemas.
val propagated = propagateMetadataForOption(schema, opt).element
val ref = toSchemaReference.map(propagated, name)
if (!markOptionsAsNullable) ref else ASchema.oneOf(List(ref, ASchema(SchemaType.Null)), None)
if (!markOptionsAsNullable) ref else ref.copy(nullable = Some(true))
case TSchemaType.SOption(el) => apply(el, isOptionElement = true)
case TSchemaType.SBinary() => ASchema(SchemaType.String).copy(format = SchemaFormat.Binary)
case TSchemaType.SDate() => ASchema(SchemaType.String).copy(format = SchemaFormat.Date)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
openapi: 3.0.3
info:
title: ClassWithOptionClassField
version: '1.0'
paths:
/:
post:
operationId: postRoot
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ClassWithOptionClassField'
required: true
responses:
'200':
description: ''
content:
text/plain:
schema:
type: string
'400':
description: 'Invalid value for: body'
content:
text/plain:
schema:
type: string
components:
schemas:
Bar:
required:
- bar
type: object
properties:
bar:
type: integer
format: int32
ClassWithOptionClassField:
required:
- requiredStringField
type: object
properties:
optionalObjField:
allOf:
- $ref: '#/components/schemas/Bar'
nullable: true
requiredStringField:
type: string
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
openapi: 3.0.3
info:
title: ClassWithOptionField
version: '1.0'
paths:
/:
get:
operationId: getRoot
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ClassWithOptionField'
required: true
responses:
'200':
description: ''
content:
text/plain:
schema:
type: string
'400':
description: 'Invalid value for: body'
content:
text/plain:
schema:
type: string
components:
schemas:
ClassWithOptionField:
required:
- requiredStringField
type: object
properties:
optionalIntField:
type: integer
format: int32
nullable: true
requiredStringField:
type: string
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,19 @@ class VerifyYamlTest extends AnyFunSuite with Matchers {
actualYamlNoIndent shouldBe expectedYaml
}

test("should mark optional fields as nullable when configured to do so using OpenAPI 3.0") {
case class ClassWithOptionField(optionalIntField: Option[Int], requiredStringField: String)

val e = endpoint.in(jsonBody[ClassWithOptionField]).out(stringBody)
val expectedYaml = load("expected_nullable_option_field_303.yml")

val options = OpenAPIDocsOptions.default.copy(markOptionsAsNullable = true)

val actualYaml = OpenAPIDocsInterpreter(options).toOpenAPI(e, Info("ClassWithOptionField", "1.0")).copy(openapi = "3.0.3").toYaml3_0_3
val actualYamlNoIndent = noIndentation(actualYaml)
actualYamlNoIndent shouldBe expectedYaml
}

test("should mark optional class fields as nullable when configured to do so") {
case class Bar(bar: Int)
case class ClassWithOptionClassField(optionalObjField: Option[Bar], requiredStringField: String)
Expand All @@ -690,6 +703,21 @@ class VerifyYamlTest extends AnyFunSuite with Matchers {
actualYamlNoIndent shouldBe expectedYaml
}

test("should mark optional class fields as nullable when configured to do so using OpenAPI 3.0") {
case class Bar(bar: Int)
case class ClassWithOptionClassField(optionalObjField: Option[Bar], requiredStringField: String)

val e = endpoint.in(jsonBody[ClassWithOptionClassField]).out(stringBody).post
val expectedYaml = load("expected_nullable_option_class_field_303.yml")

val options = OpenAPIDocsOptions.default.copy(markOptionsAsNullable = true)

val actualYaml =
OpenAPIDocsInterpreter(options).toOpenAPI(e, Info("ClassWithOptionClassField", "1.0")).copy(openapi = "3.0.3").toYaml3_0_3
val actualYamlNoIndent = noIndentation(actualYaml)
actualYamlNoIndent shouldBe expectedYaml
}

test("should generate default and example values for nested optional fields") {
case class Nested(nestedValue: String)
case class ClassWithNestedOptionalField(
Expand Down
2 changes: 1 addition & 1 deletion project/Versions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ object Versions {
val sttp = "3.9.1"
val sttpModel = "1.7.6"
val sttpShared = "1.3.16"
val sttpApispec = "0.7.1"
val sttpApispec = "0.7.2"
val akkaHttp = "10.2.10"
val akkaStreams = "2.6.20"
val pekkoHttp = "1.0.0"
Expand Down

0 comments on commit 659d27d

Please sign in to comment.