Skip to content

Commit

Permalink
fix: improve handling of nullable parameters
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Edgar <[email protected]>
  • Loading branch information
MikeEdgar committed Jan 21, 2025
1 parent bd6420e commit 1b45dd9
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ public static void addTypeObserver(Schema observed, Schema observer) {
setTypesRetainingNull(observer, getTypeList(observed));
}

public static Boolean isEmpty(Schema schema) {
if (schema instanceof BaseModel) {
return ((BaseModel<?>) schema).getAllProperties().isEmpty();
}
return null;
}

static void notifyTypeObservers(Schema observed, Consumer<Schema> observerAction) {
List<Schema> typeObservers = Extensions.getTypeObservers(observed);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -716,9 +716,21 @@ void augmentParamSchema(Parameter param, ParameterContext context) {
setDefaultValue(localSchema, context.defaultValue);

if (localOnlySchemaModified(paramSchema, localSchema, modCount)) {
// Add new `allOf` schema, erasing `type` derived above from the local schema
Schema allOf = OASFactory.createSchema().addAllOf(paramSchema).addAllOf(localSchema.type((List<SchemaType>) null));
param.setSchema(allOf);
Boolean nullable = SchemaSupport.getNullable(localSchema);
localSchema.setType(null);

if (Boolean.FALSE.equals(SchemaSupport.isEmpty(localSchema))) {
// Add new `allOf` schema, erasing `type` derived above from the local schema
localSchema = OASFactory.createSchema()
.addAllOf(paramSchema)
.addAllOf(localSchema);
param.setSchema(localSchema);
} else if (Boolean.TRUE.equals(nullable)) {
localSchema = OASFactory.createSchema()
.addAnyOf(paramSchema)
.addAnyOf(SchemaSupport.nullSchema());
param.setSchema(localSchema);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@
import org.json.JSONException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import io.smallrye.openapi.api.OpenApiConfig;
import io.smallrye.openapi.api.SmallRyeOASConfig;
import io.smallrye.openapi.model.Extensions;
import test.io.smallrye.openapi.runtime.scanner.Widget;
import test.io.smallrye.openapi.runtime.scanner.jakarta.MultipleContentTypesWithFormParamsTestResource;
Expand Down Expand Up @@ -680,4 +683,34 @@ public String get(@CustomIntConstraint @jakarta.ws.rs.QueryParam("id") int id) {
void testParameterConstraintComposition() throws IOException, JSONException {
test("params.constraint-composition.json", ParameterConstraintComposition.CLASSES);
}

static class NullableRefParamClasses {
static final Class<?>[] CLASSES = {
StatusEnum.class,
FruitResource.class
};

enum StatusEnum {
VAL1,
VAL2;
}

@jakarta.ws.rs.Path("/status")
static class FruitResource {
@jakarta.ws.rs.GET
public String get(@jakarta.ws.rs.QueryParam("status") @org.jetbrains.annotations.Nullable StatusEnum status) {
return null;
}
}
}

@ParameterizedTest
@CsvSource({
"3.0.3, params.nullable-ref-param-3.0.json",
"3.1.0, params.nullable-ref-param-3.1.json",
})
void testNullableRefParam(String oasVersion, String expectedResource) throws IOException, JSONException {
OpenAPI result = scan(config(SmallRyeOASConfig.VERSION, oasVersion), NullableRefParamClasses.CLASSES);
assertJsonEquals(expectedResource, result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"openapi" : "3.0.3",
"components" : {
"schemas" : {
"StatusEnum" : {
"enum" : [ "VAL1", "VAL2" ],
"type" : "string"
}
}
},
"paths" : {
"/status" : {
"get" : {
"parameters" : [ {
"name" : "status",
"in" : "query",
"schema" : {
"allOf" : [ {
"$ref" : "#/components/schemas/StatusEnum"
} ],
"nullable" : true
}
} ],
"responses" : {
"200" : {
"description" : "OK",
"content" : {
"*/*" : {
"schema" : {
"type" : "string"
}
}
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"openapi" : "3.1.0",
"components" : {
"schemas" : {
"StatusEnum" : {
"enum" : [ "VAL1", "VAL2" ],
"type" : "string"
}
}
},
"paths" : {
"/status" : {
"get" : {
"parameters" : [ {
"name" : "status",
"in" : "query",
"schema" : {
"anyOf" : [ {
"$ref" : "#/components/schemas/StatusEnum"
}, {
"type": "null"
} ]
}
} ],
"responses" : {
"200" : {
"description" : "OK",
"content" : {
"*/*" : {
"schema" : {
"type" : "string"
}
}
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.smallrye.openapi.testdata.kotlin
import jakarta.ws.rs.GET
import jakarta.ws.rs.Path
import jakarta.ws.rs.Produces
import jakarta.ws.rs.QueryParam
import jakarta.ws.rs.core.MediaType

import kotlinx.coroutines.flow.Flow
Expand All @@ -15,11 +16,15 @@ class KotlinResource {
@GET
@Produces(MediaType.SERVER_SENT_EVENTS)
@RestStreamElementType(MediaType.APPLICATION_JSON)
fun hello(): Flow<String> {
fun hello(@QueryParam("status") status: StatusEnum?): Flow<String> {
return flow {
Foobar("Hello")
}
}
}

data class Foobar(val data: String)
data class Foobar(val data: String)

enum class StatusEnum {
VAL1, VAL2;
}

0 comments on commit 1b45dd9

Please sign in to comment.