Skip to content

Commit 88f5da0

Browse files
committed
Provide better compatibility for projects migrating from OAS 3.0 to OAS 3.1. Fixes #2849
1 parent 8dc7e73 commit 88f5da0

File tree

3,938 files changed

+185393
-65247
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

3,938 files changed

+185393
-65247
lines changed

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocConfiguration.java

+20-6
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
* * * *
2222
* * *
2323
* *
24-
*
24+
*
2525
*/
2626

2727

@@ -52,6 +52,7 @@
5252
import org.springdoc.core.converters.AdditionalModelsConverter;
5353
import org.springdoc.core.converters.FileSupportConverter;
5454
import org.springdoc.core.converters.ModelConverterRegistrar;
55+
import org.springdoc.core.converters.OAS31ModelConverter;
5556
import org.springdoc.core.converters.PolymorphicModelConverter;
5657
import org.springdoc.core.converters.PropertyCustomizingConverter;
5758
import org.springdoc.core.converters.ResponseSupportConverter;
@@ -597,14 +598,14 @@ public SpringDocCustomizers springDocCustomizers(Optional<Set<OpenApiCustomizer>
597598
Optional<Set<OperationCustomizer>> operationCustomizers,
598599
Optional<Set<RouterOperationCustomizer>> routerOperationCustomizers,
599600
Optional<Set<DataRestRouterOperationCustomizer>> dataRestRouterOperationCustomizers,
600-
Optional<Set<OpenApiMethodFilter>> methodFilters, Optional<Set<GlobalOpenApiCustomizer>> globalOpenApiCustomizers,
601+
Optional<Set<OpenApiMethodFilter>> methodFilters, Optional<Set<GlobalOpenApiCustomizer>> globalOpenApiCustomizers,
601602
Optional<Set<GlobalOperationCustomizer>> globalOperationCustomizers,
602-
Optional<Set<GlobalOpenApiMethodFilter>> globalOpenApiMethodFilters){
603+
Optional<Set<GlobalOpenApiMethodFilter>> globalOpenApiMethodFilters) {
603604
return new SpringDocCustomizers(openApiCustomizers,
604605
operationCustomizers,
605-
routerOperationCustomizers,
606-
dataRestRouterOperationCustomizers,
607-
methodFilters, globalOpenApiCustomizers, globalOperationCustomizers, globalOpenApiMethodFilters);
606+
routerOperationCustomizers,
607+
dataRestRouterOperationCustomizers,
608+
methodFilters, globalOpenApiCustomizers, globalOperationCustomizers, globalOpenApiMethodFilters);
608609
}
609610

610611
/**
@@ -658,4 +659,17 @@ ParameterObjectNamingStrategyCustomizer parameterObjectNamingStrategyCustomizer(
658659
GlobalOpenApiCustomizer globalOpenApiCustomizer() {
659660
return new OperationIdCustomizer();
660661
}
662+
663+
/**
664+
* Oas 31 model converter oas 31 model converter.
665+
*
666+
* @param springDocConfigProperties the spring doc config properties
667+
* @return the oas 31 model converter
668+
*/
669+
@Bean
670+
@ConditionalOnMissingBean
671+
@Lazy(false)
672+
OAS31ModelConverter oas31ModelConverter(SpringDocConfigProperties springDocConfigProperties) {
673+
return springDocConfigProperties.isOpenapi31() ? new OAS31ModelConverter() : null;
674+
}
661675
}

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocHateoasConfiguration.java

+18-7
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
import com.fasterxml.jackson.core.JsonGenerator;
3232
import com.fasterxml.jackson.databind.SerializerProvider;
3333
import org.springdoc.core.converters.CollectionModelContentConverter;
34-
import org.springdoc.core.converters.RepresentationModelLinksOASMixin;
34+
import org.springdoc.core.converters.HateoasLinksConverter;
3535
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
3636
import org.springdoc.core.customizers.OpenApiHateoasLinksCustomizer;
3737
import org.springdoc.core.properties.SpringDocConfigProperties;
@@ -49,7 +49,6 @@
4949
import org.springframework.context.annotation.Configuration;
5050
import org.springframework.context.annotation.Lazy;
5151
import org.springframework.hateoas.Links;
52-
import org.springframework.hateoas.RepresentationModel;
5352
import org.springframework.hateoas.server.LinkRelationProvider;
5453

5554
/**
@@ -99,20 +98,32 @@ CollectionModelContentConverter collectionModelContentConverter(HateoasHalProvid
9998
*
10099
* @param halProvider the hal provider
101100
* @param springDocConfigProperties the spring doc config properties
102-
* @param objectMapperProvider the object mapper provider
103101
* @return the open api customizer
104-
* @see org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider) org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)
102+
* @see org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider) org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)
105103
*/
106104
@Bean(Constants.LINKS_SCHEMA_CUSTOMISER)
107105
@ConditionalOnMissingBean(name = Constants.LINKS_SCHEMA_CUSTOMISER)
108106
@Lazy(false)
109-
GlobalOpenApiCustomizer linksSchemaCustomizer(HateoasHalProvider halProvider, SpringDocConfigProperties springDocConfigProperties,
110-
ObjectMapperProvider objectMapperProvider) {
107+
GlobalOpenApiCustomizer linksSchemaCustomizer(HateoasHalProvider halProvider, SpringDocConfigProperties springDocConfigProperties) {
111108
if (!halProvider.isHalEnabled()) {
112109
return openApi -> {
113110
};
114111
}
115-
objectMapperProvider.jsonMapper().addMixIn(RepresentationModel.class, RepresentationModelLinksOASMixin.class);
116112
return new OpenApiHateoasLinksCustomizer(springDocConfigProperties);
117113
}
114+
115+
/**
116+
* Hateoas links converter hateoas links converter.
117+
*
118+
* @param springDocObjectMapper the spring doc object mapper
119+
* @return the hateoas links converter
120+
*/
121+
@Bean
122+
@ConditionalOnMissingBean
123+
@Lazy(false)
124+
HateoasLinksConverter hateoasLinksConverter(ObjectMapperProvider springDocObjectMapper) {
125+
return new HateoasLinksConverter(springDocObjectMapper) ;
126+
}
127+
128+
118129
}

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/AdditionalModelsConverter.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
import org.slf4j.LoggerFactory;
4242
import org.springdoc.core.providers.ObjectMapperProvider;
4343

44+
import static org.springdoc.core.utils.SpringDocUtils.handleSchemaTypes;
45+
4446
/**
4547
* The type Additional models converter.
4648
*
@@ -146,8 +148,11 @@ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterato
146148
Class<?> cls = javaType.getRawClass();
147149
if (modelToSchemaMap.containsKey(cls))
148150
try {
151+
Schema schema = modelToSchemaMap.get(cls);
152+
if(springDocObjectMapper.isOpenapi31())
153+
handleSchemaTypes(schema);
149154
return springDocObjectMapper.jsonMapper()
150-
.readValue(springDocObjectMapper.jsonMapper().writeValueAsString(modelToSchemaMap.get(cls)), new TypeReference<Schema>() {});
155+
.readValue(springDocObjectMapper.jsonMapper().writeValueAsString(schema), new TypeReference<Schema>() {});
151156
}
152157
catch (JsonProcessingException e) {
153158
LOGGER.warn("Json Processing Exception occurred: {}", e.getMessage());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * *
7+
* * * * * * Copyright 2019-2025 the original author or authors.
8+
* * * * * *
9+
* * * * * * Licensed under the Apache License, Version 2.0 (the "License");
10+
* * * * * * you may not use this file except in compliance with the License.
11+
* * * * * * You may obtain a copy of the License at
12+
* * * * * *
13+
* * * * * * https://www.apache.org/licenses/LICENSE-2.0
14+
* * * * * *
15+
* * * * * * Unless required by applicable law or agreed to in writing, software
16+
* * * * * * distributed under the License is distributed on an "AS IS" BASIS,
17+
* * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* * * * * * See the License for the specific language governing permissions and
19+
* * * * * * limitations under the License.
20+
* * * * *
21+
* * * *
22+
* * *
23+
* *
24+
*
25+
*/
26+
27+
package org.springdoc.core.converters;
28+
29+
30+
import java.util.Iterator;
31+
32+
import com.fasterxml.jackson.databind.JavaType;
33+
import io.swagger.v3.core.converter.ModelConverter;
34+
import io.swagger.v3.core.converter.ModelConverterContext;
35+
import io.swagger.v3.core.util.AnnotationsUtils;
36+
import io.swagger.v3.oas.models.Components;
37+
import io.swagger.v3.oas.models.media.ArraySchema;
38+
import io.swagger.v3.oas.models.media.JsonSchema;
39+
import io.swagger.v3.oas.models.media.Schema;
40+
import org.springdoc.core.providers.ObjectMapperProvider;
41+
42+
import org.springframework.hateoas.RepresentationModel;
43+
44+
/**
45+
* The type Hateoas links converter.
46+
*
47+
* @author bnasslahsen
48+
*/
49+
public class HateoasLinksConverter implements ModelConverter {
50+
51+
/**
52+
* The Spring doc object mapper.
53+
*/
54+
private final ObjectMapperProvider springDocObjectMapper;
55+
56+
/**
57+
* Instantiates a new Hateoas links converter.
58+
*
59+
* @param springDocObjectMapper the spring doc object mapper
60+
*/
61+
public HateoasLinksConverter(ObjectMapperProvider springDocObjectMapper) {
62+
this.springDocObjectMapper = springDocObjectMapper;
63+
}
64+
65+
@Override
66+
public Schema<?> resolve(
67+
io.swagger.v3.core.converter.AnnotatedType type,
68+
ModelConverterContext context,
69+
Iterator<ModelConverter> chain
70+
) {
71+
JavaType javaType = springDocObjectMapper.jsonMapper().constructType(type.getType());
72+
if (javaType != null) {
73+
if (RepresentationModel.class.isAssignableFrom(javaType.getRawClass())) {
74+
Schema<?> schema = chain.next().resolve(type, context, chain);
75+
String schemaName = schema.get$ref().substring(Components.COMPONENTS_SCHEMAS_REF.length());
76+
Schema original = context.getDefinedModels().get(schemaName);
77+
Object links = original.getProperties().get("_links");
78+
if(links instanceof JsonSchema jsonSchema) {
79+
jsonSchema.set$ref(AnnotationsUtils.COMPONENTS_REF + "Links");
80+
jsonSchema.setType(null);
81+
jsonSchema.setItems(null);
82+
jsonSchema.setTypes(null);
83+
} else if (links instanceof ArraySchema arraySchema){
84+
arraySchema.set$ref(AnnotationsUtils.COMPONENTS_REF + "Links");
85+
}
86+
return schema;
87+
}
88+
}
89+
return chain.hasNext() ? chain.next().resolve(type, context, chain) : null;
90+
}
91+
92+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * *
7+
* * * * * * Copyright 2019-2025 the original author or authors.
8+
* * * * * *
9+
* * * * * * Licensed under the Apache License, Version 2.0 (the "License");
10+
* * * * * * you may not use this file except in compliance with the License.
11+
* * * * * * You may obtain a copy of the License at
12+
* * * * * *
13+
* * * * * * https://www.apache.org/licenses/LICENSE-2.0
14+
* * * * * *
15+
* * * * * * Unless required by applicable law or agreed to in writing, software
16+
* * * * * * distributed under the License is distributed on an "AS IS" BASIS,
17+
* * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* * * * * * See the License for the specific language governing permissions and
19+
* * * * * * limitations under the License.
20+
* * * * *
21+
* * * *
22+
* * *
23+
* *
24+
*
25+
*/
26+
27+
package org.springdoc.core.converters;
28+
29+
import java.util.Iterator;
30+
31+
import io.swagger.v3.core.converter.AnnotatedType;
32+
import io.swagger.v3.core.converter.ModelConverter;
33+
import io.swagger.v3.core.converter.ModelConverterContext;
34+
import io.swagger.v3.oas.models.media.Schema;
35+
36+
import static org.springdoc.core.utils.SpringDocUtils.handleSchemaTypes;
37+
import static org.springdoc.core.utils.SpringDocUtils.isComposedSchema;
38+
39+
/**
40+
* The type OAS31 Model converter.
41+
*
42+
* @author bnasslahsen
43+
*/
44+
public class OAS31ModelConverter implements ModelConverter {
45+
46+
@Override
47+
public Schema<?> resolve(AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
48+
if (chain.hasNext()) {
49+
Schema<?> resolvedSchema = chain.next().resolve(type, context, chain);
50+
if (resolvedSchema != null && !isComposedSchema(resolvedSchema)) {
51+
handleSchemaTypes(resolvedSchema);
52+
}
53+
return resolvedSchema;
54+
}
55+
return null;
56+
}
57+
58+
}

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/PageOpenAPIConverter.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@
3535
import io.swagger.v3.core.converter.ModelConverter;
3636
import io.swagger.v3.core.converter.ModelConverterContext;
3737
import io.swagger.v3.oas.models.media.Schema;
38-
import org.apache.commons.lang3.StringUtils;
3938
import org.springdoc.core.providers.ObjectMapperProvider;
4039

4140
import org.springframework.core.ResolvableType;
4241
import org.springframework.data.web.PagedModel;
4342

43+
import static org.springdoc.core.utils.SpringDocUtils.getParentTypeName;
44+
4445
/**
4546
* The Spring Data Page type model converter.
4647
*
@@ -95,7 +96,7 @@ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterato
9596
if (!type.isSchemaProperty())
9697
type = resolvePagedModelType(type);
9798
else
98-
type.name(cls.getSimpleName() + StringUtils.capitalize(type.getParent().getType()));
99+
type.name(getParentTypeName(type, cls));
99100
}
100101
}
101102
return (chain.hasNext()) ? chain.next().resolve(type, context, chain) : null;

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/PageableOpenAPIConverter.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@
3333
import io.swagger.v3.core.converter.ModelConverter;
3434
import io.swagger.v3.core.converter.ModelConverterContext;
3535
import io.swagger.v3.oas.models.media.Schema;
36-
import org.apache.commons.lang3.StringUtils;
3736
import org.springdoc.core.converters.models.Pageable;
3837
import org.springdoc.core.providers.ObjectMapperProvider;
3938

39+
import static org.springdoc.core.utils.SpringDocUtils.getParentTypeName;
40+
4041
/**
4142
* The Pageable Type models converter.
4243
*
@@ -90,7 +91,7 @@ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterato
9091
if (!type.isSchemaProperty())
9192
type = PAGEABLE;
9293
else
93-
type.name(cls.getSimpleName() + StringUtils.capitalize(type.getParent().getType()));
94+
type.name(getParentTypeName(type, cls));
9495
}
9596
}
9697
return (chain.hasNext()) ? chain.next().resolve(type, context, chain) : null;

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/PolymorphicModelConverter.java

+13-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
* * * *
2222
* * *
2323
* *
24-
*
24+
*
2525
*/
2626

2727
package org.springdoc.core.converters;
@@ -64,6 +64,11 @@ public class PolymorphicModelConverter implements ModelConverter {
6464
*/
6565
private static final List<String> PARENT_TYPES_TO_IGNORE = Collections.synchronizedList(new ArrayList<>());
6666

67+
/**
68+
* The constant PARENT_TYPES_TO_IGNORE.
69+
*/
70+
private static final List<String> TYPES_TO_SKIP = Collections.synchronizedList(new ArrayList<>());
71+
6772
static {
6873
PARENT_TYPES_TO_IGNORE.add("JsonSchema");
6974
PARENT_TYPES_TO_IGNORE.add("Pageable");
@@ -115,6 +120,9 @@ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterato
115120
if (field.isAnnotationPresent(JsonUnwrapped.class)) {
116121
PARENT_TYPES_TO_IGNORE.add(javaType.getRawClass().getSimpleName());
117122
}
123+
else if (field.isAnnotationPresent(io.swagger.v3.oas.annotations.media.Schema.class)) {
124+
TYPES_TO_SKIP.add(field.getType().getSimpleName());
125+
}
118126
}
119127
if (chain.hasNext()) {
120128
if (!type.isResolveAsRef() && type.getParent() != null
@@ -149,12 +157,13 @@ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterato
149157
private Schema composePolymorphicSchema(AnnotatedType type, Schema schema, Collection<Schema> schemas) {
150158
String ref = schema.get$ref();
151159
List<Schema> composedSchemas = findComposedSchemas(ref, schemas);
152-
153160
if (composedSchemas.isEmpty()) return schema;
154-
155161
ComposedSchema result = new ComposedSchema();
156162
if (isConcreteClass(type)) result.addOneOfItem(schema);
157-
composedSchemas.forEach(result::addOneOfItem);
163+
JavaType javaType = springDocObjectMapper.jsonMapper().constructType(type.getType());
164+
Class<?> clazz = javaType.getRawClass();
165+
if(TYPES_TO_SKIP.stream().noneMatch(typeToSkip -> typeToSkip.equals(clazz.getSimpleName())))
166+
composedSchemas.forEach(result::addOneOfItem);
158167
return result;
159168
}
160169

0 commit comments

Comments
 (0)