Skip to content

Commit

Permalink
Fix OpenAPI 3.0 output of nullable references
Browse files Browse the repository at this point in the history
When a nullable field or parameter references a non-nullable schema, we
generate a schema for the field which uses anyOf to permit null. Any
informational properties or additional assertions defined on the field
are also put on the field schema and the type property is also retained.

When we were transforming this field schema for OpenAPI 3.0, we were
only doing certain transformations if the type property is not present,
which doesn't handle the case above.

Ensure the transformation to and from OpenAPI 3.0 schemas handles these
nullable references which include the type property.
  • Loading branch information
Azquelt committed Jan 15, 2025
1 parent a6f6367 commit 9e8d58e
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ private void populateSchemaObject30(Schema schema, O node) {
}

// Detect {$ref=....,nullable=true} and convert to anyOf[{$ref=...}, {type=null}]
if (schema.getRef() != null && schema.getType() == null && SchemaSupport.getNullable(schema) == Boolean.TRUE) {
if (schema.getRef() != null && SchemaSupport.getNullable(schema) == Boolean.TRUE) {
List<Schema> newAnyOfSchemas = new ArrayList<>();
newAnyOfSchemas.add(OASFactory.createSchema().ref(schema.getRef()));
newAnyOfSchemas.add(SchemaSupport.nullSchema());
Expand All @@ -201,7 +201,11 @@ private void populateSchemaObject30(Schema schema, O node) {
schema.addAllOf(OASFactory.createSchema().anyOf(newAnyOfSchemas));
}
schema.setRef(null);
SchemaSupport.setNullable(schema, null);
if (schema.getType() == null) {
SchemaSupport.setNullable(schema, null);
} else {
SchemaSupport.setNullable(schema, Boolean.TRUE);
}
}

// Detect {enum=[null]} and convert to {type=null}
Expand Down Expand Up @@ -389,7 +393,7 @@ private ReplacementFields compute30ReplacementFields(Schema schema31) {
}

// If we have anyOf = [{type=null}, {$ref=...}], remove it and set nullable and allOf = [{$ref=...}]
if (result.anyOf != null && result.anyOf.size() == 2 && result.ref == null && result.type == null) {
if (result.anyOf != null && result.anyOf.size() == 2 && result.ref == null) {
Optional<Schema> typeNullSchema = result.anyOf.stream().filter(s -> isSoloTypeNull(s)).findFirst();
Optional<Schema> refSchema = result.anyOf.stream().filter(s -> isSoloRef(s)).findFirst();
if (typeNullSchema.isPresent() && refSchema.isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,29 @@ static class Turtle extends Reptile {
String shellPattern;
Speed speed;
}

@Test
void testNullableFieldReference() throws Exception {
Index index = indexOf(FieldTarget.class, FieldReferrer.class);
SmallRyeOpenAPI result1 = SmallRyeOpenAPI.builder()
.withConfig(config(Collections.emptyMap()))
.withIndex(index)
.defaultRequiredProperties(false)
.build();
printToConsole(result1.model());
assertJsonEquals("components.schemas.schemaproperty-nullable.json", result1.model());
}

@org.eclipse.microprofile.openapi.annotations.media.Schema
public static class FieldTarget {
}

@org.eclipse.microprofile.openapi.annotations.media.Schema
public static class FieldReferrer {
@org.eclipse.microprofile.openapi.annotations.media.Schema(nullable = true)
private FieldTarget a;
@org.eclipse.microprofile.openapi.annotations.media.Schema(nullable = true, description = "b")
private FieldTarget b;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@
}
]
},
"NullRefWithAnnotations": {
"description": "Description is an annotation. Scanner generates type as well here.",
"type": ["object", "null"],
"anyOf": [
{
"$ref": "#/components/schemas/mySchema"
},
{
"type": "null"
}
]
},
"NullRefWithAnyOf": {
"allOf": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@
}
]
},
"NullRefWithAnnotations": {
"description": "Description is an annotation. Scanner generates type as well here.",
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/mySchema"
}
],
"nullable": true
},
"NullRefWithAnyOf": {
"allOf": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"openapi" : "3.1.0",
"components" : {
"schemas" : {
"FieldReferrer" : {
"type" : "object",
"properties" : {
"a" : {
"anyOf" : [ {
"$ref" : "#/components/schemas/FieldTarget"
}, {
"type" : "null"
} ]
},
"b" : {
"description" : "b",
"type" : "object",
"anyOf" : [ {
"$ref" : "#/components/schemas/FieldTarget"
}, {
"type" : "null"
} ]
}
}
},
"FieldTarget" : {
"type" : "object"
}
}
}
}

0 comments on commit 9e8d58e

Please sign in to comment.