Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@

import io.smallrye.openapi.api.util.MergeUtil;
import io.smallrye.openapi.internal.models.media.SchemaSupport;
import io.smallrye.openapi.model.BaseModel;
import io.smallrye.openapi.model.Extensions;
import io.smallrye.openapi.runtime.io.Names;
import io.smallrye.openapi.runtime.io.schema.SchemaConstant;
Expand Down Expand Up @@ -688,7 +689,12 @@ void mapParameterStyle(Parameter param, ParameterContext context) {
}

void mapParameterSchema(Parameter param, ParameterContext context) {
if (ModelUtil.parameterHasSchema(param) || context.targetType == null) {
if (context.targetType == null) {
return;
}

if (ModelUtil.parameterHasSchema(param)) {
mergeParameterTypeSchema(param, context);
return;
}

Expand Down Expand Up @@ -720,6 +726,43 @@ void mapParameterSchema(Parameter param, ParameterContext context) {
ModelUtil.setParameterSchema(param, schema);
}

private void mergeParameterTypeSchema(Parameter param, ParameterContext context) {
Map<String, Schema> paramSchemas = ModelUtil.getParameterMediaTypeSchemas(param);
Map<String, Schema> scannableParamSchemas = new HashMap<>(paramSchemas.size());

for (var entry : paramSchemas.entrySet()) {
if (entry.getValue().getRef() == null && entry.getValue().getType() == null) {
scannableParamSchemas.put(entry.getKey(), entry.getValue());
}
}

if (!scannableParamSchemas.isEmpty()) {
Schema typeSchema = SchemaFactory.typeToSchema(scannerContext, context.targetType, null);

if (typeSchema == null) {
// Hidden schema
return;
}

for (var entry : scannableParamSchemas.entrySet()) {
var paramSchema = entry.getValue();

if (typeSchema.getRef() != null) {
paramSchema.setRef(typeSchema.getRef());
} else {
/*
* The type schema must be cloned before using it as the target of an object merge.
* Merging here will overlay the parameter schema's attributes over the type schema's
* attributes before replacing the schema in the parameter model.
*/
typeSchema = BaseModel.deepCopy(typeSchema, Schema.class);
paramSchema = mergeObjects(typeSchema, paramSchema);
ModelUtil.setParameterMediaTypeSchema(param, entry.getKey(), paramSchema);
}
}
}
}

void mapParameterExtensions(Parameter param, ParameterContext context) {
if (param.getExtensions() == null) {
param.setExtensions(scannerContext.io().extensionIO().readMap(context.target));
Expand Down
88 changes: 73 additions & 15 deletions core/src/main/java/io/smallrye/openapi/runtime/util/ModelUtil.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package io.smallrye.openapi.runtime.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
Expand Down Expand Up @@ -219,6 +220,59 @@ public static boolean parameterHasSchema(Parameter parameter) {
return false;
}

/**
* Returns the map of {@link Schema}s defined for the given
* {@link Parameter} with the schema's content type as the key (possibly
* null for the {@linkplain Parameter#getSchema() schema defined directly on
* the parameter}.
*
* A schema can be defined either via the parameter's "schema" property, or
* any "content.*.schema" property.
*
* @param parameter
* Parameter
* @return map of schemas by content type, never null
*/
public static Map<String, Schema> getParameterMediaTypeSchemas(Parameter parameter) {
Map<String, Schema> mediaTypeSchemas;

if (parameter.getSchema() != null) {
mediaTypeSchemas = new HashMap<>(1);
mediaTypeSchemas.put(null, parameter.getSchema());
} else {
Map<String, MediaType> mediaTypes = getMediaTypesOrEmpty(parameter.getContent());
mediaTypeSchemas = new LinkedHashMap<>(mediaTypes.size());

for (var entry : mediaTypes.entrySet()) {
mediaTypeSchemas.put(entry.getKey(), entry.getValue().getSchema());
}
}

return mediaTypeSchemas;
}

/**
* Set a schema to the parameter for the given content type. When the
* content type is null, the schema is set
* {@linkplain Parameter#setSchema(Schema) directly on the schema}.
* Otherwise, the schema will be set in the parameter's content on a new or
* existing {@link MediaType} using content type as its key.
*
* @param parameter
* The parameter
* @param contentType
* The content type to which the schema applies (null allowed)
* @param schema
* The schema
*/
public static void setParameterMediaTypeSchema(Parameter parameter, String contentType, Schema schema) {
if (contentType == null) {
parameter.setSchema(schema);
} else {
parameter.setContent(putMediaTypeSchema(parameter.getContent(), contentType, schema));
}
}

/**
* Returns the list of {@link Schema}s defined for the given {@link Parameter}.
* A schema can be defined either via the parameter's "schema" property, or any
Expand All @@ -228,20 +282,7 @@ public static boolean parameterHasSchema(Parameter parameter) {
* @return list of schemas, never null
*/
public static List<Schema> getParameterSchemas(Parameter parameter) {
if (parameter.getSchema() != null) {
return Arrays.asList(parameter.getSchema());
}
Map<String, MediaType> mediaTypes = getMediaTypesOrEmpty(parameter.getContent());
if (!mediaTypes.isEmpty()) {
List<Schema> schemas = new ArrayList<>(mediaTypes.size());

for (MediaType mediaType : mediaTypes.values()) {
if (mediaType.getSchema() != null) {
schemas.add(mediaType.getSchema());
}
}
}
return Collections.emptyList();
return new ArrayList<>(getParameterMediaTypeSchemas(parameter).values());
}

/**
Expand Down Expand Up @@ -341,6 +382,23 @@ static Map<String, MediaType> getMediaTypesOrEmpty(Content content) {
return Collections.emptyMap();
}

static Content putMediaTypeSchema(Content content, String contentType, Schema schema) {
if (content == null) {
content = OASFactory.createContent();
}

MediaType mediaType = content.getMediaType(contentType);

if (mediaType == null) {
mediaType = OASFactory.createMediaType();
content.addMediaType(contentType, mediaType);
}

mediaType.setSchema(schema);

return content;
}

/**
* Returns the name component of the ref.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,59 @@ void testJakartaParameterInBeanFromField() throws IOException, JSONException {
test.io.smallrye.openapi.runtime.scanner.Widget.class);
}

@Test
void testBeanParamEnumDefaultValue() throws IOException, JSONException {
class QueryOptions {
@jakarta.ws.rs.QueryParam("sortOrder")
@Parameter(description = "Sort direction", schema = @Schema(defaultValue = "ASC"))
SortOrder sortOrder;
}

@Schema(name = "filter-option-a", ref = "http://example.com/schemas/filter-schema-a.json")
class FilterOptionA {
}

class FilterOptionB {
}

@Schema(hidden = true)
class SecretIdentifier {
}

@jakarta.ws.rs.Path("/beanparam-enum-default")
class ListResource {
@jakarta.ws.rs.GET
@jakarta.ws.rs.Produces(jakarta.ws.rs.core.MediaType.APPLICATION_JSON)
public String list(
@jakarta.ws.rs.BeanParam QueryOptions options,
@jakarta.ws.rs.QueryParam("altSortOrder") @Parameter(description = "Alternate Sort direction", schema = @Schema(implementation = String.class, enumeration = {
"asc", "desc" }, defaultValue = "desc")) SortOrder sortOrder,
@jakarta.ws.rs.QueryParam("filter1") @Parameter(description = "Filter 1", schema = @Schema(description = "Standard filter schema")) FilterOptionA filter1,
@jakarta.ws.rs.QueryParam("filter2") @Parameter(description = "Filter 2", schema = @Schema(ref = "http://example.com/schemas/filter-schema-b.json", description = "Another filter")) FilterOptionA filter2,
@jakarta.ws.rs.QueryParam("filter3") @Parameter(description = "Filter 3 with multiple variants", content = {
@Content(mediaType = "text/plain", schema = @Schema(description = "Third filter in plain text format")),
@Content(mediaType = "application/json", schema = @Schema(description = "Third filter in JSON format")),
}) FilterOptionA multivariantFilter3,
@jakarta.ws.rs.QueryParam("request-id") @Parameter(description = "Identifier for the request", schema = @Schema(format = "custom-id")) Integer requestId,
@jakarta.ws.rs.QueryParam("secret-id") @Parameter(description = "Secret identifier for the request", schema = @Schema(format = "secret-id", description = "The schema for this parameter is not disclosed")) SecretIdentifier secretId,
@jakarta.ws.rs.QueryParam("variable-id") @Parameter(description = "Identifier with multiple variants", content = {
@Content(mediaType = "text/x-custom-type1", schema = @Schema(minimum = "1", maximum = "50")),
@Content(mediaType = "text/x-custom-type2", schema = @Schema(minimum = "51", maximum = "99")),
}) Long multivariantIdentifier) {
return "ok";
}
}

test("params.beanparam-enum-default-value.json", SortOrder.class, FilterOptionA.class, FilterOptionB.class,
SecretIdentifier.class,
QueryOptions.class, ListResource.class);
}

enum SortOrder {
ASC,
DESC
}

@Test
void testJavaxParameterInBeanFromSetter() throws IOException, JSONException {
test("params.parameter-in-bean-from-setter.json",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
{
"openapi" : "3.1.0",
"components" : {
"schemas" : {
"SortOrder" : {
"type" : "string",
"enum" : [ "ASC", "DESC" ]
},
"filter-option-a" : {
"$ref" : "http://example.com/schemas/filter-schema-a.json"
}
}
},
"paths" : {
"/beanparam-enum-default" : {
"get" : {
"parameters" : [ {
"description" : "Alternate Sort direction",
"schema" : {
"type" : "string",
"default" : "desc",
"enum" : [ "asc", "desc" ]
},
"name" : "altSortOrder",
"in" : "query"
}, {
"description" : "Filter 1",
"schema" : {
"description" : "Standard filter schema",
"$ref" : "#/components/schemas/filter-option-a"
},
"name" : "filter1",
"in" : "query"
}, {
"description" : "Filter 2",
"schema" : {
"$ref" : "http://example.com/schemas/filter-schema-b.json",
"description" : "Another filter"
},
"name" : "filter2",
"in" : "query"
}, {
"content" : {
"text/plain" : {
"schema" : {
"description" : "Third filter in plain text format",
"$ref" : "#/components/schemas/filter-option-a"
}
},
"application/json" : {
"schema" : {
"description" : "Third filter in JSON format",
"$ref" : "#/components/schemas/filter-option-a"
}
}
},
"description" : "Filter 3 with multiple variants",
"name" : "filter3",
"in" : "query"
}, {
"description" : "Identifier for the request",
"schema" : {
"type" : "integer",
"format" : "custom-id"
},
"name" : "request-id",
"in" : "query"
}, {
"description" : "Secret identifier for the request",
"schema" : {
"description" : "The schema for this parameter is not disclosed",
"format" : "secret-id"
},
"name" : "secret-id",
"in" : "query"
}, {
"description" : "Sort direction",
"schema" : {
"default" : "ASC",
"$ref" : "#/components/schemas/SortOrder"
},
"name" : "sortOrder",
"in" : "query"
}, {
"content" : {
"text/x-custom-type1" : {
"schema" : {
"type" : "integer",
"format" : "int64",
"maximum" : 50,
"minimum" : 1
}
},
"text/x-custom-type2" : {
"schema" : {
"type" : "integer",
"format" : "int64",
"maximum" : 99,
"minimum" : 51
}
}
},
"description" : "Identifier with multiple variants",
"name" : "variable-id",
"in" : "query"
} ],
"responses" : {
"200" : {
"description" : "OK",
"content" : {
"application/json" : {
"schema" : {
"type" : "string"
}
}
}
}
}
}
}
}
}
Loading