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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Set<String>> findMediaTypes(Class<? extends HasContent> owningType);
Optional<Set<MimeType>> findMimeTypes(Class<? extends HasContent> owningType);

/**
* Set the owner for any following referenced item.
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -31,5 +32,5 @@
@FunctionalInterface
public interface MediaTypesProvider {

Set<String> getMediaTypes(Class<? extends HasContent> owningType);
Set<MimeType> getMimeTypes(Class<? extends HasContent> owningType);
}
Original file line number Diff line number Diff line change
@@ -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<MimeType> findConsumesMimeTypes(HandlerMethod handlerMethod);
}
Original file line number Diff line number Diff line change
@@ -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<MimeType> getConsumesMimeTypes(HandlerMethod handlerMethod);
}
Original file line number Diff line number Diff line change
@@ -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<MimeType> findProducesMimeTypes(HandlerMethod handlerMethod);
}
Original file line number Diff line number Diff line change
@@ -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<MimeType> getProducesMimeTypes(HandlerMethod handlerMethod);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -105,13 +107,13 @@ interface Parameter extends HasAnnotationsSupplier, HasType, HasContext,

interface RequestBody extends HasAnnotationsSupplier, HasType, HasContext,
HasCustomize<de.qaware.openapigeneratorforspring.model.requestbody.RequestBody> {
Set<String> getConsumesContentTypes();
Set<MimeType> getConsumesMimeTypes();
}

interface Response extends HasType, HasCustomize<ApiResponse> {
String getResponseCode();

Set<String> getProducesContentTypes();
Set<MimeType> getProducesMimeTypes();
}

/**
Expand Down Expand Up @@ -264,15 +266,15 @@ interface ContextModifier<C> {
* de.qaware.openapigeneratorforspring.common.annotation.AnnotationsSupplier
* annotations}.
*
* <p> A type may have its own set of annotations, which are in
* <p>Each type here may have its own set of annotations, which are in
* general different from the element carrying this type.
*
* <p>Note that this interface provides exactly
* the information needed by the {@link
* de.qaware.openapigeneratorforspring.common.schema.resolver.SchemaResolver#resolveFromType schema resolver}
*/
interface Type extends HasAnnotationsSupplier {
java.lang.reflect.Type getType();
ResolvableType getType();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -129,4 +133,16 @@ public DefaultRequestBodyOperationCustomizer defaultRequestBodyOperationCustomiz
) {
return new DefaultRequestBodyOperationCustomizer(requestBodyAnnotationMapper, schemaResolver, handlerMethodRequestBodyMappers);
}

@Bean
@ConditionalOnMissingBean
public DefaultProducesMimeTypeProviderStrategy defaultProducesMimeTypeProviderStrategy(List<ProducesMimeTypeProvider> providers) {
return new DefaultProducesMimeTypeProviderStrategy(providers);
}

@Bean
@ConditionalOnMissingBean
public DefaultConsumesMimeTypeProviderStrategy defaultConsumesMimeTypeProviderStrategy(List<ConsumesMimeTypeProvider> providers) {
return new DefaultConsumesMimeTypeProviderStrategy(providers);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<String> mediaTypes = mapperContext.findMediaTypes(owningType)
.orElseThrow(() -> new IllegalStateException("No media types available in context for " + owningType.getSimpleName()
Set<MimeType> 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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -48,8 +49,8 @@ public <T extends ReferencedItemConsumer> T getReferencedItemConsumer(Class<T> r
}

@Override
public Optional<Set<String>> findMediaTypes(Class<? extends HasContent> owningType) {
return Optional.ofNullable(mediaTypesProvider).map(provider -> provider.getMediaTypes(owningType));
public Optional<Set<MimeType>> findMimeTypes(Class<? extends HasContent> owningType) {
return Optional.ofNullable(mediaTypesProvider).map(provider -> provider.getMimeTypes(owningType));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -66,20 +67,20 @@ private RequestBody applyFromMethod(@Nullable RequestBody existingRequestBody, O
private RequestBody buildRequestBody(List<HandlerMethod.RequestBody> 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;
}
Expand All @@ -99,12 +100,12 @@ private RequestBody buildRequestBodyFromSwaggerAnnotations(List<HandlerMethod.Re
return requestBody;
}

private static MediaType addMediaTypeIfNotPresent(String contentType, RequestBody requestBody) {
private static MediaType addMediaTypeIfNotPresent(MimeType mimeType, RequestBody requestBody) {
if (requestBody.getContent() == null) {
Content content = new Content();
requestBody.setContent(content);
}
return requestBody.getContent().computeIfAbsent(contentType, ignored -> new MediaType());
return requestBody.getContent().computeIfAbsent(mimeType.toString(), ignored -> new MediaType());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ConsumesMimeTypeProvider> providers;

@Override
public Set<MimeType> getConsumesMimeTypes(HandlerMethod handlerMethod) {
return providers.stream()
.map(provider -> provider.findConsumesMimeTypes(handlerMethod))
.filter(mimeTypes -> !mimeTypes.isEmpty())
.findFirst()
.orElseGet(Collections::emptySet);
}
}
Original file line number Diff line number Diff line change
@@ -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<ProducesMimeTypeProvider> providers;

@Override
public Set<MimeType> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
})
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,16 @@ 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) {
handlerMethodResponse.getType().ifPresent(responseType -> {
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 <A extends Annotation> Stream<A> findAnnotations(Class<A> annotationType) {
Expand All @@ -88,14 +88,14 @@ public <A extends Annotation> Stream<A> findAnnotations(Class<A> 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);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading