diff --git a/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/mapper/MapperContext.java b/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/mapper/MapperContext.java index 40297543..172b906a 100644 --- a/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/mapper/MapperContext.java +++ b/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/mapper/MapperContext.java @@ -22,6 +22,7 @@ import de.qaware.openapigeneratorforspring.common.reference.HasReferencedItemConsumer; import de.qaware.openapigeneratorforspring.model.trait.HasContent; +import org.springframework.util.MimeType; import javax.annotation.Nullable; import java.util.Optional; @@ -52,7 +53,7 @@ public interface MapperContext extends HasReferencedItemConsumer { * @param owningType owning type, must extend {@link HasContent HasContent} * @return media types, or empty optional if nothing can be provided for this owning type */ - Optional> findMediaTypes(Class owningType); + Optional> findMimeTypes(Class owningType); /** * Set the owner for any following referenced item. @@ -64,7 +65,7 @@ public interface MapperContext extends HasReferencedItemConsumer { /** * Sets the {@link MediaTypesProvider media types provider} for any - * following calls of {@link #findMediaTypes}. + * following calls of {@link #findMimeTypes}. * * @param mediaTypesProvider media types provider * @return mapper context with modified media types provider diff --git a/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/mapper/MediaTypesProvider.java b/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/mapper/MediaTypesProvider.java index 22f8fbe8..85d4d378 100644 --- a/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/mapper/MediaTypesProvider.java +++ b/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/mapper/MediaTypesProvider.java @@ -21,6 +21,7 @@ package de.qaware.openapigeneratorforspring.common.mapper; import de.qaware.openapigeneratorforspring.model.trait.HasContent; +import org.springframework.util.MimeType; import java.util.Set; @@ -31,5 +32,5 @@ @FunctionalInterface public interface MediaTypesProvider { - Set getMediaTypes(Class owningType); + Set getMimeTypes(Class owningType); } diff --git a/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/ConsumesMimeTypeProvider.java b/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/ConsumesMimeTypeProvider.java new file mode 100644 index 00000000..7a79ff38 --- /dev/null +++ b/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/ConsumesMimeTypeProvider.java @@ -0,0 +1,11 @@ +package de.qaware.openapigeneratorforspring.common.operation.mimetype; + +import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; +import de.qaware.openapigeneratorforspring.common.util.OpenApiOrderedUtils; +import org.springframework.util.MimeType; + +import java.util.Set; + +public interface ConsumesMimeTypeProvider extends OpenApiOrderedUtils.DefaultOrdered { + Set findConsumesMimeTypes(HandlerMethod handlerMethod); +} diff --git a/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/ConsumesMimeTypeProviderStrategy.java b/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/ConsumesMimeTypeProviderStrategy.java new file mode 100644 index 00000000..1f25eb07 --- /dev/null +++ b/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/ConsumesMimeTypeProviderStrategy.java @@ -0,0 +1,10 @@ +package de.qaware.openapigeneratorforspring.common.operation.mimetype; + +import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; +import org.springframework.util.MimeType; + +import java.util.Set; + +public interface ConsumesMimeTypeProviderStrategy { + Set getConsumesMimeTypes(HandlerMethod handlerMethod); +} diff --git a/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/ProducesMimeTypeProvider.java b/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/ProducesMimeTypeProvider.java new file mode 100644 index 00000000..b0eddf56 --- /dev/null +++ b/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/ProducesMimeTypeProvider.java @@ -0,0 +1,11 @@ +package de.qaware.openapigeneratorforspring.common.operation.mimetype; + +import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; +import de.qaware.openapigeneratorforspring.common.util.OpenApiOrderedUtils; +import org.springframework.util.MimeType; + +import java.util.Set; + +public interface ProducesMimeTypeProvider extends OpenApiOrderedUtils.DefaultOrdered { + Set findProducesMimeTypes(HandlerMethod handlerMethod); +} diff --git a/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/ProducesMimeTypeProviderStrategy.java b/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/ProducesMimeTypeProviderStrategy.java new file mode 100644 index 00000000..6eb6c520 --- /dev/null +++ b/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/ProducesMimeTypeProviderStrategy.java @@ -0,0 +1,10 @@ +package de.qaware.openapigeneratorforspring.common.operation.mimetype; + +import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; +import org.springframework.util.MimeType; + +import java.util.Set; + +public interface ProducesMimeTypeProviderStrategy { + Set getProducesMimeTypes(HandlerMethod handlerMethod); +} diff --git a/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/paths/HandlerMethod.java b/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/paths/HandlerMethod.java index f8cfe1a6..027261e5 100644 --- a/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/paths/HandlerMethod.java +++ b/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/paths/HandlerMethod.java @@ -22,7 +22,9 @@ import de.qaware.openapigeneratorforspring.common.annotation.HasAnnotationsSupplier; import de.qaware.openapigeneratorforspring.model.response.ApiResponse; +import org.springframework.core.ResolvableType; import org.springframework.core.annotation.Order; +import org.springframework.util.MimeType; import javax.annotation.Nullable; import java.lang.annotation.Annotation; @@ -105,13 +107,13 @@ interface Parameter extends HasAnnotationsSupplier, HasType, HasContext, interface RequestBody extends HasAnnotationsSupplier, HasType, HasContext, HasCustomize { - Set getConsumesContentTypes(); + Set getConsumesMimeTypes(); } interface Response extends HasType, HasCustomize { String getResponseCode(); - Set getProducesContentTypes(); + Set getProducesMimeTypes(); } /** @@ -264,7 +266,7 @@ interface ContextModifier { * de.qaware.openapigeneratorforspring.common.annotation.AnnotationsSupplier * annotations}. * - *

A type may have its own set of annotations, which are in + *

Each type here may have its own set of annotations, which are in * general different from the element carrying this type. * *

Note that this interface provides exactly @@ -272,7 +274,7 @@ interface ContextModifier { * de.qaware.openapigeneratorforspring.common.schema.resolver.SchemaResolver#resolveFromType schema resolver} */ interface Type extends HasAnnotationsSupplier { - java.lang.reflect.Type getType(); + ResolvableType getType(); } /** diff --git a/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/schema/resolver/SchemaResolver.java b/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/schema/resolver/SchemaResolver.java index 28291e34..2487a6ab 100644 --- a/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/schema/resolver/SchemaResolver.java +++ b/openapi-generator-for-spring-api/src/main/java/de/qaware/openapigeneratorforspring/common/schema/resolver/SchemaResolver.java @@ -37,7 +37,7 @@ public interface SchemaResolver { /** * Resolve from given java type using the given - * annotations supplier. The finally built top-level + * AnnotationsSupplier. The finally built top-level * schema will also be "maybe referenced" if not empty. * * @param caller resolver mode (serialization or deserialization) @@ -56,7 +56,7 @@ public interface SchemaResolver { * @param caller resolver mode (serialization or deserialization) * @param clazz java clazz (Jackson type will be constructed from it) * @param referencedSchemaConsumer referenced schema consumer for nested schemas - * @return resolved schema, might be empty if input is Void.class + * @return resolved schema, might be empty if input is {@code Void.class} */ Schema resolveFromClassWithoutReference(Caller caller, Class clazz, ReferencedSchemaConsumer referencedSchemaConsumer); diff --git a/openapi-generator-for-spring-autoconfigure/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorOperationAutoConfiguration.java b/openapi-generator-for-spring-autoconfigure/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorOperationAutoConfiguration.java index c8225701..fd9b8e18 100644 --- a/openapi-generator-for-spring-autoconfigure/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorOperationAutoConfiguration.java +++ b/openapi-generator-for-spring-autoconfigure/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorOperationAutoConfiguration.java @@ -42,6 +42,10 @@ import de.qaware.openapigeneratorforspring.common.operation.id.DefaultOperationIdProvider; import de.qaware.openapigeneratorforspring.common.operation.id.OperationIdConflictResolver; import de.qaware.openapigeneratorforspring.common.operation.id.OperationIdProvider; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.ConsumesMimeTypeProvider; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.DefaultConsumesMimeTypeProviderStrategy; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.DefaultProducesMimeTypeProviderStrategy; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.ProducesMimeTypeProvider; import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; import de.qaware.openapigeneratorforspring.common.schema.resolver.SchemaResolver; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -129,4 +133,16 @@ public DefaultRequestBodyOperationCustomizer defaultRequestBodyOperationCustomiz ) { return new DefaultRequestBodyOperationCustomizer(requestBodyAnnotationMapper, schemaResolver, handlerMethodRequestBodyMappers); } + + @Bean + @ConditionalOnMissingBean + public DefaultProducesMimeTypeProviderStrategy defaultProducesMimeTypeProviderStrategy(List providers) { + return new DefaultProducesMimeTypeProviderStrategy(providers); + } + + @Bean + @ConditionalOnMissingBean + public DefaultConsumesMimeTypeProviderStrategy defaultConsumesMimeTypeProviderStrategy(List providers) { + return new DefaultConsumesMimeTypeProviderStrategy(providers); + } } diff --git a/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/mapper/DefaultContentAnnotationMapper.java b/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/mapper/DefaultContentAnnotationMapper.java index 99591211..c1eab2a7 100644 --- a/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/mapper/DefaultContentAnnotationMapper.java +++ b/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/mapper/DefaultContentAnnotationMapper.java @@ -31,6 +31,7 @@ import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; +import org.springframework.util.MimeType; import java.util.Arrays; import java.util.Objects; @@ -57,16 +58,16 @@ public Content mapArray(io.swagger.v3.oas.annotations.media.Content[] contentAnn if (StringUtils.isBlank(contentAnnotation.mediaType())) { // if the mapperContext doesn't have any suggested media types, // the mediaTypeValue is discarded! - Set mediaTypes = mapperContext.findMediaTypes(owningType) - .orElseThrow(() -> new IllegalStateException("No media types available in context for " + owningType.getSimpleName() + Set mimeTypes = mapperContext.findMimeTypes(owningType) + .orElseThrow(() -> new IllegalStateException("No mime types available in context for " + owningType.getSimpleName() + " and Content annotation has blank mediaType")); - return mediaTypes.stream().map(mediaType -> Pair.of(mediaType, mediaTypeValue)); + return mimeTypes.stream().map(mimeType -> Pair.of(mimeType, mediaTypeValue)); } - return Stream.of(Pair.of(contentAnnotation.mediaType(), mediaTypeValue)); + return Stream.of(Pair.of(MimeType.valueOf(contentAnnotation.mediaType()), mediaTypeValue)); }) - .collect(Collectors.toMap(Pair::getKey, Pair::getValue, (a, b) -> { + .collect(Collectors.toMap(p -> p.getKey().toString(), Pair::getValue, (a, b) -> { if (!Objects.equals(a, b)) { - throw new IllegalStateException("Conflicting media type found for " + a + " vs. " + b); + throw new IllegalStateException("Conflicting mime type found for " + a + " vs. " + b); } return a; }, Content::new)); diff --git a/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/mapper/MapperContextImpl.java b/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/mapper/MapperContextImpl.java index 91a16d51..9ab88521 100644 --- a/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/mapper/MapperContextImpl.java +++ b/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/mapper/MapperContextImpl.java @@ -26,6 +26,7 @@ import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.With; +import org.springframework.util.MimeType; import javax.annotation.Nullable; import java.util.Optional; @@ -48,8 +49,8 @@ public T getReferencedItemConsumer(Class r } @Override - public Optional> findMediaTypes(Class owningType) { - return Optional.ofNullable(mediaTypesProvider).map(provider -> provider.getMediaTypes(owningType)); + public Optional> findMimeTypes(Class owningType) { + return Optional.ofNullable(mediaTypesProvider).map(provider -> provider.getMimeTypes(owningType)); } @Override diff --git a/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/customizer/DefaultRequestBodyOperationCustomizer.java b/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/customizer/DefaultRequestBodyOperationCustomizer.java index 8fabc3cf..8cb4c86a 100644 --- a/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/customizer/DefaultRequestBodyOperationCustomizer.java +++ b/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/customizer/DefaultRequestBodyOperationCustomizer.java @@ -31,6 +31,7 @@ import de.qaware.openapigeneratorforspring.model.operation.Operation; import de.qaware.openapigeneratorforspring.model.requestbody.RequestBody; import lombok.RequiredArgsConstructor; +import org.springframework.util.MimeType; import javax.annotation.Nullable; import java.util.List; @@ -66,20 +67,20 @@ private RequestBody applyFromMethod(@Nullable RequestBody existingRequestBody, O private RequestBody buildRequestBody(List handlerMethodRequestBodies, @Nullable RequestBody existingRequestBody, OperationBuilderContext operationBuilderContext) { RequestBody requestBody = buildRequestBodyFromSwaggerAnnotations(handlerMethodRequestBodies, existingRequestBody, operationBuilderContext); - handlerMethodRequestBodies.forEach(handlerMethodRequestBodyParameter -> { - for (String contentType : handlerMethodRequestBodyParameter.getConsumesContentTypes()) { - MediaType mediaType = addMediaTypeIfNotPresent(contentType, requestBody); + handlerMethodRequestBodies.forEach(handlerMethodRequestBody -> { + for (MimeType mimeType : handlerMethodRequestBody.getConsumesMimeTypes()) { + MediaType mediaType = addMediaTypeIfNotPresent(mimeType, requestBody); if (mediaType.getSchema() == null) { - handlerMethodRequestBodyParameter.getType().ifPresent(parameterType -> schemaResolver.resolveFromType( + handlerMethodRequestBody.getType().ifPresent(parameterType -> schemaResolver.resolveFromType( REQUEST_BODY, - parameterType.getType(), - handlerMethodRequestBodyParameter.getAnnotationsSupplier().andThen(parameterType.getAnnotationsSupplier()), + parameterType.getType().getType(), + handlerMethodRequestBody.getAnnotationsSupplier().andThen(parameterType.getAnnotationsSupplier()), operationBuilderContext.getReferencedItemConsumer(ReferencedSchemaConsumer.class), mediaType::setSchema )); } } - handlerMethodRequestBodyParameter.customize(requestBody); + handlerMethodRequestBody.customize(requestBody); }); return requestBody; } @@ -99,12 +100,12 @@ private RequestBody buildRequestBodyFromSwaggerAnnotations(List new MediaType()); + return requestBody.getContent().computeIfAbsent(mimeType.toString(), ignored -> new MediaType()); } @Override diff --git a/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/DefaultConsumesMimeTypeProviderStrategy.java b/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/DefaultConsumesMimeTypeProviderStrategy.java new file mode 100644 index 00000000..65f95e05 --- /dev/null +++ b/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/DefaultConsumesMimeTypeProviderStrategy.java @@ -0,0 +1,24 @@ +package de.qaware.openapigeneratorforspring.common.operation.mimetype; + +import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; +import lombok.RequiredArgsConstructor; +import org.springframework.util.MimeType; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +@RequiredArgsConstructor +public class DefaultConsumesMimeTypeProviderStrategy implements ConsumesMimeTypeProviderStrategy { + + private final List providers; + + @Override + public Set getConsumesMimeTypes(HandlerMethod handlerMethod) { + return providers.stream() + .map(provider -> provider.findConsumesMimeTypes(handlerMethod)) + .filter(mimeTypes -> !mimeTypes.isEmpty()) + .findFirst() + .orElseGet(Collections::emptySet); + } +} diff --git a/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/DefaultProducesMimeTypeProviderStrategy.java b/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/DefaultProducesMimeTypeProviderStrategy.java new file mode 100644 index 00000000..225383cb --- /dev/null +++ b/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/DefaultProducesMimeTypeProviderStrategy.java @@ -0,0 +1,27 @@ +package de.qaware.openapigeneratorforspring.common.operation.mimetype; + +import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; +import lombok.RequiredArgsConstructor; +import org.springframework.util.MimeType; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +public class DefaultProducesMimeTypeProviderStrategy implements ProducesMimeTypeProviderStrategy { + + private final List providers; + + @Override + public Set getProducesMimeTypes(HandlerMethod handlerMethod) { + return providers.stream() + .map(provider -> provider.findProducesMimeTypes(handlerMethod)) + // Produces mime types end up as response body keys in model and must be concrete (aka no wildcards!) + .map(mimeTypes -> mimeTypes.stream().filter(MimeType::isConcrete).collect(Collectors.toSet())) + .filter(mimeTypes -> !mimeTypes.isEmpty()) + .findFirst() + .orElseGet(Collections::emptySet); + } +} diff --git a/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/parameter/customizer/DefaultOperationParameterSchemaCustomizer.java b/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/parameter/customizer/DefaultOperationParameterSchemaCustomizer.java index a368a018..faa01eaa 100644 --- a/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/parameter/customizer/DefaultOperationParameterSchemaCustomizer.java +++ b/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/parameter/customizer/DefaultOperationParameterSchemaCustomizer.java @@ -39,7 +39,7 @@ public void customize(Parameter parameter, OperationParameterCustomizerContext c ReferencedSchemaConsumer referencedSchemaConsumer = context.getReferencedItemConsumer(ReferencedSchemaConsumer.class); AnnotationsSupplier annotationsSupplier = handlerMethodParameter.getAnnotationsSupplier() .andThen(parameterType.getAnnotationsSupplier()); - schemaResolver.resolveFromType(PARAMETER, parameterType.getType(), annotationsSupplier, referencedSchemaConsumer, parameter::setSchema); + schemaResolver.resolveFromType(PARAMETER, parameterType.getType().getType(), annotationsSupplier, referencedSchemaConsumer, parameter::setSchema); }) ); } diff --git a/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/response/DefaultOperationApiResponsesFromMethodCustomizer.java b/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/response/DefaultOperationApiResponsesFromMethodCustomizer.java index 25cdc9f4..d527bb47 100644 --- a/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/response/DefaultOperationApiResponsesFromMethodCustomizer.java +++ b/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/operation/response/DefaultOperationApiResponsesFromMethodCustomizer.java @@ -69,8 +69,8 @@ public void customize(ApiResponses apiResponses, OperationBuilderContext operati } private void addMediaTypesToApiResponseContent(Content content, HandlerMethod handlerMethod, HandlerMethod.Response handlerMethodResponse, ReferencedSchemaConsumer referencedSchemaConsumer) { - handlerMethodResponse.getProducesContentTypes().forEach(contentType -> { - MediaType mediaType = content.getOrDefault(contentType, new MediaType()); + handlerMethodResponse.getProducesMimeTypes().forEach(mimeType -> { + MediaType mediaType = content.getOrDefault(mimeType.toString(), new MediaType()); // we might have already set some media type, only amend this if the schema is not present // this gives annotations a higher preference than the schema inferred from the method return type if (mediaType.getSchema() == null) { @@ -78,7 +78,7 @@ private void addMediaTypesToApiResponseContent(Content content, HandlerMethod ha AnnotationsSupplier annotationsSupplier = responseType.getAnnotationsSupplier() // restrict searching the annotations from the handler method to @Schema or @ArraySchema only // this prevents things like @Deprecated on the method to make the response schema also deprecated - // but we can still use @Schema to modify properties of the "default" response + // but, we can still use @Schema to modify properties of the "default" response .andThen(new AnnotationsSupplier() { @Override public Stream findAnnotations(Class annotationType) { @@ -88,14 +88,14 @@ public Stream findAnnotations(Class annotationType) return Stream.empty(); } }); - schemaResolver.resolveFromType(API_RESPONSE, responseType.getType(), annotationsSupplier, + schemaResolver.resolveFromType(API_RESPONSE, responseType.getType().getType(), annotationsSupplier, referencedSchemaConsumer, mediaType::setSchema ); }); // putting empty media types is ok // when there are multiple content types present for one response code - content.put(contentType, mediaType); + content.put(mimeType.toString(), mediaType); } }); } diff --git a/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/supplier/DefaultOpenApiObjectMapperSupplier.java b/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/supplier/DefaultOpenApiObjectMapperSupplier.java index 4d26911f..dca4580f 100644 --- a/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/supplier/DefaultOpenApiObjectMapperSupplier.java +++ b/openapi-generator-for-spring-common/src/main/java/de/qaware/openapigeneratorforspring/common/supplier/DefaultOpenApiObjectMapperSupplier.java @@ -28,7 +28,7 @@ import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; public class DefaultOpenApiObjectMapperSupplier implements OpenApiObjectMapperSupplier { - // Maybe the "auto-configured" object mapper from spring would works better? + // Maybe the autoconfigured object mapper from spring would work better? // Allow customizations? // See GH Issue #7 private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() diff --git a/openapi-generator-for-spring-test/src/test/java/de/qaware/openapigeneratorforspring/test/app55/App55.java b/openapi-generator-for-spring-test/src/test/java/de/qaware/openapigeneratorforspring/test/app55/App55.java new file mode 100644 index 00000000..3b2a2167 --- /dev/null +++ b/openapi-generator-for-spring-test/src/test/java/de/qaware/openapigeneratorforspring/test/app55/App55.java @@ -0,0 +1,11 @@ +package de.qaware.openapigeneratorforspring.test.app55; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +class App55 { + public static void main(String[] args) { + SpringApplication.run(App55.class, args); + } +} diff --git a/openapi-generator-for-spring-test/src/test/java/de/qaware/openapigeneratorforspring/test/app55/App55Controller.java b/openapi-generator-for-spring-test/src/test/java/de/qaware/openapigeneratorforspring/test/app55/App55Controller.java new file mode 100644 index 00000000..c326b87d --- /dev/null +++ b/openapi-generator-for-spring-test/src/test/java/de/qaware/openapigeneratorforspring/test/app55/App55Controller.java @@ -0,0 +1,19 @@ +package de.qaware.openapigeneratorforspring.test.app55; + +import lombok.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class App55Controller { + + @GetMapping + public SomeDto getJson() { + return new SomeDto("some-value"); + } + + @Value + private static class SomeDto { + String someProperty; + } +} diff --git a/openapi-generator-for-spring-test/src/test/java/de/qaware/openapigeneratorforspring/test/app55/App55Test.java b/openapi-generator-for-spring-test/src/test/java/de/qaware/openapigeneratorforspring/test/app55/App55Test.java new file mode 100644 index 00000000..44993c78 --- /dev/null +++ b/openapi-generator-for-spring-test/src/test/java/de/qaware/openapigeneratorforspring/test/app55/App55Test.java @@ -0,0 +1,7 @@ +package de.qaware.openapigeneratorforspring.test.app55; + +import de.qaware.openapigeneratorforspring.test.AbstractOpenApiGeneratorWebMvcIntTest; + +class App55Test extends AbstractOpenApiGeneratorWebMvcIntTest { + +} diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app17.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app17.json index 5f480ed5..34a0ffe7 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app17.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app17.json @@ -10,7 +10,12 @@ "operationId": "mapping1", "requestBody": { "content": { - "*/*": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/nullable_App17Controller.SomeDto" + } + }, + "application/*+json": { "schema": { "$ref": "#/components/schemas/nullable_App17Controller.SomeDto" } @@ -22,7 +27,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/array_App17Controller.SomeDto" } @@ -37,7 +42,12 @@ "operationId": "mapping2", "requestBody": { "content": { - "*/*": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/array_App17Controller.SomeDto" + } + }, + "application/*+json": { "schema": { "$ref": "#/components/schemas/array_App17Controller.SomeDto" } @@ -68,7 +78,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/App17Controller.SomeDto" } diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app2.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app2.json index c9c30cab..ae6534a7 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app2.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app2.json @@ -97,8 +97,8 @@ "description": "external doc description", "url": "http://some-url", "x-extension1": { - "property2": "value2", - "property1": "value1" + "property1": "value1", + "property2": "value2" } }, "operationId": "getMappingOperation3", @@ -130,14 +130,14 @@ ], "default": "A", "x-extension2": { - "property4": "value4", - "property3": "value3" + "property3": "value3", + "property4": "value4" } } }, "x-extension3": { - "property6": "value6", - "property5": "value5" + "property5": "value5", + "property6": "value6" } } ] @@ -189,7 +189,12 @@ "200": { "description": "Default response", "content": { - "*/*": { + "text/plain": { + "schema": { + "type": "string" + } + }, + "application/json": { "schema": { "type": "string" } diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app30.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app30.json index 49c1798b..a254a0bf 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app30.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app30.json @@ -59,12 +59,12 @@ "200": { "description": "Default response", "content": { + "application/json": {}, "text/html": { "schema": { "type": "string" } - }, - "*/*": {} + } } } } @@ -73,7 +73,6 @@ "operationId": "mapping3_post_textHtml", "requestBody": { "content": { - "*/*": {}, "text/html": { "schema": { "type": "string" diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app31.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app31.json index 37f76320..d3a23198 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app31.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app31.json @@ -39,7 +39,6 @@ "requestBody": { "description": "request body description", "content": { - "*/*": {}, "text/html": { "schema": { "type": "string" @@ -56,7 +55,7 @@ "type": "string" } }, - "*/*": {} + "application/json": {} } }, "500": { @@ -67,7 +66,7 @@ "$ref": "#/components/schemas/App31Controller.ErrorDto" } }, - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/App31Controller.ErrorDto" } diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app33.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app33.json index d6d497b4..4ca71273 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app33.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app33.json @@ -27,7 +27,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/App33Controller.SomeDto" } @@ -54,7 +54,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/App33Controller.SomeReturnEnum" } diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app34.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app34.json index c32c749b..775185fc 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app34.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app34.json @@ -12,7 +12,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/App34Controller.SomeListDto" } @@ -29,7 +29,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/App34Controller.SomeDto" } diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app4.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app4.json index da2a88cb..23886926 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app4.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app4.json @@ -142,6 +142,9 @@ } }, "text/plain": { + "schema": { + "type": "string" + }, "examples": { "MyExampleName": { "summary": "summary example2", @@ -152,11 +155,6 @@ "externalValue": "http://some-url2" } } - }, - "*/*": { - "schema": { - "type": "string" - } } }, "links": { @@ -200,11 +198,11 @@ "x-extension-api-response-1": { "name1": "value2" }, + "x-nameless-property-1": "value2", "x-nameless-property-2": { "key1": "value1", "key2": "value2" - }, - "x-nameless-property-1": "value2" + } } } } @@ -230,6 +228,9 @@ }, "content": { "application/json": { + "schema": { + "type": "string" + }, "encoding": { "propertyInSchema2": { "contentType": "text/plain", @@ -244,7 +245,7 @@ } } }, - "*/*": { + "text/plain": { "schema": { "type": "string" } diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app41.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app41.json index 6fa7be98..a5d44007 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app41.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app41.json @@ -10,7 +10,12 @@ "operationId": "mapping1", "requestBody": { "content": { - "*/*": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/App41Controller.SomeType1_1" + } + }, + "application/*+json": { "schema": { "$ref": "#/components/schemas/App41Controller.SomeType1_1" } @@ -22,7 +27,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/App41Controller.SomeType1_0" } @@ -35,7 +40,12 @@ "operationId": "mapping2", "requestBody": { "content": { - "*/*": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/App41Controller.SomeType2" + } + }, + "application/*+json": { "schema": { "$ref": "#/components/schemas/App41Controller.SomeType2" } diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app42.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app42.json index cac369b8..dc8b48ce 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app42.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app42.json @@ -12,7 +12,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/App42Controller.SomeType1" } diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app43.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app43.json index d48e8dcc..2419e6ff 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app43.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app43.json @@ -12,7 +12,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/App43Controller.Shape" } diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app44.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app44.json index 73396be4..6202b75f 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app44.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app44.json @@ -12,7 +12,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "type": "array", "items": { diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app45.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app45.json index 91f3eed2..b8345461 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app45.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app45.json @@ -22,7 +22,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/App45Controller.Base1" } diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app47.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app47.json index 23ac9a01..74fbef89 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app47.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app47.json @@ -32,7 +32,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/array_string_1" } @@ -69,7 +69,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "type": "array", "description": "Description for array", @@ -91,7 +91,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "maxItems": 5, "minItems": 2, @@ -116,7 +116,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/array_string_0" } diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app49.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app49.json index 71c95f29..31ea35aa 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app49.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app49.json @@ -36,7 +36,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "type": "array", "items": { diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app5.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app5.json index ac1d2df0..f006dae0 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app5.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app5.json @@ -50,7 +50,14 @@ "operationId": "getMappingWithOptionalString", "responses": { "200": { - "$ref": "#/components/responses/200_0" + "description": "Default response", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } } } } @@ -60,14 +67,7 @@ "operationId": "getMappingReturnsObject", "responses": { "200": { - "description": "Default response", - "content": { - "*/*": { - "schema": { - "type": "object" - } - } - } + "description": "Default response" } } } @@ -89,7 +89,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/array_App5Controller.SimpleDto" } @@ -106,7 +106,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/App5Controller.DtoInOptional" } @@ -132,7 +132,12 @@ "200_0": { "description": "Default response", "content": { - "*/*": { + "text/plain": { + "schema": { + "type": "string" + } + }, + "application/json": { "schema": { "type": "string" } @@ -142,7 +147,7 @@ "200_1": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/App5Controller.ComplexDto_2" } @@ -152,7 +157,7 @@ "200_2": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/App5Controller.SimpleDto" } diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app50.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app50.json index a48a683c..10db576f 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app50.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app50.json @@ -86,7 +86,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/array_App50Controller.UserId" } diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app52.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app52.json index 931e3848..23c6adaf 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app52.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app52.json @@ -12,7 +12,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/App52Controller.DtoWithMap" } @@ -29,7 +29,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/MapOfObject" } diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app54.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app54.json index ac1152cb..bfaf53b8 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app54.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app54.json @@ -12,7 +12,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/App54Controller.Animal" } diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app55.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app55.json new file mode 100644 index 00000000..ad0a9c75 --- /dev/null +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app55.json @@ -0,0 +1,38 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "API for App55", + "version": "unknown" + }, + "paths": { + "/": { + "get": { + "operationId": "getJson", + "responses": { + "200": { + "description": "Default response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/App55Controller.SomeDto" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "App55Controller.SomeDto": { + "type": "object", + "properties": { + "someProperty": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app7.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app7.json index 2a7b39ce..c08706c5 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app7.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app7.json @@ -22,7 +22,12 @@ "200": { "description": "Default response", "content": { - "*/*": { + "text/plain": { + "schema": { + "type": "string" + } + }, + "application/json": { "schema": { "type": "string" } @@ -60,4 +65,4 @@ } } } -} +} \ No newline at end of file diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app8.json b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app8.json index e298d99a..1079c55d 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiJson/app8.json +++ b/openapi-generator-for-spring-test/src/test/resources/openApiJson/app8.json @@ -42,7 +42,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/responseEntitySchema" } @@ -58,7 +58,7 @@ "200": { "description": "Default response", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/string_date-time" } diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiYaml/app36.yaml b/openapi-generator-for-spring-test/src/test/resources/openApiYaml/app36.yaml index 03a84d3e..bcc2da45 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiYaml/app36.yaml +++ b/openapi-generator-for-spring-test/src/test/resources/openApiYaml/app36.yaml @@ -14,14 +14,26 @@ paths: type: string requestBody: content: + text/plain: + schema: + type: string '*/*': schema: type: string + application/json: + schema: + type: string + application/*+json: + schema: + type: string required: true responses: "200": description: Default response content: - '*/*': + text/plain: + schema: + type: string + application/json: schema: type: string \ No newline at end of file diff --git a/openapi-generator-for-spring-test/src/test/resources/openApiYaml/app38.yaml b/openapi-generator-for-spring-test/src/test/resources/openApiYaml/app38.yaml index a6b059ac..7165bc5b 100644 --- a/openapi-generator-for-spring-test/src/test/resources/openApiYaml/app38.yaml +++ b/openapi-generator-for-spring-test/src/test/resources/openApiYaml/app38.yaml @@ -17,11 +17,17 @@ paths: '*/*': schema: type: string + text/plain;charset=UTF-8: + schema: + type: string required: true responses: "200": description: Default response content: - '*/*': + text/plain;charset=UTF-8: + schema: + type: string + text/event-stream: schema: type: string \ No newline at end of file diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebAutoConfiguration.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebAutoConfiguration.java index b8a8f51a..14928dc4 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebAutoConfiguration.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebAutoConfiguration.java @@ -23,6 +23,7 @@ import de.qaware.openapigeneratorforspring.common.OpenApiConfigurationProperties; import de.qaware.openapigeneratorforspring.common.OpenApiGenerator; import de.qaware.openapigeneratorforspring.common.annotation.AnnotationsSupplierFactory; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.SpringWebRequestMappingAnnotationMimeTypesProvider; import de.qaware.openapigeneratorforspring.common.operation.parameter.DefaultOpenApiSpringWebParameterNameDiscoverer; import de.qaware.openapigeneratorforspring.common.operation.parameter.OpenApiSpringWebParameterNameDiscoverer; import de.qaware.openapigeneratorforspring.common.operation.parameter.converter.DefaultParameterMethodConverterFromMatrixVariableAnnotation; @@ -110,4 +111,10 @@ public SpringWebResponseEntityInitialTypeBuilder springWebResponseEntityInitialT public SpringWebRequestMethodEnumMapper springWebRequestMethodEnumMapper() { return new SpringWebRequestMethodEnumMapper(); } + + @Bean + @ConditionalOnMissingBean + public SpringWebRequestMappingAnnotationMimeTypesProvider springWebRequestMappingAnnotationMimeTypesProvider() { + return new SpringWebRequestMappingAnnotationMimeTypesProvider(); + } } diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebMethodAutoConfiguration.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebMethodAutoConfiguration.java index 2c52f679..117a9bfe 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebMethodAutoConfiguration.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebMethodAutoConfiguration.java @@ -21,9 +21,10 @@ package de.qaware.openapigeneratorforspring.autoconfigure; import de.qaware.openapigeneratorforspring.common.annotation.AnnotationsSupplierFactory; -import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodContentTypesMapper; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.ConsumesMimeTypeProviderStrategy; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.ProducesMimeTypeProviderStrategy; import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodMapper; -import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodRequestBodyParameterMapper; +import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodRequestBodyParameterProvider; import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodResponseCodeMapper; import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodReturnTypeMapper; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -35,33 +36,36 @@ public class OpenApiGeneratorWebMethodAutoConfiguration { @Bean @ConditionalOnMissingBean - public SpringWebHandlerMethodMapper.ContextModifierMapper springWebHandlerMethodContextAwareMapper(SpringWebHandlerMethodContentTypesMapper springWebHandlerMethodContentTypesMapper) { - return new SpringWebHandlerMethodMapper.ContextModifierMapper(springWebHandlerMethodContentTypesMapper); + public SpringWebHandlerMethodMapper.ContextModifierMapper springWebHandlerMethodContextAwareMapper( + ConsumesMimeTypeProviderStrategy consumesMimeTypeProviderStrategy, + ProducesMimeTypeProviderStrategy producesMimeTypeProviderStrategy + ) { + return new SpringWebHandlerMethodMapper.ContextModifierMapper(consumesMimeTypeProviderStrategy, producesMimeTypeProviderStrategy); } @Bean @ConditionalOnMissingBean public SpringWebHandlerMethodMapper.RequestBodyMapper springWebHandlerMethodRequestBodyMapper( - SpringWebHandlerMethodContentTypesMapper springWebHandlerMethodContentTypesMapper, - SpringWebHandlerMethodRequestBodyParameterMapper springWebHandlerMethodRequestBodyParameterMapper + ConsumesMimeTypeProviderStrategy consumesMimeTypeProviderStrategy, + SpringWebHandlerMethodRequestBodyParameterProvider springWebHandlerMethodRequestBodyParameterProvider ) { - return new SpringWebHandlerMethodMapper.RequestBodyMapper(springWebHandlerMethodContentTypesMapper, springWebHandlerMethodRequestBodyParameterMapper); + return new SpringWebHandlerMethodMapper.RequestBodyMapper(consumesMimeTypeProviderStrategy, springWebHandlerMethodRequestBodyParameterProvider); } @Bean @ConditionalOnMissingBean public SpringWebHandlerMethodMapper.ResponseMapper springWebHandlerMethodResponseMapper( - SpringWebHandlerMethodContentTypesMapper springWebHandlerMethodContentTypesMapper, + ProducesMimeTypeProviderStrategy producesMimeTypeProviderStrategy, SpringWebHandlerMethodResponseCodeMapper springWebHandlerMethodResponseCodeMapper, SpringWebHandlerMethodReturnTypeMapper springWebHandlerMethodReturnTypeMapper ) { - return new SpringWebHandlerMethodMapper.ResponseMapper(springWebHandlerMethodContentTypesMapper, springWebHandlerMethodResponseCodeMapper, springWebHandlerMethodReturnTypeMapper); + return new SpringWebHandlerMethodMapper.ResponseMapper(producesMimeTypeProviderStrategy, springWebHandlerMethodResponseCodeMapper, springWebHandlerMethodReturnTypeMapper); } @Bean @ConditionalOnMissingBean - public SpringWebHandlerMethodRequestBodyParameterMapper springWebHandlerMethodRequestBodyParameterMapper() { - return new SpringWebHandlerMethodRequestBodyParameterMapper(); + public SpringWebHandlerMethodRequestBodyParameterProvider springWebHandlerMethodRequestBodyParameterProvider() { + return new SpringWebHandlerMethodRequestBodyParameterProvider(); } @Bean @@ -75,10 +79,4 @@ public SpringWebHandlerMethodReturnTypeMapper springWebHandlerMethodReturnTypeMa public SpringWebHandlerMethodResponseCodeMapper springWebHandlerMethodResponseCodeMapper() { return new SpringWebHandlerMethodResponseCodeMapper(); } - - @Bean - @ConditionalOnMissingBean - public SpringWebHandlerMethodContentTypesMapper springWebHandlerMethodContentTypesMapper() { - return new SpringWebHandlerMethodContentTypesMapper(); - } } diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebMethodMergerAutoConfiguration.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebMethodMergerAutoConfiguration.java index 2e45ecee..c450970a 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebMethodMergerAutoConfiguration.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebMethodMergerAutoConfiguration.java @@ -20,8 +20,9 @@ package de.qaware.openapigeneratorforspring.autoconfigure; -import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodContentTypesMapper; -import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodRequestBodyParameterMapper; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.ConsumesMimeTypeProviderStrategy; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.ProducesMimeTypeProviderStrategy; +import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodRequestBodyParameterProvider; import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodResponseCodeMapper; import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodReturnTypeMapper; import de.qaware.openapigeneratorforspring.common.paths.method.merger.MergedSpringWebHandlerMethodMapper; @@ -83,23 +84,23 @@ public SpringWebHandlerMethodParameterMerger springWebHandlerMethodParameterMerg @ConditionalOnMissingBean public SpringWebHandlerMethodRequestBodyMerger springWebHandlerMethodRequestBodyMerger( SpringWebHandlerMethodTypeMerger springWebHandlerMethodTypeMerger, - SpringWebHandlerMethodContentTypesMapper springWebHandlerMethodContentTypesMapper, - SpringWebHandlerMethodRequestBodyParameterMapper springWebHandlerMethodRequestBodyParameterMapper + ConsumesMimeTypeProviderStrategy consumesMimeTypeProviderStrategy, + SpringWebHandlerMethodRequestBodyParameterProvider springWebHandlerMethodRequestBodyParameterProvider ) { - return new SpringWebHandlerMethodRequestBodyMerger(springWebHandlerMethodTypeMerger, springWebHandlerMethodContentTypesMapper, - springWebHandlerMethodRequestBodyParameterMapper); + return new SpringWebHandlerMethodRequestBodyMerger(springWebHandlerMethodTypeMerger, consumesMimeTypeProviderStrategy, + springWebHandlerMethodRequestBodyParameterProvider); } @Bean @ConditionalOnMissingBean public SpringWebHandlerMethodResponseMerger springWebHandlerMethodResponseMerger( SpringWebHandlerMethodTypeMerger springWebHandlerMethodTypeMerger, - SpringWebHandlerMethodContentTypesMapper springWebHandlerMethodContentTypesMapper, + ProducesMimeTypeProviderStrategy producesMimeTypeProviderStrategy, SpringWebHandlerMethodResponseCodeMapper springWebHandlerMethodResponseCodeMapper, SpringWebHandlerMethodReturnTypeMapper springWebHandlerMethodReturnTypeMapper ) { - return new SpringWebHandlerMethodResponseMerger(springWebHandlerMethodTypeMerger, springWebHandlerMethodContentTypesMapper, + return new SpringWebHandlerMethodResponseMerger(springWebHandlerMethodTypeMerger, producesMimeTypeProviderStrategy, springWebHandlerMethodResponseCodeMapper, springWebHandlerMethodReturnTypeMapper); } } diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodContentTypesMapper.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/SpringWebRequestMappingAnnotationMimeTypesProvider.java similarity index 58% rename from openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodContentTypesMapper.java rename to openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/SpringWebRequestMappingAnnotationMimeTypesProvider.java index dce13f1b..39c14966 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodContentTypesMapper.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/operation/mimetype/SpringWebRequestMappingAnnotationMimeTypesProvider.java @@ -18,10 +18,12 @@ * #L% */ -package de.qaware.openapigeneratorforspring.common.paths.method; +package de.qaware.openapigeneratorforspring.common.operation.mimetype; import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; +import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; +import org.springframework.util.MimeType; import org.springframework.web.bind.annotation.RequestMapping; import java.util.LinkedHashSet; @@ -30,29 +32,38 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static java.util.Collections.singleton; -import static org.springframework.http.MediaType.ALL_VALUE; +import static de.qaware.openapigeneratorforspring.common.util.OpenApiOrderedUtils.laterThan; -public class SpringWebHandlerMethodContentTypesMapper { - public static final Set SINGLE_ALL_VALUE = singleton(ALL_VALUE); +@RequiredArgsConstructor +public class SpringWebRequestMappingAnnotationMimeTypesProvider implements ConsumesMimeTypeProvider, ProducesMimeTypeProvider { + // give user-provided providers (if any) precedence by default + public static final int ORDER = laterThan(DEFAULT_ORDER); - public Set findConsumesContentTypes(SpringWebHandlerMethod handlerMethod) { + @Override + public Set findConsumesMimeTypes(HandlerMethod handlerMethod) { return fromRequestMappingAnnotation(handlerMethod, RequestMapping::consumes); } - public Set findProducesContentTypes(SpringWebHandlerMethod handlerMethod) { + @Override + public Set findProducesMimeTypes(HandlerMethod handlerMethod) { return fromRequestMappingAnnotation(handlerMethod, RequestMapping::produces); } - private static Set fromRequestMappingAnnotation(HandlerMethod handlerMethod, Function annotationMapper) { + private static Set fromRequestMappingAnnotation(HandlerMethod handlerMethod, Function annotationMapper) { return handlerMethod.findAnnotations(RequestMapping.class) .map(annotationMapper) .filter(contentTypes -> !StringUtils.isAllBlank(contentTypes)) // Spring doc says the first one should win, - // ie. annotation on class level is overridden by method level + // i.e. annotation on class level is overridden by method level, there is no merging! .findFirst() .map(Stream::of) - .orElse(Stream.of(ALL_VALUE)) + .orElseGet(Stream::empty) + .map(MimeType::valueOf) .collect(Collectors.toCollection(LinkedHashSet::new)); } + + @Override + public int getOrder() { + return ORDER; + } } diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/DefaultSpringWebHandlerMethodBuilder.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/DefaultSpringWebHandlerMethodBuilder.java index 6f416a83..d83f31eb 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/DefaultSpringWebHandlerMethodBuilder.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/DefaultSpringWebHandlerMethodBuilder.java @@ -23,11 +23,13 @@ import de.qaware.openapigeneratorforspring.common.annotation.AnnotationsSupplier; import de.qaware.openapigeneratorforspring.common.annotation.AnnotationsSupplierFactory; import de.qaware.openapigeneratorforspring.common.operation.parameter.OpenApiSpringWebParameterNameDiscoverer; -import de.qaware.openapigeneratorforspring.common.paths.method.AbstractSpringWebHandlerMethod; +import de.qaware.openapigeneratorforspring.common.paths.method.AbstractSpringWebHandlerMethod.SpringWebParameter; +import de.qaware.openapigeneratorforspring.common.paths.method.AbstractSpringWebHandlerMethod.SpringWebType; import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethod; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; +import org.springframework.core.ResolvableType; import javax.annotation.Nullable; import java.lang.annotation.Annotation; @@ -56,7 +58,7 @@ public HandlerMethod build(org.springframework.web.method.HandlerMethod springWe ); } - private List buildParameters(org.springframework.web.method.HandlerMethod springWebHandlerMethod) { + private List buildParameters(org.springframework.web.method.HandlerMethod springWebHandlerMethod) { MethodParameter[] methodParameters = springWebHandlerMethod.getMethodParameters(); IntFunction parameterNameSupplier = buildParameterNameSupplier(springWebHandlerMethod); return IntStream.range(0, methodParameters.length).boxed() @@ -78,12 +80,15 @@ private IntFunction buildParameterNameSupplier(org.springframework.web.m return parameterIndex -> null; } - private AbstractSpringWebHandlerMethod.SpringWebParameter buildParameter(MethodParameter parameter, @Nullable String parameterName) { - return new AbstractSpringWebHandlerMethod.SpringWebParameter( + private SpringWebParameter buildParameter(MethodParameter parameter, @Nullable String parameterName) { + return new SpringWebParameter( // simply using parameter.getParameterName() does not work because // we don't want to set a ParameterNameDiscoverer, which is not thread-safe (see parameter.getParameterName())! parameterName, - AbstractSpringWebHandlerMethod.SpringWebType.of(parameter.getGenericParameterType(), annotationsSupplierFactory.createFromAnnotatedElement(parameter.getParameterType())), + SpringWebType.of( + ResolvableType.forMethodParameter(parameter), + annotationsSupplierFactory.createFromAnnotatedElement(parameter.getParameterType()) + ), new AnnotationsSupplier() { @Override public Stream findAnnotations(Class annotationType) { diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/AbstractSpringWebHandlerMethod.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/AbstractSpringWebHandlerMethod.java index 33bdfad0..86262645 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/AbstractSpringWebHandlerMethod.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/AbstractSpringWebHandlerMethod.java @@ -29,23 +29,35 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.ToString; +import org.springframework.core.ResolvableType; +import org.springframework.util.MimeType; import javax.annotation.Nullable; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; @RequiredArgsConstructor(access = AccessLevel.PROTECTED) public abstract class AbstractSpringWebHandlerMethod implements HandlerMethod { - @Getter - private final List parameters; + private final List parameters; + + @Override + public List getParameters() { + return new ArrayList<>(parameters); + } + + public List getSpringWebParameters() { + return parameters; + } @RequiredArgsConstructor(staticName = "of") @Getter @ToString public static class SpringWebType implements HandlerMethod.Type { - private final java.lang.reflect.Type type; + private final ResolvableType type; @ToString.Exclude private final AnnotationsSupplier annotationsSupplier; } @@ -54,7 +66,8 @@ public static class SpringWebType implements HandlerMethod.Type { public static class SpringWebParameter implements Parameter { @Nullable private final String parameterName; - private final Type parameterType; + @Getter + private final SpringWebType parameterType; @Getter private final AnnotationsSupplier annotationsSupplier; @@ -74,7 +87,7 @@ public Optional getType() { @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public static class SpringWebRequestBody implements RequestBody { private final AnnotationsSupplier annotationsSupplier; - private final Set consumesContentTypes; + private final Set consumesMimeTypes; private final Optional type; @Nullable private final Boolean required; @@ -92,11 +105,11 @@ public void customize(de.qaware.openapigeneratorforspring.model.requestbody.Requ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public static class SpringWebResponse implements Response { private final String responseCode; - private final Set producesContentTypes; + private final Set producesMimeTypes; private final Optional type; public boolean shouldClearContent(Content content) { - return producesContentTypes.equals(content.keySet()); + return producesMimeTypes.stream().map(MimeType::toString).collect(Collectors.toSet()).equals(content.keySet()); } @Override diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethod.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethod.java index fc5d06aa..5509156c 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethod.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethod.java @@ -39,7 +39,7 @@ public class SpringWebHandlerMethod extends AbstractSpringWebHandlerMethod imple private final org.springframework.web.method.HandlerMethod method; private final AnnotationsSupplier annotationsSupplier; - public SpringWebHandlerMethod(AnnotationsSupplier annotationsSupplier, List parameters, org.springframework.web.method.HandlerMethod method) { + public SpringWebHandlerMethod(AnnotationsSupplier annotationsSupplier, List parameters, org.springframework.web.method.HandlerMethod method) { super(parameters); this.method = method; this.annotationsSupplier = annotationsSupplier; diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodMapper.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodMapper.java index 1a281b70..eaddc5d2 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodMapper.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodMapper.java @@ -22,8 +22,10 @@ import de.qaware.openapigeneratorforspring.common.mapper.MapperContext; import de.qaware.openapigeneratorforspring.common.mapper.MediaTypesProvider; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.ConsumesMimeTypeProviderStrategy; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.ProducesMimeTypeProviderStrategy; import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; -import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodRequestBodyParameterMapper.RequestBodyParameter; +import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodRequestBodyParameterProvider.RequestBodyParameter; import de.qaware.openapigeneratorforspring.model.requestbody.RequestBody; import de.qaware.openapigeneratorforspring.model.response.ApiResponse; import lombok.AccessLevel; @@ -41,7 +43,8 @@ public class SpringWebHandlerMethodMapper { @RequiredArgsConstructor public static class ContextModifierMapper implements HandlerMethod.ContextModifierMapper { - private final SpringWebHandlerMethodContentTypesMapper contentTypesMapper; + private final ConsumesMimeTypeProviderStrategy consumesMimeTypeProviderStrategy; + private final ProducesMimeTypeProviderStrategy producesMimeTypeProviderStrategy; @Nullable @Override @@ -50,9 +53,9 @@ public HandlerMethod.ContextModifier map(@Nullable HandlerMethod. SpringWebHandlerMethod springWebHandlerMethod = (SpringWebHandlerMethod) context; MediaTypesProvider mediaTypesProvider = owningType -> { if (RequestBody.class.equals(owningType)) { - return contentTypesMapper.findConsumesContentTypes(springWebHandlerMethod); + return consumesMimeTypeProviderStrategy.getConsumesMimeTypes(springWebHandlerMethod); } else if (ApiResponse.class.equals(owningType)) { - return contentTypesMapper.findProducesContentTypes(springWebHandlerMethod); + return producesMimeTypeProviderStrategy.getProducesMimeTypes(springWebHandlerMethod); } throw new IllegalStateException("Cannot provide media types for " + owningType.getSimpleName()); }; @@ -65,15 +68,15 @@ public HandlerMethod.ContextModifier map(@Nullable HandlerMethod. @RequiredArgsConstructor public static class RequestBodyMapper implements HandlerMethod.RequestBodyMapper { - private final SpringWebHandlerMethodContentTypesMapper contentTypesMapper; - private final SpringWebHandlerMethodRequestBodyParameterMapper requestBodyParameterMapper; + private final ConsumesMimeTypeProviderStrategy consumesMimeTypeProviderStrategy; + private final SpringWebHandlerMethodRequestBodyParameterProvider requestBodyParameterProvider; @Nullable @Override public List map(HandlerMethod handlerMethod) { if (handlerMethod instanceof SpringWebHandlerMethod) { SpringWebHandlerMethod springWebHandlerMethod = (SpringWebHandlerMethod) handlerMethod; - return requestBodyParameterMapper.findRequestBodyParameter(springWebHandlerMethod) + return requestBodyParameterProvider.findRequestBodyParameter(springWebHandlerMethod) .map(requestBodyParameter -> buildSpringWebRequestBody(requestBodyParameter, springWebHandlerMethod)) .map(HandlerMethod.RequestBody.class::cast) .map(Collections::singletonList) @@ -85,7 +88,7 @@ public List map(HandlerMethod handlerMethod) { private AbstractSpringWebHandlerMethod.SpringWebRequestBody buildSpringWebRequestBody(RequestBodyParameter requestBodyParameter, SpringWebHandlerMethod handlerMethod) { return new AbstractSpringWebHandlerMethod.SpringWebRequestBody( requestBodyParameter.getParameter().getAnnotationsSupplier(), - contentTypesMapper.findConsumesContentTypes(handlerMethod), + consumesMimeTypeProviderStrategy.getConsumesMimeTypes(handlerMethod), requestBodyParameter.getParameter().getType(), requestBodyParameter.isRequired() ) { @@ -100,7 +103,7 @@ public HandlerMethod.Context getContext() { @RequiredArgsConstructor public static class ResponseMapper implements HandlerMethod.ResponseMapper { - private final SpringWebHandlerMethodContentTypesMapper contentTypesMapper; + private final ProducesMimeTypeProviderStrategy producesMimeTypeProviderStrategy; private final SpringWebHandlerMethodResponseCodeMapper responseCodeMapper; private final SpringWebHandlerMethodReturnTypeMapper returnTypeMapper; @@ -111,7 +114,7 @@ public List map(HandlerMethod handlerMethod) { SpringWebHandlerMethod springWebHandlerMethod = (SpringWebHandlerMethod) handlerMethod; return Collections.singletonList(new AbstractSpringWebHandlerMethod.SpringWebResponse( responseCodeMapper.getResponseCode(springWebHandlerMethod), - contentTypesMapper.findProducesContentTypes(springWebHandlerMethod), + producesMimeTypeProviderStrategy.getProducesMimeTypes(handlerMethod), Optional.of(returnTypeMapper.getReturnType(springWebHandlerMethod)) )); } diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodRequestBodyParameterMapper.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodRequestBodyParameterProvider.java similarity index 79% rename from openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodRequestBodyParameterMapper.java rename to openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodRequestBodyParameterProvider.java index 598cae59..f2bcbdc5 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodRequestBodyParameterMapper.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodRequestBodyParameterProvider.java @@ -20,7 +20,7 @@ package de.qaware.openapigeneratorforspring.common.paths.method; -import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; +import de.qaware.openapigeneratorforspring.common.paths.method.AbstractSpringWebHandlerMethod.SpringWebParameter; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -32,20 +32,19 @@ import java.util.stream.Stream; @Slf4j -public class SpringWebHandlerMethodRequestBodyParameterMapper { +public class SpringWebHandlerMethodRequestBodyParameterProvider { public Optional findRequestBodyParameter(SpringWebHandlerMethod handlerMethod) { - return handlerMethod.getParameters().stream() + return handlerMethod.getSpringWebParameters().stream() .flatMap(parameter -> Stream.concat( parameter.getAnnotationsSupplier() .findAnnotations(RequestBody.class) - .map(RequestBody::required) - .map(requiredFlag -> RequestBodyParameter.of(parameter, requiredFlag)), + .map(requestBodyAnnotation -> RequestBodyParameter.of(parameter, requestBodyAnnotation.required())), // also check if we're encountering a "bare" InputStream as a parameter // this can also be seen as request body parameter.getType() - .filter(type -> type.getType().equals(InputStream.class)) + .filter(type -> InputStream.class.isAssignableFrom(type.getType().toClass())) .map(type -> RequestBodyParameter.of(parameter, false)) .map(Stream::of).orElseGet(Stream::empty) ) @@ -58,7 +57,7 @@ public Optional findRequestBodyParameter(SpringWebHandlerM @RequiredArgsConstructor(staticName = "of", access = AccessLevel.PRIVATE) @Getter public static class RequestBodyParameter { - private final HandlerMethod.Parameter parameter; + private final SpringWebParameter parameter; private final boolean required; } } diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodReturnTypeMapper.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodReturnTypeMapper.java index 9b8c809e..0ac885b2 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodReturnTypeMapper.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/SpringWebHandlerMethodReturnTypeMapper.java @@ -21,20 +21,19 @@ package de.qaware.openapigeneratorforspring.common.paths.method; import de.qaware.openapigeneratorforspring.common.annotation.AnnotationsSupplierFactory; -import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; +import de.qaware.openapigeneratorforspring.common.paths.method.AbstractSpringWebHandlerMethod.SpringWebType; import lombok.RequiredArgsConstructor; - -import java.lang.reflect.Method; +import org.springframework.core.ResolvableType; +import org.springframework.web.method.HandlerMethod; @RequiredArgsConstructor public class SpringWebHandlerMethodReturnTypeMapper { private final AnnotationsSupplierFactory annotationsSupplierFactory; - public HandlerMethod.Type getReturnType(SpringWebHandlerMethod springWebHandlerMethod) { - Method method = springWebHandlerMethod.getMethod().getMethod(); + public SpringWebType getReturnType(SpringWebHandlerMethod springWebHandlerMethod) { + HandlerMethod method = springWebHandlerMethod.getMethod(); // even for Void method return type, there might still be @Schema annotation which could be useful - // using method.getReturnType() does not work for generic return types - return AbstractSpringWebHandlerMethod.SpringWebType.of(method.getGenericReturnType(), annotationsSupplierFactory.createFromAnnotatedElement(method.getReturnType())); + return SpringWebType.of(ResolvableType.forMethodParameter(method.getReturnType()), annotationsSupplierFactory.createFromAnnotatedElement(method.getReturnType().getParameterType())); } } diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/MergedSpringWebHandlerMethod.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/MergedSpringWebHandlerMethod.java index ae8d1637..34dcaca7 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/MergedSpringWebHandlerMethod.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/MergedSpringWebHandlerMethod.java @@ -35,7 +35,7 @@ public class MergedSpringWebHandlerMethod extends AbstractSpringWebHandlerMethod private final String identifier; private final List handlerMethods; - public MergedSpringWebHandlerMethod(List parameters, String identifier, List handlerMethods) { + public MergedSpringWebHandlerMethod(List parameters, String identifier, List handlerMethods) { super(parameters); this.identifier = identifier; this.handlerMethods = handlerMethods; diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/MergedSpringWebHandlerMethodContext.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/MergedSpringWebHandlerMethodContext.java index 70916b16..4777fab0 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/MergedSpringWebHandlerMethodContext.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/MergedSpringWebHandlerMethodContext.java @@ -23,11 +23,12 @@ import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.springframework.util.MimeType; import java.util.Set; @RequiredArgsConstructor(staticName = "of") @Getter public class MergedSpringWebHandlerMethodContext implements HandlerMethod.Context { - private final Set consumesContentTypes; + private final Set consumesMimeTypes; } diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/MergedSpringWebHandlerMethodMapper.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/MergedSpringWebHandlerMethodMapper.java index 923c92fa..cc558dfe 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/MergedSpringWebHandlerMethodMapper.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/MergedSpringWebHandlerMethodMapper.java @@ -42,7 +42,7 @@ public HandlerMethod.ContextModifier map(@Nullable HandlerMethod. MergedSpringWebHandlerMethodContext mergedHandlerMethodContext = (MergedSpringWebHandlerMethodContext) context; MediaTypesProvider mediaTypesProvider = owningType -> { if (RequestBody.class.equals(owningType)) { - return mergedHandlerMethodContext.getConsumesContentTypes(); + return mergedHandlerMethodContext.getConsumesMimeTypes(); } throw new IllegalStateException("Cannot provide media types for " + owningType.getSimpleName() + " in merged method"); }; diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/SpringWebHandlerMethodParameterMerger.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/SpringWebHandlerMethodParameterMerger.java index 1b3d4133..40c334c7 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/SpringWebHandlerMethodParameterMerger.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/SpringWebHandlerMethodParameterMerger.java @@ -32,6 +32,7 @@ import lombok.val; import org.apache.commons.lang3.tuple.Pair; +import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.function.Consumer; @@ -43,9 +44,9 @@ public class SpringWebHandlerMethodParameterMerger { private final SpringWebHandlerMethodTypeMerger springWebHandlerMethodTypeMerger; - public List mergeParameters(List handlerMethods) { + public List mergeParameters(List handlerMethods) { return handlerMethods.stream() - .flatMap(handlerMethod -> handlerMethod.getParameters().stream() + .flatMap(handlerMethod -> handlerMethod.getSpringWebParameters().stream() .map(parameter -> buildExtendedParameterPair(handlerMethod, parameter)) ) .collect(OpenApiStreamUtils.groupingByPairKeyAndCollectingValuesToList()) @@ -57,7 +58,7 @@ public List mergeParameters(List { if (!handlerMethodsForParameters.containsAll(handlerMethods)) { parameter.setRequired(false); // otherwise, the Swagger UI doesn't work @@ -75,13 +76,13 @@ public List mergeParameters(List, ExtendedParameter> buildExtendedParameterPair(SpringWebHandlerMethod handlerMethod, HandlerMethod.Parameter parameter) { + private Pair, ExtendedParameter> buildExtendedParameterPair(SpringWebHandlerMethod handlerMethod, SpringWebParameter parameter) { return Pair.of(parameter.getName(), ExtendedParameter.of(parameter, handlerMethod)); } - private HandlerMethod.Parameter buildMergedParameter(String parameterName, List parameters, Consumer parameterCustomizer) { + private SpringWebParameter buildMergedParameter(String parameterName, List parameters, Consumer parameterCustomizer) { val mergedAnnotationsSupplier = AnnotationsSupplier.merge(parameters.stream()); - val mergedType = springWebHandlerMethodTypeMerger.mergeTypes(parameters.stream()) + val mergedType = springWebHandlerMethodTypeMerger.mergeTypes(parameters.stream().map(SpringWebParameter::getParameterType)) .orElseThrow(() -> new IllegalStateException("Grouped parameters should contain at least one entry")); return new SpringWebParameter(parameterName, mergedType, mergedAnnotationsSupplier) { @Override @@ -94,7 +95,7 @@ public void customize(Parameter parameter) { @RequiredArgsConstructor(staticName = "of") @Getter private static class ExtendedParameter { - private final HandlerMethod.Parameter parameter; + private final SpringWebParameter parameter; private final HandlerMethod owningHandlerMethod; } } diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/SpringWebHandlerMethodRequestBodyMerger.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/SpringWebHandlerMethodRequestBodyMerger.java index 98f2594e..a8e3ffd6 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/SpringWebHandlerMethodRequestBodyMerger.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/SpringWebHandlerMethodRequestBodyMerger.java @@ -21,25 +21,27 @@ package de.qaware.openapigeneratorforspring.common.paths.method.merger; import de.qaware.openapigeneratorforspring.common.annotation.AnnotationsSupplier; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.ConsumesMimeTypeProviderStrategy; import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; +import de.qaware.openapigeneratorforspring.common.paths.method.AbstractSpringWebHandlerMethod.SpringWebParameter; import de.qaware.openapigeneratorforspring.common.paths.method.AbstractSpringWebHandlerMethod.SpringWebRequestBody; import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethod; -import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodContentTypesMapper; -import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodRequestBodyParameterMapper; -import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodRequestBodyParameterMapper.RequestBodyParameter; +import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodRequestBodyParameterProvider; +import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodRequestBodyParameterProvider.RequestBodyParameter; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.tuple.Pair; +import org.springframework.util.MimeType; import javax.annotation.Nullable; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import static de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodContentTypesMapper.SINGLE_ALL_VALUE; import static de.qaware.openapigeneratorforspring.common.util.OpenApiStreamUtils.groupingByPairKeyAndCollectingValuesToList; import static java.util.Collections.emptyList; @@ -47,20 +49,23 @@ public class SpringWebHandlerMethodRequestBodyMerger { private final SpringWebHandlerMethodTypeMerger typeMerger; - private final SpringWebHandlerMethodContentTypesMapper contentTypesMapper; - private final SpringWebHandlerMethodRequestBodyParameterMapper requestBodyParameterMapper; + private final ConsumesMimeTypeProviderStrategy consumesMimeTypeProviderStrategy; + private final SpringWebHandlerMethodRequestBodyParameterProvider requestBodyParameterProvider; public List mergeRequestBodies(List handlerMethods) { - // group request bodies by (unique) list of consumesContentTypes + // group request bodies by set of consumesContentTypes // this way, we can detect best how to build the final request body list - Map, List>> groupedRequestBodyParameters = handlerMethods.stream() - .map(handlerMethod -> Pair.of(contentTypesMapper.findConsumesContentTypes(handlerMethod), requestBodyParameterMapper.findRequestBodyParameter(handlerMethod))) + Map, List>> groupedRequestBodyParameters = handlerMethods.stream() + .map(handlerMethod -> { + Optional requestBodyParameter = requestBodyParameterProvider.findRequestBodyParameter(handlerMethod); + return Pair.of(consumesMimeTypeProviderStrategy.getConsumesMimeTypes(handlerMethod), requestBodyParameter); + }) .collect(groupingByPairKeyAndCollectingValuesToList()); - List> allValueRequestBodyParameters = groupedRequestBodyParameters.get(SINGLE_ALL_VALUE); + List> emptyRequestBodyParameters = groupedRequestBodyParameters.get(Collections.emptySet()); if (groupedRequestBodyParameters.size() == 1 - && allValueRequestBodyParameters != null && allValueRequestBodyParameters.stream().noneMatch(Optional::isPresent)) { - // we can omit the request bodies entirely if there's only empty request bodies matching for ALL_VALUE + && emptyRequestBodyParameters != null && emptyRequestBodyParameters.stream().noneMatch(Optional::isPresent)) { + // we can omit the request bodies entirely if there are no request body parameters without any consumesContentTypes return emptyList(); } @@ -70,12 +75,12 @@ public List mergeRequestBodies(List { - Set consumesContentTypes = entry.getKey(); + Set consumesMimeTypes = entry.getKey(); List requestBodyParameters = entry.getValue().stream() .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); - return typeMerger.mergeTypes(requestBodyParameters.stream().map(RequestBodyParameter::getParameter)) + return typeMerger.mergeTypes(requestBodyParameters.stream().map(RequestBodyParameter::getParameter).map(SpringWebParameter::getParameterType)) .map(parameterType -> { boolean required = requestBodyParameters.stream() .map(RequestBodyParameter::isRequired) @@ -83,20 +88,20 @@ public List mergeRequestBodies(List new EmptyRequestBody(consumesContentTypes)); + .orElseGet(() -> new EmptyRequestBody(consumesMimeTypes)); }) .collect(Collectors.toList()); } @@ -104,7 +109,7 @@ public HandlerMethod.Context getContext() { @RequiredArgsConstructor private static class EmptyRequestBody implements HandlerMethod.RequestBody { @Getter - private final Set consumesContentTypes; + private final Set consumesMimeTypes; @Override public Optional getType() { diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/SpringWebHandlerMethodResponseMerger.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/SpringWebHandlerMethodResponseMerger.java index f256434c..ae2d853e 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/SpringWebHandlerMethodResponseMerger.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/SpringWebHandlerMethodResponseMerger.java @@ -20,15 +20,16 @@ package de.qaware.openapigeneratorforspring.common.paths.method.merger; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.ProducesMimeTypeProviderStrategy; import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; import de.qaware.openapigeneratorforspring.common.paths.method.AbstractSpringWebHandlerMethod; import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethod; -import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodContentTypesMapper; import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodResponseCodeMapper; import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodReturnTypeMapper; import de.qaware.openapigeneratorforspring.model.media.Content; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.tuple.Pair; +import org.springframework.util.MimeType; import java.util.ArrayList; import java.util.List; @@ -43,14 +44,14 @@ public class SpringWebHandlerMethodResponseMerger { private final SpringWebHandlerMethodTypeMerger typeMerger; - private final SpringWebHandlerMethodContentTypesMapper contentTypesMapper; + private final ProducesMimeTypeProviderStrategy producesMimeTypeProviderStrategy; private final SpringWebHandlerMethodResponseCodeMapper responseCodeMapper; private final SpringWebHandlerMethodReturnTypeMapper returnTypeMapper; public List mergeResponses(List handlerMethods) { List mergedResponses = new ArrayList<>(); - Map, List>> groupedHandlerMethods = handlerMethods.stream() - .map(method -> Pair.of(responseCodeMapper.getResponseCode(method), Pair.of(contentTypesMapper.findProducesContentTypes(method), method))) + Map, List>> groupedHandlerMethods = handlerMethods.stream() + .map(method -> Pair.of(responseCodeMapper.getResponseCode(method), Pair.of(producesMimeTypeProviderStrategy.getProducesMimeTypes(method), method))) .collect(groupingByPairKeyAndCollectingValuesTo(groupingByPairKeyAndCollectingValuesToList())); groupedHandlerMethods.forEach((responseCode, typesGroupedByProduces) -> @@ -75,6 +76,7 @@ public boolean shouldClearContent(Content content) { private Optional mergeReturnTypes(List handlerMethods) { return handlerMethods.stream() .map(returnTypeMapper::getReturnType) - .reduce(typeMerger::mergeType); + .reduce(typeMerger::mergeType) + .map(x -> x); } } diff --git a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/SpringWebHandlerMethodTypeMerger.java b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/SpringWebHandlerMethodTypeMerger.java index 7283f3db..25c05a26 100644 --- a/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/SpringWebHandlerMethodTypeMerger.java +++ b/openapi-generator-for-spring-web/src/main/java/de/qaware/openapigeneratorforspring/common/paths/method/merger/SpringWebHandlerMethodTypeMerger.java @@ -22,36 +22,33 @@ import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; -import de.qaware.openapigeneratorforspring.common.paths.method.AbstractSpringWebHandlerMethod; +import de.qaware.openapigeneratorforspring.common.paths.method.AbstractSpringWebHandlerMethod.SpringWebType; +import org.springframework.core.ResolvableType; -import java.lang.reflect.Type; import java.util.Optional; import java.util.stream.Stream; public class SpringWebHandlerMethodTypeMerger { - public Optional mergeTypes(Stream hasTypes) { - return hasTypes - // flatMap might be an alternative to throwing exception - .map(hasType -> hasType.getType().orElseThrow(() -> new IllegalStateException("No type present on " + hasType.getClass().getSimpleName()))) - .reduce(this::mergeType); + public Optional mergeTypes(Stream types) { + return types.reduce(this::mergeType); } - public HandlerMethod.Type mergeType(HandlerMethod.Type a, HandlerMethod.Type b) { + public SpringWebType mergeType(SpringWebType a, SpringWebType b) { if (isNotVoid(a) && isNotVoid(b) && !a.getType().equals(b.getType())) { throw new IllegalStateException("Cannot merge conflicting types: " + a + " vs. " + b); } - return AbstractSpringWebHandlerMethod.SpringWebType.of( + return SpringWebType.of( chooseParameterType(a, b), a.getAnnotationsSupplier().andThen(b.getAnnotationsSupplier()) ); } - private static Type chooseParameterType(HandlerMethod.Type a, HandlerMethod.Type b) { + private static ResolvableType chooseParameterType(HandlerMethod.Type a, HandlerMethod.Type b) { return isNotVoid(a) ? a.getType() : b.getType(); } private static boolean isNotVoid(HandlerMethod.Type type) { - return !void.class.equals(type.getType()) && !Void.class.equals(type.getType()); + return !void.class.equals(type.getType().getType()) && !Void.class.equals(type.getType().getType()); } } diff --git a/openapi-generator-for-spring-web/src/test/java/de/qaware/openapigeneratorforspring/common/paths/DefaultSpringWebHandlerMethodBuilderTest.java b/openapi-generator-for-spring-web/src/test/java/de/qaware/openapigeneratorforspring/common/paths/DefaultSpringWebHandlerMethodBuilderTest.java index b35d7ea4..3a1212c8 100644 --- a/openapi-generator-for-spring-web/src/test/java/de/qaware/openapigeneratorforspring/common/paths/DefaultSpringWebHandlerMethodBuilderTest.java +++ b/openapi-generator-for-spring-web/src/test/java/de/qaware/openapigeneratorforspring/common/paths/DefaultSpringWebHandlerMethodBuilderTest.java @@ -27,8 +27,6 @@ class DefaultSpringWebHandlerMethodBuilderTest { private OpenApiSpringWebParameterNameDiscoverer parameterNameDiscoverer; @Mock private org.springframework.web.method.HandlerMethod springWebHandlerMethod; - @Mock - private MethodParameter methodParameter1; @BeforeEach @@ -39,7 +37,7 @@ void setUp() { @Test void build_parameterNumberMismatch() { when(parameterNameDiscoverer.getParameterNames(null)).thenReturn(Arrays.asList("parameter1", "parameter2")); - when(springWebHandlerMethod.getMethodParameters()).thenReturn(new MethodParameter[]{methodParameter1}); + when(springWebHandlerMethod.getMethodParameters()).thenReturn(new MethodParameter[]{}); val actual = sut.build(springWebHandlerMethod); diff --git a/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebFluxAutoConfiguration.java b/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebFluxAutoConfiguration.java index 33fb0ee8..ed90c012 100644 --- a/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebFluxAutoConfiguration.java +++ b/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebFluxAutoConfiguration.java @@ -24,11 +24,13 @@ import de.qaware.openapigeneratorforspring.common.paths.HandlerMethodsProvider; import de.qaware.openapigeneratorforspring.common.paths.SpringWebHandlerMethodBuilder; import de.qaware.openapigeneratorforspring.common.paths.SpringWebRequestMethodEnumMapper; +import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodRequestBodyParameterProvider; import de.qaware.openapigeneratorforspring.common.schema.resolver.type.TypeResolverForCollectionLikeTypeSupport; import de.qaware.openapigeneratorforspring.common.schema.resolver.type.TypeResolverForFlux; import de.qaware.openapigeneratorforspring.common.schema.resolver.type.initial.InitialTypeBuilderForMono; import de.qaware.openapigeneratorforspring.common.web.OpenApiResource; import de.qaware.openapigeneratorforspring.webflux.HandlerMethodsProviderForWebFlux; +import de.qaware.openapigeneratorforspring.webflux.HttpMessageConvertersMimeTypesProviderForWebFlux; import de.qaware.openapigeneratorforspring.webflux.OpenApiBaseUriSupplierForWebFlux; import de.qaware.openapigeneratorforspring.webflux.OpenApiRequestAwareProviderForWebFlux; import de.qaware.openapigeneratorforspring.webflux.OpenApiResourceForWebFlux; @@ -39,6 +41,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Import; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.result.method.RequestMappingInfo; import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; @@ -106,4 +109,10 @@ public InitialTypeBuilderForMono defaultInitialTypeBuilderForMono() { public OpenApiBaseUriSupplierForWebFlux openApiBaseUriProviderForWebFlux(OpenApiConfigurationProperties openApiConfigurationProperties) { return new OpenApiBaseUriSupplierForWebFlux(openApiConfigurationProperties); } + + @Bean + @ConditionalOnMissingBean + public HttpMessageConvertersMimeTypesProviderForWebFlux httpMessageConvertersMimeTypesProviderForWebFlux(ServerCodecConfigurer serverCodecConfigurer, SpringWebHandlerMethodRequestBodyParameterProvider springWebHandlerMethodRequestBodyParameterProvider) { + return new HttpMessageConvertersMimeTypesProviderForWebFlux(serverCodecConfigurer.getReaders(), serverCodecConfigurer.getWriters(), springWebHandlerMethodRequestBodyParameterProvider); + } } diff --git a/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/webflux/HttpMessageConvertersMimeTypesProviderForWebFlux.java b/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/webflux/HttpMessageConvertersMimeTypesProviderForWebFlux.java new file mode 100644 index 00000000..8affaf81 --- /dev/null +++ b/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/webflux/HttpMessageConvertersMimeTypesProviderForWebFlux.java @@ -0,0 +1,108 @@ +/*- + * #%L + * OpenAPI Generator for Spring Boot :: Web + * %% + * Copyright (C) 2020 QAware GmbH + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +package de.qaware.openapigeneratorforspring.webflux; + +import de.qaware.openapigeneratorforspring.common.annotation.AnnotationsSupplier; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.ConsumesMimeTypeProvider; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.ProducesMimeTypeProvider; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.SpringWebRequestMappingAnnotationMimeTypesProvider; +import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; +import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethod; +import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodRequestBodyParameterProvider; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.RequiredArgsConstructor; +import org.springframework.core.ResolvableType; +import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.util.MimeType; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import static de.qaware.openapigeneratorforspring.common.util.OpenApiOrderedUtils.laterThan; + +@RequiredArgsConstructor +public class HttpMessageConvertersMimeTypesProviderForWebFlux implements ConsumesMimeTypeProvider, ProducesMimeTypeProvider { + // give explicit annotations precedence before falling back to consulting readers/writers + public static final int ORDER = laterThan(SpringWebRequestMappingAnnotationMimeTypesProvider.ORDER); + + private final List> httpMessageReaders; + private final List> httpMessageWriters; + private final SpringWebHandlerMethodRequestBodyParameterProvider requestBodyParameterProvider; + + + @Override + public Set findConsumesMimeTypes(HandlerMethod handlerMethod) { + if (handlerMethod instanceof SpringWebHandlerMethod) { + SpringWebHandlerMethod springWebHandlerMethod = (SpringWebHandlerMethod) handlerMethod; + return requestBodyParameterProvider.findRequestBodyParameter(springWebHandlerMethod) + .flatMap(requestBodyParameter -> findResolvableTypeFromSchemaAnnotationOrGet( + requestBodyParameter.getParameter().getAnnotationsSupplier(), + () -> requestBodyParameter.getParameter().getParameterType().getType() + )).map(resolvableType -> httpMessageReaders.stream() + .filter(reader -> reader.canRead(resolvableType, null)) + .flatMap(reader -> reader.getReadableMediaTypes(resolvableType).stream()) + .collect(Collectors.toSet()) + ) + .orElseGet(Collections::emptySet); + } + return Collections.emptySet(); + } + + @Override + public Set findProducesMimeTypes(HandlerMethod handlerMethod) { + return findResolvableTypeFromSchemaAnnotationOrGet(handlerMethod::findAnnotations, + () -> Optional.of(handlerMethod) + .filter(SpringWebHandlerMethod.class::isInstance) + .map(SpringWebHandlerMethod.class::cast) + .map(SpringWebHandlerMethod::getMethod) + .filter(method -> !method.isVoid()) + .map(org.springframework.web.method.HandlerMethod::getReturnType) + .map(ResolvableType::forMethodParameter) + .orElse(null) + ).map(resolvableType -> httpMessageWriters.stream() + .filter(decoder -> decoder.canWrite(resolvableType, null)) + .flatMap(decoder -> decoder.getWritableMediaTypes(resolvableType).stream()) + .collect(Collectors.toSet()) + ).orElseGet(Collections::emptySet); + } + + private Optional findResolvableTypeFromSchemaAnnotationOrGet(AnnotationsSupplier annotationsSupplier, Supplier supplier) { + Optional typeFromSchemaAnnotation = annotationsSupplier.findAnnotations(Schema.class) + .>map(Schema::implementation) + .filter(clazz -> !Void.class.equals(clazz)) + .findFirst() + .map(ResolvableType::forClass); + if (typeFromSchemaAnnotation.isPresent()) { + return typeFromSchemaAnnotation; + } + return Optional.ofNullable(supplier.get()); + } + + @Override + public int getOrder() { + return ORDER; + } +} diff --git a/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/webflux/function/RouterFunctionAnalysis.java b/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/webflux/function/RouterFunctionAnalysis.java index 39472753..543200bd 100644 --- a/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/webflux/function/RouterFunctionAnalysis.java +++ b/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/webflux/function/RouterFunctionAnalysis.java @@ -28,6 +28,7 @@ import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.util.MimeType; import org.springframework.web.reactive.function.server.HandlerFunction; import org.springframework.web.reactive.function.server.RequestPredicate; import org.springframework.web.reactive.function.server.RequestPredicates; @@ -113,10 +114,10 @@ public void header(String name, String value) { // so it wants to consume what we produce if (HttpHeaders.ACCEPT.equals(name)) { // see also org.springframework.web.reactive.function.server.RequestPredicates.AcceptPredicate - result.producesContentTypesFromHeader.add(value); + result.producesMimeTypesFromHeader.add(MimeType.valueOf(value)); } else if (HttpHeaders.CONTENT_TYPE.equals(name)) { // see also org.springframework.web.reactive.function.server.RequestPredicates.ContentTypePredicate - result.consumesContentTypesFromHeader.add(value); + result.consumesMimeTypesFromHeader.add(MimeType.valueOf(value)); } else { result.parameters.add(new RouterFunctionHandlerMethod.Parameter(name, ParameterIn.HEADER)); } @@ -178,7 +179,7 @@ static class Result { private final Set requestMethods = new LinkedHashSet<>(); private final Set paths = new LinkedHashSet<>(); private final List parameters = new ArrayList<>(); - private final Set consumesContentTypesFromHeader = new LinkedHashSet<>(); - private final Set producesContentTypesFromHeader = new LinkedHashSet<>(); + private final Set consumesMimeTypesFromHeader = new LinkedHashSet<>(); + private final Set producesMimeTypesFromHeader = new LinkedHashSet<>(); } } diff --git a/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/webflux/function/RouterFunctionHandlerMethod.java b/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/webflux/function/RouterFunctionHandlerMethod.java index 2f2e7d97..2aa899e2 100644 --- a/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/webflux/function/RouterFunctionHandlerMethod.java +++ b/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/webflux/function/RouterFunctionHandlerMethod.java @@ -27,6 +27,8 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.ToString; +import org.springframework.core.ResolvableType; +import org.springframework.util.MimeType; import org.springframework.web.reactive.function.server.RouterFunction; import java.lang.annotation.Annotation; @@ -61,7 +63,7 @@ public ContextAwareAnnotations findAnnotationsWithCont @Getter static class RouterFunctionType implements HandlerMethod.Type { private final AnnotationsSupplier annotationsSupplier; - private final java.lang.reflect.Type type; + private final ResolvableType type; } @RequiredArgsConstructor @@ -90,7 +92,7 @@ public Optional getType() { @Getter static class Response implements HandlerMethod.Response { private final String responseCode; - private final Set producesContentTypes; + private final Set producesMimeTypes; private final RouterFunctionType routerFunctionType; @Override diff --git a/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/webflux/function/RouterFunctionHandlerMethodMapper.java b/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/webflux/function/RouterFunctionHandlerMethodMapper.java index fa2d1e23..7c36b644 100644 --- a/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/webflux/function/RouterFunctionHandlerMethodMapper.java +++ b/openapi-generator-for-spring-webflux/src/main/java/de/qaware/openapigeneratorforspring/webflux/function/RouterFunctionHandlerMethodMapper.java @@ -24,6 +24,7 @@ import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; import lombok.AccessLevel; import lombok.NoArgsConstructor; +import org.springframework.core.ResolvableType; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @@ -52,10 +53,10 @@ public List map(HandlerMethod handlerMethod) { RouterFunctionHandlerMethod.Response response = new RouterFunctionHandlerMethod.Response( // see GH Issue #4 findResponseCode(handlerMethod), - routerFunctionHandlerMethod.getRouterFunctionAnalysisResult().getProducesContentTypesFromHeader(), + routerFunctionHandlerMethod.getRouterFunctionAnalysisResult().getProducesMimeTypesFromHeader(), // Schema building could still use @Schema annotation from bean factory method, // so supply a "dummy" type here for schema building - new RouterFunctionHandlerMethod.RouterFunctionType(AnnotationsSupplier.EMPTY, Void.class) + new RouterFunctionHandlerMethod.RouterFunctionType(AnnotationsSupplier.EMPTY, ResolvableType.forClass(Void.class)) ); return Collections.singletonList(response); } diff --git a/openapi-generator-for-spring-webmvc/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebMvcAutoConfiguration.java b/openapi-generator-for-spring-webmvc/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebMvcAutoConfiguration.java index eecf0e6f..5c3413fb 100644 --- a/openapi-generator-for-spring-webmvc/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebMvcAutoConfiguration.java +++ b/openapi-generator-for-spring-webmvc/src/main/java/de/qaware/openapigeneratorforspring/autoconfigure/OpenApiGeneratorWebMvcAutoConfiguration.java @@ -24,13 +24,16 @@ import de.qaware.openapigeneratorforspring.common.paths.HandlerMethodsProvider; import de.qaware.openapigeneratorforspring.common.paths.SpringWebHandlerMethodBuilder; import de.qaware.openapigeneratorforspring.common.paths.SpringWebRequestMethodEnumMapper; +import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodRequestBodyParameterProvider; import de.qaware.openapigeneratorforspring.common.web.OpenApiResource; import de.qaware.openapigeneratorforspring.webmvc.HandlerMethodPathPatternsProviderForWebMvc; import de.qaware.openapigeneratorforspring.webmvc.HandlerMethodsProviderForWebMvc; +import de.qaware.openapigeneratorforspring.webmvc.HttpMessageConvertersMimeTypesProviderForWebMvc; import de.qaware.openapigeneratorforspring.webmvc.OpenApiRequestAwareSupplierForWebMvc; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.web.context.request.WebRequest; @@ -87,4 +90,13 @@ public OpenApiRequestAwareSupplierForWebMvc openApiRequestAwareProviderForWebMvc ) { return new OpenApiRequestAwareSupplierForWebMvc(webRequest, httpServletRequest); } + + @Bean + @ConditionalOnMissingBean + public HttpMessageConvertersMimeTypesProviderForWebMvc springBootHttpMessageConvertersMimeTypesProvider( + HttpMessageConverters httpMessageConverters, + SpringWebHandlerMethodRequestBodyParameterProvider springWebHandlerMethodRequestBodyParameterProvider + ) { + return new HttpMessageConvertersMimeTypesProviderForWebMvc(httpMessageConverters, springWebHandlerMethodRequestBodyParameterProvider); + } } diff --git a/openapi-generator-for-spring-webmvc/src/main/java/de/qaware/openapigeneratorforspring/webmvc/HttpMessageConvertersMimeTypesProviderForWebMvc.java b/openapi-generator-for-spring-webmvc/src/main/java/de/qaware/openapigeneratorforspring/webmvc/HttpMessageConvertersMimeTypesProviderForWebMvc.java new file mode 100644 index 00000000..b5b75a89 --- /dev/null +++ b/openapi-generator-for-spring-webmvc/src/main/java/de/qaware/openapigeneratorforspring/webmvc/HttpMessageConvertersMimeTypesProviderForWebMvc.java @@ -0,0 +1,142 @@ +/*- + * #%L + * OpenAPI Generator for Spring Boot :: Web + * %% + * Copyright (C) 2020 QAware GmbH + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +package de.qaware.openapigeneratorforspring.webmvc; + +import de.qaware.openapigeneratorforspring.common.annotation.AnnotationsSupplier; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.ConsumesMimeTypeProvider; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.ProducesMimeTypeProvider; +import de.qaware.openapigeneratorforspring.common.operation.mimetype.SpringWebRequestMappingAnnotationMimeTypesProvider; +import de.qaware.openapigeneratorforspring.common.paths.HandlerMethod; +import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethod; +import de.qaware.openapigeneratorforspring.common.paths.method.SpringWebHandlerMethodRequestBodyParameterProvider; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.core.MethodParameter; +import org.springframework.core.ResolvableType; +import org.springframework.http.MediaType; +import org.springframework.http.converter.GenericHttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.util.MimeType; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Predicate; + +import static de.qaware.openapigeneratorforspring.common.util.OpenApiOrderedUtils.laterThan; + +@RequiredArgsConstructor +public class HttpMessageConvertersMimeTypesProviderForWebMvc implements ConsumesMimeTypeProvider, ProducesMimeTypeProvider { + // give explicit annotations precedence before falling back to consulting HttpMessageConverters + public static final int ORDER = laterThan(SpringWebRequestMappingAnnotationMimeTypesProvider.ORDER); + + private final HttpMessageConverters httpMessageConverters; + private final SpringWebHandlerMethodRequestBodyParameterProvider requestBodyParameterProvider; + + @Override + public Set findConsumesMimeTypes(HandlerMethod handlerMethod) { + if (handlerMethod instanceof SpringWebHandlerMethod) { + SpringWebHandlerMethod springWebHandlerMethod = (SpringWebHandlerMethod) handlerMethod; + return requestBodyParameterProvider.findRequestBodyParameter(springWebHandlerMethod) + .map(requestBodyParameter -> findTypeFromSchemaAnnotationImplementation(requestBodyParameter.getParameter().getAnnotationsSupplier()) + .map(schemaImplementationType -> getSupportedMediaTypes(schemaImplementationType, HttpMessageConverterWrapper::canRead)) + .orElseGet(() -> getSupportedMediaTypes(requestBodyParameter.getParameter().getParameterType().getType(), HttpMessageConverterWrapper::canRead)) + ).orElseGet(Collections::emptySet); + } + return Collections.emptySet(); + } + + @Override + public Set findProducesMimeTypes(HandlerMethod handlerMethod) { + return findTypeFromSchemaAnnotationImplementation(handlerMethod::findAnnotations) + .map(schemaImplementationType -> getSupportedMediaTypes(schemaImplementationType, HttpMessageConverterWrapper::canWrite)) + .orElseGet(() -> { + if (handlerMethod instanceof SpringWebHandlerMethod) { + MethodParameter returnType = ((SpringWebHandlerMethod) handlerMethod).getMethod().getReturnType(); + return getSupportedMediaTypes(ResolvableType.forMethodParameter(returnType), HttpMessageConverterWrapper::canWrite); + } + return Collections.emptySet(); + }); + } + + private Set getSupportedMediaTypes(ResolvableType type, BiPredicate canReadOrWrite) { + Set result = new LinkedHashSet<>(); + for (HttpMessageConverter converter : httpMessageConverters) { + HttpMessageConverterWrapper wrapper = HttpMessageConverterWrapper.of(converter); + if (canReadOrWrite.test(wrapper, type)) { + result.addAll(wrapper.getSupportedMediaTypes(type.toClass())); + } + } + return result; + } + + private Optional findTypeFromSchemaAnnotationImplementation(AnnotationsSupplier annotationsSupplier) { + return annotationsSupplier.findAnnotations(Schema.class) + .>map(Schema::implementation) + .filter(clazz -> !Void.class.equals(clazz)) + .findFirst() + .map(ResolvableType::forClass); + } + + @RequiredArgsConstructor + private static class HttpMessageConverterWrapper { + private final Function, List> getSupportedMediaTypes; + private final Predicate canRead; + private final Predicate canWrite; + + static HttpMessageConverterWrapper of(HttpMessageConverter converter) { + if (converter instanceof GenericHttpMessageConverter) { + GenericHttpMessageConverter genericConverter = (GenericHttpMessageConverter) converter; + return new HttpMessageConverterWrapper(converter::getSupportedMediaTypes, + type -> genericConverter.canRead(type.getType(), null, null), + type -> genericConverter.canWrite(type.getType(), type.toClass(), null) + ); + } else { + return new HttpMessageConverterWrapper(converter::getSupportedMediaTypes, + type -> converter.canRead(type.toClass(), null), + type -> converter.canWrite(type.toClass(), null) + ); + } + } + + List getSupportedMediaTypes(Class valueClass) { + return getSupportedMediaTypes.apply(valueClass); + } + + boolean canRead(ResolvableType type) { + return canRead.test(type); + } + + boolean canWrite(ResolvableType type) { + return canWrite.test(type); + } + } + + @Override + public int getOrder() { + return ORDER; + } +}