Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed wrong schema when schema set by RequestBody annotation #1072

Merged
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 @@ -111,6 +111,8 @@
import static io.micronaut.openapi.visitor.ElementUtils.isNullable;
import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.getSecurityProperties;
import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.isOpenApiEnabled;
import static io.micronaut.openapi.visitor.SchemaUtils.COMPONENTS_CALLBACKS_PREFIX;
import static io.micronaut.openapi.visitor.SchemaUtils.COMPONENTS_SCHEMAS_PREFIX;
import static io.micronaut.openapi.visitor.SchemaUtils.TYPE_OBJECT;
import static io.micronaut.openapi.visitor.Utils.DEFAULT_MEDIA_TYPES;

Expand All @@ -122,8 +124,6 @@
*/
public abstract class AbstractOpenApiEndpointVisitor extends AbstractOpenApiVisitor {

public static final String COMPONENTS_CALLBACKS_PREFIX = "#/components/callbacks/";

protected static final String CONTEXT_CHILD_PATH = "internal.child.path";
protected static final String CONTEXT_CHILD_OP_ID_PREFIX = "internal.opId.prefix";
protected static final String CONTEXT_CHILD_OP_ID_SUFFIX = "internal.opId.suffix";
Expand Down Expand Up @@ -398,6 +398,8 @@ public void visitMethod(MethodElement element, VisitorContext context) {
List<MediaType> consumesMediaTypes = consumesMediaTypes(element);
Map<PathItem, io.swagger.v3.oas.models.Operation> swaggerOperations = readOperations(pathItemEntry.getKey(), httpMethod, pathItems, element, context);

boolean isRequestBodySchemaSet = false;

for (Map.Entry<PathItem, io.swagger.v3.oas.models.Operation> operationEntry : swaggerOperations.entrySet()) {
io.swagger.v3.oas.models.Operation swaggerOperation = operationEntry.getValue();
io.swagger.v3.oas.models.ExternalDocumentation externalDocs = readExternalDocs(element, context);
Expand Down Expand Up @@ -427,7 +429,12 @@ public void visitMethod(MethodElement element, VisitorContext context) {
readResponse(element, context, openAPI, swaggerOperation, javadocDescription);

if (permitsRequestBody) {
RequestBody requestBody = readSwaggerRequestBody(element, consumesMediaTypes, context);
Pair<RequestBody, Boolean> requestBodyPair = readSwaggerRequestBody(element, consumesMediaTypes, context);
RequestBody requestBody = null;
if (requestBodyPair != null) {
requestBody = requestBodyPair.getFirst();
isRequestBodySchemaSet = requestBodyPair.getSecond();
}
if (requestBody != null) {
RequestBody currentRequestBody = swaggerOperation.getRequestBody();
if (currentRequestBody != null) {
Expand All @@ -454,14 +461,15 @@ public void visitMethod(MethodElement element, VisitorContext context) {
List<TypedElement> extraBodyParameters = new ArrayList<>();
for (io.swagger.v3.oas.models.Operation operation : swaggerOperations.values()) {
processParameters(element, context, openAPI, operation, javadocDescription, permitsRequestBody, pathVariables, consumesMediaTypes, extraBodyParameters, httpMethod, matchTemplates, pathItems);
processExtraBodyParameters(context, httpMethod, openAPI, operation, javadocDescription, consumesMediaTypes, extraBodyParameters);
processExtraBodyParameters(context, httpMethod, openAPI, operation, javadocDescription, isRequestBodySchemaSet, consumesMediaTypes, extraBodyParameters);
}
}
}

private void processExtraBodyParameters(VisitorContext context, HttpMethod httpMethod, OpenAPI openAPI,
io.swagger.v3.oas.models.Operation swaggerOperation,
JavadocDescription javadocDescription,
boolean isRequestBodySchemaSet,
List<MediaType> consumesMediaTypes,
List<TypedElement> extraBodyParameters) {
RequestBody requestBody = swaggerOperation.getRequestBody();
Expand Down Expand Up @@ -491,31 +499,39 @@ private void processExtraBodyParameters(VisitorContext context, HttpMethod httpM
mediaType.setSchema(schema);
}
if (schema.get$ref() != null) {
ComposedSchema composedSchema = new ComposedSchema();
Schema extraBodyParametersSchema = new Schema();
// Composition of existing + a new schema where extra body parameters are going to be added
composedSchema.addAllOfItem(schema);
composedSchema.addAllOfItem(extraBodyParametersSchema);
schema = extraBodyParametersSchema;
mediaType.setSchema(composedSchema);
if (isRequestBodySchemaSet) {
schema = openAPI.getComponents().getSchemas().get(schema.get$ref().substring(COMPONENTS_SCHEMAS_PREFIX.length()));
} else {
ComposedSchema composedSchema = new ComposedSchema();
Schema extraBodyParametersSchema = new Schema();
// Composition of existing + a new schema where extra body parameters are going to be added
composedSchema.addAllOfItem(schema);
composedSchema.addAllOfItem(extraBodyParametersSchema);
schema = extraBodyParametersSchema;
mediaType.setSchema(composedSchema);
}
}
for (TypedElement parameter : extraBodyParameters) {
processBodyParameter(context, openAPI, javadocDescription, MediaType.of(mediaTypeName), schema, parameter);
if (!isRequestBodySchemaSet) {
processBodyParameter(context, openAPI, javadocDescription, MediaType.of(mediaTypeName), schema, parameter);
}
if (mediaTypeName.equals(MediaType.MULTIPART_FORM_DATA)) {
for (String prop : (Set<String>) schema.getProperties().keySet()) {
Map<String, Encoding> encodings = mediaType.getEncoding();
if (encodings == null) {
encodings = new HashMap<>();
mediaType.setEncoding(encodings);
}
// if content type doesn't set by annotation,
// we can set application/octet-stream for file upload classes
Encoding encoding = encodings.get(prop);
if (encoding == null && isFileUpload(parameter.getType())) {
encoding = new Encoding();
encodings.put(prop, encoding);

encoding.setContentType(MediaType.APPLICATION_OCTET_STREAM);
if (CollectionUtils.isNotEmpty(schema.getProperties())) {
for (String prop : (Set<String>) schema.getProperties().keySet()) {
Map<String, Encoding> encodings = mediaType.getEncoding();
if (encodings == null) {
encodings = new HashMap<>();
mediaType.setEncoding(encodings);
}
// if content type doesn't set by annotation,
// we can set application/octet-stream for file upload classes
Encoding encoding = encodings.get(prop);
if (encoding == null && isFileUpload(parameter.getType())) {
encoding = new Encoding();
encodings.put(prop, encoding);

encoding.setContentType(MediaType.APPLICATION_OCTET_STREAM);
}
}
}
}
Expand Down Expand Up @@ -662,9 +678,9 @@ private void processParameter(VisitorContext context, OpenAPI openAPI,
return;
}
if (permitsRequestBody && swaggerOperation.getRequestBody() == null) {
RequestBody requestBody = readSwaggerRequestBody(parameter, consumesMediaTypes, context);
if (requestBody != null) {
swaggerOperation.setRequestBody(requestBody);
Pair<RequestBody, Boolean> requestBodyPair = readSwaggerRequestBody(parameter, consumesMediaTypes, context);
if (requestBodyPair != null && requestBodyPair.getFirst() != null) {
swaggerOperation.setRequestBody(requestBodyPair.getFirst());
}
}

Expand Down Expand Up @@ -1717,15 +1733,25 @@ private void processResponses(io.swagger.v3.oas.models.Operation operation, List
}
}

private RequestBody readSwaggerRequestBody(Element element, List<MediaType> consumesMediaTypes, VisitorContext context) {
// boolean - is swagger schema has implementation
private Pair<RequestBody, Boolean> readSwaggerRequestBody(Element element, List<MediaType> consumesMediaTypes, VisitorContext context) {
AnnotationValue<io.swagger.v3.oas.annotations.parameters.RequestBody> requestBodyAnnValue =
element.findAnnotation(io.swagger.v3.oas.annotations.parameters.RequestBody.class).orElse(null);

if (requestBodyAnnValue == null) {
return null;
}

boolean hasSchemaImplementation = false;

AnnotationValue<io.swagger.v3.oas.annotations.media.Content> content = requestBodyAnnValue.getAnnotation("content", io.swagger.v3.oas.annotations.media.Content.class).orElse(null);
if (content != null) {
AnnotationValue<io.swagger.v3.oas.annotations.media.Schema> swaggerSchema = content.getAnnotation("schema", io.swagger.v3.oas.annotations.media.Schema.class).orElse(null);
if (swaggerSchema != null) {
hasSchemaImplementation = swaggerSchema.stringValue("implementation").orElse(null) != null;
}
}

RequestBody requestBody = toValue(requestBodyAnnValue.getValues(), context, RequestBody.class).orElse(null);
// if media type doesn't set in swagger annotation, check micronaut annotation
if (content != null
Expand All @@ -1740,7 +1766,7 @@ private RequestBody readSwaggerRequestBody(Element element, List<MediaType> cons
}
}

return requestBody;
return Pair.of(requestBody, hasSchemaImplementation);
}

private void readServers(MethodElement element, VisitorContext context, io.swagger.v3.oas.models.Operation swaggerOperation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@
import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.getConfigurationProperty;
import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.getExpandableProperties;
import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.resolvePlaceholders;
import static io.micronaut.openapi.visitor.SchemaUtils.EMPTY_SCHEMA;
import static io.micronaut.openapi.visitor.SchemaUtils.TYPE_OBJECT;
import static io.micronaut.openapi.visitor.Utils.resolveComponents;
import static java.util.stream.Collectors.toMap;
Expand All @@ -146,7 +145,6 @@
abstract class AbstractOpenApiVisitor {

private static final Lock VISITED_ELEMENTS_LOCK = new ReentrantLock();
private static final ComposedSchema EMPTY_COMPOSED_SCHEMA = new ComposedSchema();

/**
* Stores relations between schema names and class names.
Expand Down Expand Up @@ -1258,7 +1256,7 @@ protected Schema bindSchemaForElement(VisitorContext context, TypedElement eleme
notOnlyRef = true;
}

boolean addSchemaToBind = !schemaToBind.equals(EMPTY_SCHEMA);
boolean addSchemaToBind = !SchemaUtils.isEmptySchema(schemaToBind);

if (addSchemaToBind) {
if (TYPE_OBJECT.equals(originalSchema.getType())) {
Expand All @@ -1267,7 +1265,7 @@ protected Schema bindSchemaForElement(VisitorContext context, TypedElement eleme
}
originalSchema.setType(null);
}
if (!originalSchema.equals(EMPTY_SCHEMA)) {
if (!SchemaUtils.isEmptySchema(originalSchema)) {
composedSchema.addAllOfItem(originalSchema);
}
} else if (isNullable && CollectionUtils.isEmpty(composedSchema.getAllOf())) {
Expand All @@ -1283,7 +1281,7 @@ protected Schema bindSchemaForElement(VisitorContext context, TypedElement eleme
composedSchema.addAllOfItem(schemaToBind);
}

if (!composedSchema.equals(EMPTY_COMPOSED_SCHEMA)
if (!SchemaUtils.isEmptySchema(composedSchema)
&& ((CollectionUtils.isNotEmpty(composedSchema.getAllOf()) && composedSchema.getAllOf().size() > 1)
|| CollectionUtils.isNotEmpty(composedSchema.getOneOf())
|| CollectionUtils.isNotEmpty(composedSchema.getAnyOf())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,6 @@
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;

import static io.micronaut.openapi.visitor.SchemaUtils.EMPTY_COMPOSED_SCHEMA;
import static io.micronaut.openapi.visitor.SchemaUtils.EMPTY_SCHEMA;
import static io.micronaut.openapi.visitor.SchemaUtils.EMPTY_SIMPLE_SCHEMA;
import static io.micronaut.openapi.visitor.SchemaUtils.TYPE_OBJECT;
import static io.swagger.v3.oas.models.Components.COMPONENTS_SCHEMAS_REF;
Expand Down Expand Up @@ -1450,7 +1448,7 @@ private Schema normalizeSchema(Schema schema) {
}
boolean isSameType = allOfSchema.getType() == null || allOfSchema.getType().equals(type);

if (schema.equals(EMPTY_SCHEMA) || schema.equals(EMPTY_COMPOSED_SCHEMA)
if (SchemaUtils.isEmptySchema(schema)
&& (serializedDefaultValue == null || serializedDefaultValue.equals(serializedAllOfDefaultValue))
&& (type == null || allOfSchema.getType() == null || allOfSchema.getType().equals(type))) {
normalizedSchema = allOfSchema;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,33 @@
*/
package io.micronaut.openapi.visitor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import io.micronaut.core.annotation.Internal;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.BinarySchema;
import io.swagger.v3.oas.models.media.BooleanSchema;
import io.swagger.v3.oas.models.media.ByteArraySchema;
import io.swagger.v3.oas.models.media.ComposedSchema;
import io.swagger.v3.oas.models.media.DateSchema;
import io.swagger.v3.oas.models.media.DateTimeSchema;
import io.swagger.v3.oas.models.media.EmailSchema;
import io.swagger.v3.oas.models.media.FileSchema;
import io.swagger.v3.oas.models.media.IntegerSchema;
import io.swagger.v3.oas.models.media.JsonSchema;
import io.swagger.v3.oas.models.media.MapSchema;
import io.swagger.v3.oas.models.media.NumberSchema;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.PasswordSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.media.UUIDSchema;

import static io.micronaut.openapi.visitor.Utils.resolveComponents;
import static io.swagger.v3.oas.models.Components.COMPONENTS_SCHEMAS_REF;
Expand All @@ -36,14 +54,65 @@
@Internal
public final class SchemaUtils {

public static final String COMPONENTS_CALLBACKS_PREFIX = "#/components/callbacks/";
public static final String COMPONENTS_SCHEMAS_PREFIX = "#/components/schemas/";

public static final Schema<?> EMPTY_SCHEMA = new Schema<>();
public static final Schema<?> EMPTY_SIMPLE_SCHEMA = new SimpleSchema();
public static final Schema<?> EMPTY_ARRAY_SCHEMA = new ArraySchema();
public static final Schema<?> EMPTY_BINARY_SCHEMA = new BinarySchema();
public static final Schema<?> EMPTY_BOOLEAN_SCHEMA = new BooleanSchema();
public static final Schema<?> EMPTY_BYTE_ARRAY_SCHEMA = new ByteArraySchema();
public static final Schema<?> EMPTY_COMPOSED_SCHEMA = new ComposedSchema();
public static final Schema<?> EMPTY_DATE_SCHEMA = new DateSchema();
public static final Schema<?> EMPTY_DATE_TIME_SCHEMA = new DateTimeSchema();
public static final Schema<?> EMPTY_EMAIL_SCHEMA = new EmailSchema();
public static final Schema<?> EMPTY_FILE_SCHEMA = new FileSchema();
public static final Schema<?> EMPTY_INTEGER_SCHEMA = new IntegerSchema();
public static final Schema<?> EMPTY_JSON_SCHEMA = new JsonSchema();
public static final Schema<?> EMPTY_MAP_SCHEMA = new MapSchema();
public static final Schema<?> EMPTY_NUMBER_SCHEMA = new NumberSchema();
public static final Schema<?> EMPTY_OBJECT_SCHEMA = new ObjectSchema();
public static final Schema<?> EMPTY_PASSWORD_SCHEMA = new PasswordSchema();
public static final Schema<?> EMPTY_STRING_SCHEMA = new StringSchema();
public static final Schema<?> EMPTY_UUID_SCHEMA = new UUIDSchema();
public static final Schema<?> EMPTY_SIMPLE_SCHEMA = new SimpleSchema();

public static final String TYPE_OBJECT = "object";

private static final List<Schema<?>> ALL_EMPTY_SCHEMAS;

static {
List<Schema<?>> schemas = new ArrayList<>();
schemas.add(EMPTY_SCHEMA);
schemas.add(EMPTY_ARRAY_SCHEMA);
schemas.add(EMPTY_BINARY_SCHEMA);
schemas.add(EMPTY_BOOLEAN_SCHEMA);
schemas.add(EMPTY_BYTE_ARRAY_SCHEMA);
schemas.add(EMPTY_COMPOSED_SCHEMA);
schemas.add(EMPTY_DATE_SCHEMA);
schemas.add(EMPTY_DATE_TIME_SCHEMA);
schemas.add(EMPTY_EMAIL_SCHEMA);
schemas.add(EMPTY_FILE_SCHEMA);
schemas.add(EMPTY_INTEGER_SCHEMA);
schemas.add(EMPTY_JSON_SCHEMA);
schemas.add(EMPTY_MAP_SCHEMA);
schemas.add(EMPTY_NUMBER_SCHEMA);
schemas.add(EMPTY_OBJECT_SCHEMA);
schemas.add(EMPTY_PASSWORD_SCHEMA);
schemas.add(EMPTY_STRING_SCHEMA);
schemas.add(EMPTY_UUID_SCHEMA);
schemas.add(EMPTY_SIMPLE_SCHEMA);

ALL_EMPTY_SCHEMAS = Collections.unmodifiableList(schemas);
}

private SchemaUtils() {
}

public static boolean isEmptySchema(Schema<?> schema) {
return ALL_EMPTY_SCHEMAS.contains(schema);
}

public static Map<String, Schema> resolveSchemas(OpenAPI openAPI) {
Components components = resolveComponents(openAPI);
Map<String, Schema> schemas = components.getSchemas();
Expand Down
Loading