diff --git a/CI/post-release.sh b/CI/post-release.sh index 9936615799..99f4b658d2 100755 --- a/CI/post-release.sh +++ b/CI/post-release.sh @@ -44,6 +44,9 @@ sc_find="io.swagger.core.v3:swagger-jaxrs2:$SC_VERSION" sc_replace="io.swagger.core.v3:swagger-jaxrs2:$SC_NEXT_VERSION-SNAPSHOT" sed -i -e "s/$sc_find/$sc_replace/g" $CUR/modules/swagger-gradle-plugin/src/test/java/io/swagger/v3/plugins/gradle/SwaggerResolveTest.java +sc_find="$SC_VERSION<\/version>" +sc_replace="$SC_NEXT_VERSION-SNAPSHOT<\/version>" +sed -i -e "s/$sc_find/$sc_replace/g" $CUR/modules/swagger-java17-support/pom.xml ##################### ### Copy scripts to temp folder, as they are not available when checking out different branch or repo diff --git a/CI/prepare-release.sh b/CI/prepare-release.sh index 691526e724..8c8106a501 100755 --- a/CI/prepare-release.sh +++ b/CI/prepare-release.sh @@ -73,6 +73,10 @@ sc_find="$SC_LAST_RELEASE<\/version>" sc_replace="$SC_VERSION<\/version>" sed -i -e "s/$sc_find/$sc_replace/g" $CUR/modules/swagger-maven-plugin/README.md +sc_find="$SC_LAST_RELEASE<\/version>" +sc_replace="$SC_VERSION<\/version>" +sed -i -e "s/$sc_find/$sc_replace/g" $CUR/modules/swaggerjava17-support/pom.xml + ##################### ### build and test maven ### ##################### diff --git a/README.md b/README.md index a87ea4465f..db90aa5c35 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,9 @@ The OpenAPI Specification has undergone several revisions since initial creation Swagger core Version | Release Date | OpenAPI Spec compatibility | Notes | Status ------------------------- | ------------ | -------------------------- | ----- | ---- -2.2.23 (**current stable**)| 2024-08-28 | 3.x | [tag v2.2.23](https://github.com/swagger-api/swagger-core/tree/v2.2.23) | Supported +2.2.25 (**current stable**)| 2024-10-02 | 3.x | [tag v2.2.25](https://github.com/swagger-api/swagger-core/tree/v2.2.25) | Supported +2.2.24 | 2024-09-23 | 3.x | [tag v2.2.24](https://github.com/swagger-api/swagger-core/tree/v2.2.24) | Supported +2.2.23 | 2024-08-28 | 3.x | [tag v2.2.23](https://github.com/swagger-api/swagger-core/tree/v2.2.23) | Supported 2.2.22 | 2024-05-15 | 3.x | [tag v2.2.22](https://github.com/swagger-api/swagger-core/tree/v2.2.22) | Supported 2.2.21 | 2024-03-20 | 3.x | [tag v2.2.21](https://github.com/swagger-api/swagger-core/tree/v2.2.21) | Supported 2.2.20 | 2023-12-19 | 3.x | [tag v2.2.20](https://github.com/swagger-api/swagger-core/tree/v2.2.20) | Supported @@ -116,7 +118,7 @@ You need the following installed and available in your $PATH: * Jackson 2.4.5 or greater -### To build from source (currently 2.2.24-SNAPSHOT) +### To build from source (currently 2.2.26-SNAPSHOT) ``` # first time building locally mvn -N diff --git a/modules/swagger-annotations/pom.xml b/modules/swagger-annotations/pom.xml index f106d90afa..774aa1967f 100644 --- a/modules/swagger-annotations/pom.xml +++ b/modules/swagger-annotations/pom.xml @@ -3,7 +3,7 @@ io.swagger.core.v3 swagger-project - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../.. 4.0.0 diff --git a/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/media/Schema.java b/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/media/Schema.java index b5d02b0a83..0ba732c6e5 100644 --- a/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/media/Schema.java +++ b/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/media/Schema.java @@ -237,7 +237,7 @@ boolean writeOnly() default false; /** - * Allows to specify the access mode (AccessMode.READ_ONLY, READ_WRITE) + * Allows to specify the access mode (AccessMode.READ_ONLY, WRITE_ONLY, READ_WRITE) * * AccessMode.READ_ONLY: value will not be written to during a request but may be returned during a response. * AccessMode.WRITE_ONLY: value will only be written to during a request but not returned during a response. @@ -246,7 +246,7 @@ * @return the accessMode for this schema (property) * */ - AccessMode accessMode() default AccessMode.AUTO; + AccessMode accessMode() default AccessMode.AUTO; /** * Provides an example of the schema. When associated with a specific media type, the example string shall be parsed by the consumer to be treated as an object or an array. @@ -554,6 +554,14 @@ enum RequiredMode { NOT_REQUIRED; } + enum SchemaResolution { + AUTO, + DEFAULT, + INLINE, + ALL_OF, + ALL_OF_REF; + } + /** * Allows to specify the dependentRequired value ** @@ -616,4 +624,17 @@ enum RequiredMode { */ @OpenAPI31 String _const() default ""; + + /** + * Allows to specify the schema resolution mode for object schemas + * + * SchemaResolution.DEFAULT: bundled into components/schemas, $ref with no siblings + * SchemaResolution.INLINE: inline schema, no $ref + * SchemaResolution.ALL_OF: bundled into components/schemas, $ref and any context annotation resolution into allOf + * SchemaResolution.ALL_OF_REF: bundled into components/schemas, $ref into allOf, context annotation resolution into root + * + * @return the schema resolution mode for this schema + * + */ + SchemaResolution schemaResolution() default SchemaResolution.AUTO; } diff --git a/modules/swagger-core/pom.xml b/modules/swagger-core/pom.xml index 4c181abdc8..5c64e4204f 100644 --- a/modules/swagger-core/pom.xml +++ b/modules/swagger-core/pom.xml @@ -3,7 +3,7 @@ io.swagger.core.v3 swagger-project - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../.. 4.0.0 diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java index 0bb9c21f02..7149e587c5 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java @@ -42,6 +42,15 @@ public ModelConverters(boolean openapi31) { } } + public ModelConverters(boolean openapi31, Schema.SchemaResolution schemaResolution) { + converters = new CopyOnWriteArrayList<>(); + if (openapi31) { + converters.add(new ModelResolver(Json31.mapper()).openapi31(true).schemaResolution(schemaResolution)); + } else { + converters.add(new ModelResolver(Json.mapper()).schemaResolution(schemaResolution)); + } + } + public Set getSkippedPackages() { return skippedPackages; } @@ -61,6 +70,30 @@ public static ModelConverters getInstance(boolean openapi31) { return SINGLETON; } + public static void reset() { + synchronized (ModelConverters.class) { + SINGLETON = null; + SINGLETON31 = null; + } + } + + public static ModelConverters getInstance(boolean openapi31, Schema.SchemaResolution schemaResolution) { + synchronized (ModelConverters.class) { + if (openapi31) { + if (SINGLETON31 == null) { + SINGLETON31 = new ModelConverters(openapi31, Schema.SchemaResolution.DEFAULT); + init(SINGLETON31); + } + return SINGLETON31; + } + if (SINGLETON == null) { + SINGLETON = new ModelConverters(openapi31, schemaResolution); + init(SINGLETON); + } + return SINGLETON; + } + } + private static void init(ModelConverters converter) { converter.addPackageToSkip("java.lang"); converter.addPackageToSkip("groovy.lang"); diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/filter/SpecFilter.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/filter/SpecFilter.java index 7d2f00a211..e0f15c8679 100755 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/filter/SpecFilter.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/filter/SpecFilter.java @@ -85,7 +85,7 @@ public OpenAPI filter(OpenAPI openAPI, OpenAPISpecFilter filter, Map referencedDefin Map ops = pathItem.readOperationsMap(); for (Operation op : ops.values()) { if (op.getRequestBody() != null) { - addContentSchemaRef(op.getRequestBody().getContent(), referencedDefinitions); + addRequestBodySchemaRef(op.getRequestBody(), referencedDefinitions); } if (op.getResponses() != null) { for (String keyResponses : op.getResponses().keySet()) { ApiResponse response = op.getResponses().get(keyResponses); - if (response.getHeaders() != null) { - for (String keyHeaders : response.getHeaders().keySet()) { - Header header = response.getHeaders().get(keyHeaders); - addSchemaRef(header.getSchema(), referencedDefinitions); - addContentSchemaRef(header.getContent(), referencedDefinitions); - } - } - addContentSchemaRef(response.getContent(), referencedDefinitions); + addApiResponseSchemaRef(response, referencedDefinitions); } } if (op.getParameters() != null) { for (Parameter parameter : op.getParameters()) { - addSchemaRef(parameter.getSchema(), referencedDefinitions); - addContentSchemaRef(parameter.getContent(), referencedDefinitions); + addParameterSchemaRef(parameter, referencedDefinitions); } } if (op.getCallbacks() != null) { for (String keyCallback : op.getCallbacks().keySet()) { Callback callback = op.getCallbacks().get(keyCallback); - for (PathItem callbackPathItem : callback.values()) { - addPathItemSchemaRef(callbackPathItem, referencedDefinitions); - } + addCallbackSchemaRef(callback, referencedDefinitions); } } } } + private void addApiResponseSchemaRef(ApiResponse response, Set referencedDefinitions) { + if (response.getHeaders() != null) { + for (String keyHeaders : response.getHeaders().keySet()) { + Header header = response.getHeaders().get(keyHeaders); + addHeaderSchemaRef(header, referencedDefinitions); + } + } + addContentSchemaRef(response.getContent(), referencedDefinitions); + } + + private void addRequestBodySchemaRef(RequestBody requestBody, Set referencedDefinitions) { + addContentSchemaRef(requestBody.getContent(), referencedDefinitions); + } + + private void addParameterSchemaRef(Parameter parameter, Set referencedDefinitions) { + addSchemaRef(parameter.getSchema(), referencedDefinitions); + addContentSchemaRef(parameter.getContent(), referencedDefinitions); + } + + private void addHeaderSchemaRef(Header header, Set referencedDefinitions) { + addSchemaRef(header.getSchema(), referencedDefinitions); + addContentSchemaRef(header.getContent(), referencedDefinitions); + } + + private void addCallbackSchemaRef(Callback callback, Set referencedDefinitions){ + for (PathItem callbackPathItem : callback.values()) { + addPathItemSchemaRef(callbackPathItem, referencedDefinitions); + } + } + + private void addComponentsSchemaRef(Components components, Set referencedDefinitions){ + + if (components.getResponses() != null){ + for (String resourcePath : components.getResponses().keySet()) { + ApiResponse apiResponse = components.getResponses().get(resourcePath); + addApiResponseSchemaRef(apiResponse, referencedDefinitions); + } + } + if (components.getRequestBodies() != null){ + for (String requestBody : components.getRequestBodies().keySet()) { + RequestBody requestBody1 = components.getRequestBodies().get(requestBody); + addRequestBodySchemaRef(requestBody1, referencedDefinitions); + } + } + if (components.getParameters() != null){ + for (String parameter : components.getParameters().keySet()) { + Parameter resourceParam = components.getParameters().get(parameter); + addParameterSchemaRef(resourceParam, referencedDefinitions); + } + } + if (components.getHeaders() != null){ + for (String header : components.getHeaders().keySet()) { + Header resourceHeader = components.getHeaders().get(header); + addHeaderSchemaRef(resourceHeader, referencedDefinitions); + } + } + if (components.getCallbacks() != null){ + for (String callback : components.getCallbacks().keySet()){ + Callback resourceCallback = components.getCallbacks().get(callback); + addCallbackSchemaRef(resourceCallback, referencedDefinitions); + } + } + if (components.getPathItems() != null){ + for (String resourcePath : components.getPathItems().keySet()){ + PathItem pathItem = components.getPathItems().get(resourcePath); + addPathItemSchemaRef(pathItem, referencedDefinitions); + } + } + } + protected OpenAPI removeBrokenReferenceDefinitions(OpenAPI openApi) { if (openApi == null || openApi.getComponents() == null || openApi.getComponents().getSchemas() == null) { @@ -395,6 +455,16 @@ protected OpenAPI removeBrokenReferenceDefinitions(OpenAPI openApi) { addPathItemSchemaRef(pathItem, referencedDefinitions); } } + if (openApi.getWebhooks() != null){ + for (String resourcePath : openApi.getWebhooks().keySet()) { + PathItem pathItem = openApi.getWebhooks().get(resourcePath); + addPathItemSchemaRef(pathItem, referencedDefinitions); + } + } + if (openApi.getComponents() != null){ + Components components = openApi.getComponents(); + addComponentsSchemaRef(components, referencedDefinitions); + } referencedDefinitions.addAll(resolveAllNestedRefs(referencedDefinitions, referencedDefinitions, openApi)); openApi.getComponents() diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java index bafa33ee0c..19f54de1e5 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java @@ -125,6 +125,8 @@ public class ModelResolver extends AbstractModelConverter implements ModelConver private boolean openapi31; + private Schema.SchemaResolution schemaResolution = Schema.SchemaResolution.DEFAULT; + public ModelResolver(ObjectMapper mapper) { super(mapper); } @@ -536,18 +538,24 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context } } } else if (isComposedSchema) { - model = new ComposedSchema() - .type("object") - .name(name); + model = new ComposedSchema().name(name); + if (openapi31 && resolvedArrayAnnotation == null){ + model.addType("object"); + }else{ + model.type("object"); + } } else { AnnotatedType aType = ReferenceTypeUtils.unwrapReference(annotatedType); if (aType != null) { model = context.resolve(aType); return model; } else { - model = new Schema() - .type("object") - .name(name); + model = new Schema().name(name); + if (openapi31 && resolvedArrayAnnotation == null){ + model.addType("object"); + }else{ + model.type("object"); + } } } @@ -673,8 +681,11 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context io.swagger.v3.oas.annotations.media.Schema.RequiredMode requiredMode = resolveRequiredMode(propResolvedSchemaAnnotation); Annotation[] ctxAnnotation31 = null; - - if (openapi31) { + Schema.SchemaResolution resolvedSchemaResolution = AnnotationsUtils.resolveSchemaResolution(this.schemaResolution, ctxSchema); + if ( + Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) || + Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) || + openapi31) { List ctxAnnotations31List = new ArrayList<>(); if (annotations != null) { for (Annotation a : annotations) { @@ -684,8 +695,8 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context ctxAnnotations31List.add(a); } if ((ctxSchema != null) && (!ctxSchema.implementation().equals(Void.class) || StringUtils.isNotEmpty(ctxSchema.type()))) { - ctxAnnotations31List.add(a); - } + ctxAnnotations31List.add(a); + } } ctxAnnotation31 = ctxAnnotations31List.toArray(new Annotation[ctxAnnotations31List.size()]); } @@ -693,7 +704,6 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context AnnotatedType aType = new AnnotatedType() .type(propType) - .ctxAnnotations(openapi31 ? ctxAnnotation31 : annotations) .parent(model) .resolveAsRef(annotatedType.isResolveAsRef()) .jsonViewAnnotation(annotatedType.getJsonViewAnnotation()) @@ -701,7 +711,14 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context .schemaProperty(true) .components(annotatedType.getComponents()) .propertyName(propName); - + if ( + Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) || + Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) || + openapi31) { + aType.ctxAnnotations(ctxAnnotation31); + } else { + aType.ctxAnnotations(annotations); + } final AnnotatedMember propMember = member; aType.jsonUnwrappedHandler(t -> { JsonUnwrapped uw = propMember.getAnnotation(JsonUnwrapped.class); @@ -718,8 +735,9 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context }); property = context.resolve(aType); property = clone(property); + Schema ctxProperty = null; if (openapi31) { - Optional reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, property, context); + Optional reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, property, schemaResolution, context); if (reResolvedProperty.isPresent()) { property = reResolvedProperty.get(); } @@ -728,6 +746,16 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context property = reResolvedProperty.get(); } + } else if (Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) || Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution)) { + Optional reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, null, schemaResolution, context); + if (reResolvedProperty.isPresent()) { + ctxProperty = reResolvedProperty.get(); + } + reResolvedProperty = AnnotationsUtils.getArraySchema(ctxArraySchema, annotatedType.getComponents(), null, openapi31, ctxProperty); + if (reResolvedProperty.isPresent()) { + ctxProperty = reResolvedProperty.get(); + } + } if (property != null) { Boolean required = md.getRequired(); @@ -767,10 +795,21 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context } if (context.getDefinedModels().containsKey(pName)) { - property = new Schema().$ref(constructRef(pName)); + if (Schema.SchemaResolution.INLINE.equals(resolvedSchemaResolution)) { + property = context.getDefinedModels().get(pName); + } else if (Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) && ctxProperty != null) { + property = new Schema() + .addAllOfItem(ctxProperty) + .addAllOfItem(new Schema().$ref(constructRef(pName))); + } else if (Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) && ctxProperty != null) { + property = ctxProperty.addAllOfItem(new Schema().$ref(constructRef(pName))); + } else { + property = new Schema().$ref(constructRef(pName)); + } property = clone(property); - if (openapi31) { - Optional reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, property, context); + // TODO: why is this needed? is it not handled before? + if (openapi31 || Schema.SchemaResolution.INLINE.equals(resolvedSchemaResolution)) { + Optional reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, property, this.schemaResolution, context); if (reResolvedProperty.isPresent()) { property = reResolvedProperty.get(); } @@ -782,7 +821,16 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context } } else if (property.get$ref() != null) { if (!openapi31) { - property = new Schema().$ref(StringUtils.isNotEmpty(property.get$ref()) ? property.get$ref() : property.getName()); + if (Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) && ctxProperty != null) { + property = new Schema() + .addAllOfItem(ctxProperty) + .addAllOfItem(new Schema().$ref(StringUtils.isNotEmpty(property.get$ref()) ? property.get$ref() : property.getName())); + } else if (Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) && ctxProperty != null) { + property = ctxProperty + .addAllOfItem(new Schema().$ref(StringUtils.isNotEmpty(property.get$ref()) ? property.get$ref() : property.getName())); + } else { + property = new Schema().$ref(StringUtils.isNotEmpty(property.get$ref()) ? property.get$ref() : property.getName()); + } } else { if (StringUtils.isEmpty(property.get$ref())) { property.$ref(property.getName()); @@ -795,9 +843,12 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context if (property != null && io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED.equals(requiredMode)) { addRequiredItem(model, property.getName()); } + if (ctxProperty == null) { + ctxProperty = property; + } final boolean applyNotNullAnnotations = io.swagger.v3.oas.annotations.media.Schema.RequiredMode.AUTO.equals(requiredMode); annotations = addGenericTypeArgumentAnnotationsForOptionalField(propDef, annotations); - applyBeanValidatorAnnotations(propDef, property, annotations, model, applyNotNullAnnotations); + applyBeanValidatorAnnotations(propDef, ctxProperty, annotations, model, applyNotNullAnnotations); props.add(property); } @@ -989,12 +1040,16 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context context.defineModel(name, model, annotatedType, null); } + Schema.SchemaResolution resolvedSchemaResolution = AnnotationsUtils.resolveSchemaResolution(this.schemaResolution, resolvedSchemaAnnotation); + if (model != null && annotatedType.isResolveAsRef() && (isComposedSchema || isObjectSchema(model)) && StringUtils.isNotBlank(model.getName())) { if (context.getDefinedModels().containsKey(model.getName())) { - model = new Schema().$ref(constructRef(model.getName())); + if (!Schema.SchemaResolution.INLINE.equals(resolvedSchemaResolution)) { + model = new Schema().$ref(constructRef(model.getName())); + } } } else if (model != null && model.get$ref() != null) { model = new Schema().$ref(StringUtils.isNotEmpty(model.get$ref()) ? model.get$ref() : model.getName()); @@ -1062,17 +1117,21 @@ protected Type findJsonValueType(final BeanDescription beanDesc) { // use recursion to check for method findJsonValueAccessor existence (Jackson 2.9+) // if not found use previous deprecated method which could lead to inaccurate result try { - Method m = BeanDescription.class.getMethod("findJsonValueAccessor"); - AnnotatedMember jsonValueMember = (AnnotatedMember)m.invoke(beanDesc); + AnnotatedMember jsonValueMember = invokeMethod(beanDesc, "findJsonValueAccessor"); if (jsonValueMember != null) { return jsonValueMember.getType(); } - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + } catch (Exception e) { LOGGER.warn("jackson BeanDescription.findJsonValueAccessor not found, this could lead to inaccurate result, please update jackson to 2.9+"); - final AnnotatedMethod jsonValueMethod = beanDesc.findJsonValueMethod(); - if (jsonValueMethod != null) { - return jsonValueMethod.getType(); + } + + try { + AnnotatedMember jsonValueMember = invokeMethod(beanDesc, "findJsonValueMethod"); + if (jsonValueMember != null) { + return jsonValueMember.getType(); } + } catch (Exception e) { + LOGGER.error("Neither 'findJsonValueMethod' nor 'findJsonValueAccessor' found in jackson BeanDescription. Please verify your Jackson version."); } return null; } @@ -1249,6 +1308,18 @@ private void handleUnwrapped(List props, Schema innerModel, String prefi } } + public Schema.SchemaResolution getSchemaResolution() { + return schemaResolution; + } + + public void setSchemaResolution(Schema.SchemaResolution schemaResolution) { + this.schemaResolution = schemaResolution; + } + + public ModelResolver schemaResolution(Schema.SchemaResolution schemaResolution) { + setSchemaResolution(schemaResolution); + return this; + } private class GeneratorWrapper { @@ -1469,59 +1540,54 @@ protected void applyBeanValidatorAnnotations(Schema property, Annotation[] annot } } if (annos.containsKey("javax.validation.constraints.Min")) { - if ("integer".equals(property.getType()) || "number".equals(property.getType())) { + if (isNumberSchema(property)) { Min min = (Min) annos.get("javax.validation.constraints.Min"); property.setMinimum(new BigDecimal(min.value())); } } if (annos.containsKey("javax.validation.constraints.Max")) { - if ("integer".equals(property.getType()) || "number".equals(property.getType())) { + if (isNumberSchema(property)) { Max max = (Max) annos.get("javax.validation.constraints.Max"); property.setMaximum(new BigDecimal(max.value())); } } if (annos.containsKey("javax.validation.constraints.Size")) { Size size = (Size) annos.get("javax.validation.constraints.Size"); - if ("integer".equals(property.getType()) || "number".equals(property.getType())) { + if (isNumberSchema(property)) { property.setMinimum(new BigDecimal(size.min())); property.setMaximum(new BigDecimal(size.max())); - } else if (property instanceof StringSchema) { - StringSchema sp = (StringSchema) property; - sp.minLength(Integer.valueOf(size.min())); - sp.maxLength(Integer.valueOf(size.max())); - } else if (property instanceof ArraySchema) { - ArraySchema sp = (ArraySchema) property; - sp.setMinItems(size.min()); - sp.setMaxItems(size.max()); + } + if (isStringSchema(property)) { + property.setMinLength(Integer.valueOf(size.min())); + property.setMaxLength(Integer.valueOf(size.max())); + } + if (isArraySchema(property)) { + property.setMinItems(size.min()); + property.setMaxItems(size.max()); } } if (annos.containsKey("javax.validation.constraints.DecimalMin")) { DecimalMin min = (DecimalMin) annos.get("javax.validation.constraints.DecimalMin"); - if (property instanceof NumberSchema) { - NumberSchema ap = (NumberSchema) property; - ap.setMinimum(new BigDecimal(min.value())); - ap.setExclusiveMinimum(!min.inclusive()); + if (isNumberSchema(property)) { + property.setMinimum(new BigDecimal(min.value())); + property.setExclusiveMinimum(!min.inclusive()); } } if (annos.containsKey("javax.validation.constraints.DecimalMax")) { DecimalMax max = (DecimalMax) annos.get("javax.validation.constraints.DecimalMax"); - if (property instanceof NumberSchema) { - NumberSchema ap = (NumberSchema) property; - ap.setMaximum(new BigDecimal(max.value())); - ap.setExclusiveMaximum(!max.inclusive()); + if (isNumberSchema(property)) { + property.setMaximum(new BigDecimal(max.value())); + property.setExclusiveMaximum(!max.inclusive()); } } if (annos.containsKey("javax.validation.constraints.Pattern")) { Pattern pattern = (Pattern) annos.get("javax.validation.constraints.Pattern"); - - if (property instanceof StringSchema) { + if (isStringSchema(property)) { property.setPattern(pattern.regexp()); } - - if(property.getItems() != null && property.getItems() instanceof StringSchema) { + if(property.getItems() != null && isStringSchema(property.getItems())) { property.getItems().setPattern(pattern.regexp()); } - } } @@ -1864,7 +1930,7 @@ protected Map resolveDependentSchemas(JavaType a, Annotation[] a } Annotation[] propAnnotations = new Annotation[]{dependentSchemaAnnotation.schema(), dependentSchemaAnnotation.array()}; Schema existingSchema = null; - Optional resolvedPropSchema = AnnotationsUtils.getSchemaFromAnnotation(dependentSchemaAnnotation.schema(), components, jsonViewAnnotation, openapi31, null, context); + Optional resolvedPropSchema = AnnotationsUtils.getSchemaFromAnnotation(dependentSchemaAnnotation.schema(), components, jsonViewAnnotation, openapi31, null, Schema.SchemaResolution.DEFAULT, context); if (resolvedPropSchema.isPresent()) { existingSchema = resolvedPropSchema.get(); dependentSchemas.put(name, existingSchema); @@ -2980,7 +3046,24 @@ public void setOpenapi31(boolean openapi31) { } protected boolean isObjectSchema(Schema schema) { - return "object".equals(schema.getType()) || (schema.getType() == null && schema.getProperties() != null && !schema.getProperties().isEmpty()); + return (schema.getTypes() != null && schema.getTypes().contains("object")) || "object".equals(schema.getType()) || (schema.getType() == null && schema.getProperties() != null && !schema.getProperties().isEmpty()); + } + + protected boolean isArraySchema(Schema schema){ + return "array".equals(schema.getType()) || (schema.getTypes() != null && schema.getTypes().contains("array")); + } + + protected boolean isStringSchema(Schema schema){ + return "string".equals(schema.getType()) || (schema.getTypes() != null && schema.getTypes().contains("string")); + } + + protected boolean isNumberSchema(Schema schema){ + return "number".equals(schema.getType()) || (schema.getTypes() != null && schema.getTypes().contains("number")) || "integer".equals(schema.getType()) || (schema.getTypes() != null && schema.getTypes().contains("integer")); + } + + private AnnotatedMember invokeMethod(final BeanDescription beanDesc, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException{ + Method m = BeanDescription.class.getMethod(methodName); + return (AnnotatedMember) m.invoke(beanDesc); } protected Schema buildRefSchemaIfObject(Schema schema, ModelConverterContext context) { diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java index e1ed50c9f2..cb26009e0a 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java @@ -587,24 +587,46 @@ public static Optional getSchemaFromAnnotation( boolean openapi31, Schema existingSchema, ModelConverterContext context) { + return getSchemaFromAnnotation(schema, components, jsonViewAnnotation, openapi31, existingSchema, Schema.SchemaResolution.DEFAULT, null); + } + public static Optional getSchemaFromAnnotation( + io.swagger.v3.oas.annotations.media.Schema schema, + Components components, + JsonView jsonViewAnnotation, + boolean openapi31, + Schema existingSchema, + Schema.SchemaResolution schemaResolution, + ModelConverterContext context) { if (schema == null || !hasSchemaAnnotation(schema)) { - if (existingSchema == null || !openapi31) { + if (existingSchema == null || (!openapi31 && Schema.SchemaResolution.DEFAULT.equals(schemaResolution))) { return Optional.empty(); - } else if (existingSchema != null && openapi31) { + } else if (existingSchema != null && (openapi31 || Schema.SchemaResolution.INLINE.equals(schemaResolution))) { return Optional.of(existingSchema); } } Schema schemaObject = null; if (!openapi31) { if (existingSchema != null) { - return Optional.of(existingSchema); + if (!Schema.SchemaResolution.DEFAULT.equals(schemaResolution)) { + schemaObject = existingSchema; + } else { + return Optional.of(existingSchema); + } } - if (schema.oneOf().length > 0 || - schema.allOf().length > 0 || - schema.anyOf().length > 0) { - schemaObject = new ComposedSchema(); - } else { - schemaObject = new Schema(); + if (Schema.SchemaResolution.DEFAULT.equals(schemaResolution)) { + if (schema != null && (schema.oneOf().length > 0 || + schema.allOf().length > 0 || + schema.anyOf().length > 0)) { + schemaObject = new ComposedSchema(); + } else { + schemaObject = new Schema(); + } + } else if (Schema.SchemaResolution.ALL_OF.equals(schemaResolution) || Schema.SchemaResolution.ALL_OF_REF.equals(schemaResolution)) { + if (existingSchema == null) { + schemaObject = new Schema(); + } else { + schemaObject = existingSchema; + } } } else { if (existingSchema == null) { @@ -613,6 +635,9 @@ public static Optional getSchemaFromAnnotation( schemaObject = existingSchema; } } + if (schema == null) { + return Optional.of(schemaObject); + } if (StringUtils.isNotBlank(schema.description())) { schemaObject.setDescription(schema.description()); } @@ -824,21 +849,21 @@ public static Optional getSchemaFromAnnotation( Class[] schemaImplementations = schema.oneOf(); for (Class schemaImplementation : schemaImplementations) { Schema oneOfSchemaObject = resolveSchemaFromType(schemaImplementation, components, jsonViewAnnotation, openapi31, null, null, context); - ((ComposedSchema) schemaObject).addOneOfItem(oneOfSchemaObject); + schemaObject.addOneOfItem(oneOfSchemaObject); } } if (schema.anyOf().length > 0) { Class[] schemaImplementations = schema.anyOf(); for (Class schemaImplementation : schemaImplementations) { Schema anyOfSchemaObject = resolveSchemaFromType(schemaImplementation, components, jsonViewAnnotation, openapi31, null, null, context); - ((ComposedSchema) schemaObject).addAnyOfItem(anyOfSchemaObject); + schemaObject.addAnyOfItem(anyOfSchemaObject); } } if (schema.allOf().length > 0) { Class[] schemaImplementations = schema.allOf(); for (Class schemaImplementation : schemaImplementations) { Schema allOfSchemaObject = resolveSchemaFromType(schemaImplementation, components, jsonViewAnnotation, openapi31, null, null, context); - ((ComposedSchema) schemaObject).addAllOfItem(allOfSchemaObject); + schemaObject.addAllOfItem(allOfSchemaObject); } } if (schema.additionalProperties().equals(io.swagger.v3.oas.annotations.media.Schema.AdditionalPropertiesValue.TRUE)) { @@ -1956,11 +1981,15 @@ public static void updateAnnotation(Class clazz, io.swagger.v3.oas.annotation } + public static Annotation mergeSchemaAnnotations( + Annotation[] ctxAnnotations, JavaType type) { + return mergeSchemaAnnotations(ctxAnnotations, type, false); + } /* * returns null if no annotations, otherwise either ArraySchema or Schema */ public static Annotation mergeSchemaAnnotations( - Annotation[] ctxAnnotations, JavaType type) { + Annotation[] ctxAnnotations, JavaType type, boolean contextWins) { // get type array and schema io.swagger.v3.oas.annotations.media.Schema tS = type.getRawClass().getDeclaredAnnotation(io.swagger.v3.oas.annotations.media.Schema.class); if (!hasSchemaAnnotation(tS)) { @@ -2020,6 +2049,9 @@ else if (tS != null && tA == null && cS == null && cA != null) { } else if (tA != null && cA != null) { + if (contextWins) { + return mergeArraySchemaAnnotations(tA, cA); + } return mergeArraySchemaAnnotations(cA, tA); } @@ -2028,6 +2060,9 @@ else if (tS != null && cS == null && cA == null) { } else if (tS != null && cS != null) { + if (contextWins) { + return mergeSchemaAnnotations(cS, tS); + } return mergeSchemaAnnotations(tS, cS); } @@ -2623,6 +2658,15 @@ public Class additionalPropertiesSchema() { return patch.additionalPropertiesSchema(); } + /* We always want the patch to take precedence in schema resolution behavior */ + @Override + public SchemaResolution schemaResolution() { + if (!patch.schemaResolution().equals(SchemaResolution.DEFAULT) || master.schemaResolution().equals(SchemaResolution.DEFAULT)) { + return patch.schemaResolution(); + } + return master.schemaResolution(); + } + }; return (io.swagger.v3.oas.annotations.media.Schema)schema; @@ -2839,4 +2883,10 @@ public io.swagger.v3.oas.annotations.media.Schema[] prefixItems() { return (io.swagger.v3.oas.annotations.media.ArraySchema)newArraySchema; } + public static Schema.SchemaResolution resolveSchemaResolution(Schema.SchemaResolution globalSchemaResolution, io.swagger.v3.oas.annotations.media.Schema schemaAnnotation) { + if (schemaAnnotation != null && !io.swagger.v3.oas.annotations.media.Schema.SchemaResolution.AUTO.equals(schemaAnnotation.schemaResolution())) { + return Schema.SchemaResolution.valueOf(schemaAnnotation.schemaResolution().toString()); + } + return globalSchemaResolution; + } } diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Json.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Json.java index 84c318fd62..899c7476ed 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Json.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Json.java @@ -6,13 +6,12 @@ public class Json { - private static ObjectMapper mapper; + private static final class ObjectMapperHolder { + private static final ObjectMapper MAPPER = ObjectMapperFactory.createJson(); + } public static ObjectMapper mapper() { - if (mapper == null) { - mapper = ObjectMapperFactory.createJson(); - } - return mapper; + return ObjectMapperHolder.MAPPER; } public static ObjectWriter pretty() { diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Json31.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Json31.java index 16a883c090..0ac52783a8 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Json31.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Json31.java @@ -13,23 +13,22 @@ public class Json31 { - private static ObjectMapper mapper; - private static ObjectMapper converterMapper; + private static final class ObjectMapperHolder { + private static final ObjectMapper MAPPER = ObjectMapperFactory.createJson31(); + } + + private static final class ConverterMapperHolder { + private static final ObjectMapper MAPPER = ObjectMapperFactory.createJsonConverter(); + } static Logger LOGGER = LoggerFactory.getLogger(Json31.class); public static ObjectMapper mapper() { - if (mapper == null) { - mapper = ObjectMapperFactory.createJson31(); - } - return mapper; + return ObjectMapperHolder.MAPPER; } public static ObjectMapper converterMapper() { - if (converterMapper == null) { - converterMapper = ObjectMapperFactory.createJsonConverter(); - } - return converterMapper; + return ConverterMapperHolder.MAPPER; } public static ObjectWriter pretty() { diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/OpenAPI30To31.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/OpenAPI30To31.java index 967ccf02fa..e2cc8fafc9 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/OpenAPI30To31.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/OpenAPI30To31.java @@ -10,7 +10,7 @@ public class OpenAPI30To31 { public void process(OpenAPI openAPI) { openAPI.openapi("3.1.0") - .jsonSchemaDialect("https://json-schema.org/draft/2020-12/schema") + .jsonSchemaDialect("https://spec.openapis.org/oas/3.1/dialect/base") .specVersion(SpecVersion.V31); removeReservedExtensionsName(openAPI.getExtensions()); diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java index 5c977bdfed..18e1277328 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java @@ -42,6 +42,19 @@ public static Parameter applyAnnotations( String[] methodTypes, JsonView jsonViewAnnotation, boolean openapi31) { + return applyAnnotations(parameter, type, annotations, components, classTypes, methodTypes, jsonViewAnnotation, openapi31, null); + } + + public static Parameter applyAnnotations( + Parameter parameter, + Type type, + List annotations, + Components components, + String[] classTypes, + String[] methodTypes, + JsonView jsonViewAnnotation, + boolean openapi31, + Schema.SchemaResolution schemaResolution) { final AnnotationsHelper helper = new AnnotationsHelper(annotations, type); if (helper.isContext()) { @@ -59,17 +72,60 @@ public static Parameter applyAnnotations( if (paramSchemaOrArrayAnnotation != null) { reworkedAnnotations.add(paramSchemaOrArrayAnnotation); } + io.swagger.v3.oas.annotations.media.Schema ctxSchema = AnnotationsUtils.getSchemaAnnotation(annotations.toArray(new Annotation[0])); + io.swagger.v3.oas.annotations.media.ArraySchema ctxArraySchema = AnnotationsUtils.getArraySchemaAnnotation(annotations.toArray(new Annotation[0])); + Annotation[] ctxAnnotation31 = null; + + if (Schema.SchemaResolution.ALL_OF.equals(schemaResolution) || Schema.SchemaResolution.ALL_OF_REF.equals(schemaResolution)) { + List ctxAnnotations31List = new ArrayList<>(); + if (annotations != null) { + for (Annotation a : annotations) { + if ( + !(a instanceof io.swagger.v3.oas.annotations.media.Schema) && + !(a instanceof io.swagger.v3.oas.annotations.media.ArraySchema)) { + ctxAnnotations31List.add(a); + } + } + ctxAnnotation31 = ctxAnnotations31List.toArray(new Annotation[ctxAnnotations31List.size()]); + } + } AnnotatedType annotatedType = new AnnotatedType() .type(type) .resolveAsRef(true) .skipOverride(true) - .jsonViewAnnotation(jsonViewAnnotation) - .ctxAnnotations(reworkedAnnotations.toArray(new Annotation[reworkedAnnotations.size()])); + .jsonViewAnnotation(jsonViewAnnotation); + + if (Schema.SchemaResolution.ALL_OF.equals(schemaResolution) || Schema.SchemaResolution.ALL_OF_REF.equals(schemaResolution)) { + annotatedType.ctxAnnotations(ctxAnnotation31); + } else { + annotatedType.ctxAnnotations(reworkedAnnotations.toArray(new Annotation[reworkedAnnotations.size()])); + } - final ResolvedSchema resolvedSchema = ModelConverters.getInstance(openapi31).resolveAsResolvedSchema(annotatedType); + final ResolvedSchema resolvedSchema = ModelConverters.getInstance(openapi31, schemaResolution).resolveAsResolvedSchema(annotatedType); if (resolvedSchema.schema != null) { - parameter.setSchema(resolvedSchema.schema); + Schema resSchema = AnnotationsUtils.clone(resolvedSchema.schema, openapi31); + Schema ctxSchemaObject = null; + if (Schema.SchemaResolution.ALL_OF.equals(schemaResolution) || Schema.SchemaResolution.ALL_OF_REF.equals(schemaResolution)) { + Optional reResolvedSchema = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, null, schemaResolution, null); + if (reResolvedSchema.isPresent()) { + ctxSchemaObject = reResolvedSchema.get(); + } + reResolvedSchema = AnnotationsUtils.getArraySchema(ctxArraySchema, annotatedType.getComponents(), null, openapi31, ctxSchemaObject); + if (reResolvedSchema.isPresent()) { + ctxSchemaObject = reResolvedSchema.get(); + } + + } + if (Schema.SchemaResolution.ALL_OF.equals(schemaResolution) && ctxSchemaObject != null) { + resSchema = new Schema() + .addAllOfItem(ctxSchemaObject) + .addAllOfItem(resolvedSchema.schema); + } else if (Schema.SchemaResolution.ALL_OF_REF.equals(schemaResolution) && ctxSchemaObject != null) { + resSchema = ctxSchemaObject + .addAllOfItem(resolvedSchema.schema); + } + parameter.setSchema(resSchema); } resolvedSchema.referencedSchemas.forEach(components::addSchemas); diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Yaml.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Yaml.java index eaef8709fb..2a77b70305 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Yaml.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Yaml.java @@ -5,13 +5,13 @@ import com.fasterxml.jackson.databind.ObjectWriter; public class Yaml { - static ObjectMapper mapper; + + private static final class ObjectMapperHolder { + private static final ObjectMapper MAPPER = ObjectMapperFactory.createYaml(); + } public static ObjectMapper mapper() { - if (mapper == null) { - mapper = ObjectMapperFactory.createYaml(); - } - return mapper; + return ObjectMapperHolder.MAPPER; } public static ObjectWriter pretty() { diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Yaml31.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Yaml31.java index ba16433896..75d9819a84 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Yaml31.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Yaml31.java @@ -11,15 +11,16 @@ import java.util.Map; public class Yaml31 { - static ObjectMapper mapper; + + private static final class ObjectMapperHolder { + private static final ObjectMapper MAPPER = ObjectMapperFactory.createYaml31(); + } + static Logger LOGGER = LoggerFactory.getLogger(Yaml31.class); public static ObjectMapper mapper() { - if (mapper == null) { - mapper = ObjectMapperFactory.createYaml31(); - } - return mapper; + return ObjectMapperHolder.MAPPER; } public static ObjectWriter pretty() { diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/filter/SpecFilterTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/filter/SpecFilterTest.java index f04c9b56e7..355d4722e2 100644 --- a/modules/swagger-core/src/test/java/io/swagger/v3/core/filter/SpecFilterTest.java +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/filter/SpecFilterTest.java @@ -16,6 +16,8 @@ import io.swagger.v3.core.matchers.SerializationMatchers; import io.swagger.v3.core.util.Json; import io.swagger.v3.core.util.Json31; +import io.swagger.v3.core.util.Yaml; +import io.swagger.v3.core.util.Yaml31; import io.swagger.v3.core.util.ResourceUtils; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; @@ -46,6 +48,8 @@ public class SpecFilterTest { private static final String RESOURCE_RECURSIVE_MODELS = "specFiles/recursivemodels.json"; private static final String RESOURCE_PATH = "specFiles/petstore-3.0-v2.json"; private static final String RESOURCE_PATH_3303 = "specFiles/petstore-3.0-v2-ticket-3303.json"; + private static final String RESOURCE_WITH_REF_DEFINITION_4737 = "specFiles/3.1.0/issue-4737-3.1.yaml"; + private static final String RESOURCE_WITH_REFERRED_DEFINITIONS= "specFiles/3.1.0/specWithReferredSchemas-3.1.yaml"; private static final String RESOURCE_PATH_LIST = "specFiles/3.1.0/list-3.1.json"; private static final String RESOURCE_PATH_COMPOSED_SCHEMA = "specFiles/3.1.0/composed-schema-3.1.json"; private static final String RESOURCE_REFERRED_SCHEMAS = "specFiles/petstore-3.0-referred-schemas.json"; @@ -450,6 +454,30 @@ public void filterWithNullDefinitions() throws IOException { assertNotNull(filtered); } + @Test(description = "RemoveUnreferencedDefinitionsFilter should not remove schema definition if ref used in Webhook") + public void testTicket4737() throws IOException { + final OpenAPI openAPI = getOpenAPIYaml31(RESOURCE_WITH_REF_DEFINITION_4737); + final RemoveUnreferencedDefinitionsFilter remover = new RemoveUnreferencedDefinitionsFilter(); + final OpenAPI filtered = new SpecFilter().filter(openAPI, remover, null, null, null); + assertNotNull(filtered.getComponents().getSchemas().get("RequestDto")); + } + + @Test + public void shouldNotRemoveUsedDefinitions() throws IOException { + final OpenAPI openAPI = getOpenAPIYaml31(RESOURCE_WITH_REFERRED_DEFINITIONS); + final RemoveUnreferencedDefinitionsFilter remover = new RemoveUnreferencedDefinitionsFilter(); + final OpenAPI filtered = new SpecFilter().filter(openAPI, remover, null, null, null); + assertNotNull(filtered.getComponents().getSchemas().get("ResponseDefinition")); + assertNotNull(filtered.getComponents().getSchemas().get("WebhookResponseDefinition")); + assertNotNull(filtered.getComponents().getSchemas().get("WebhookOperationDefinition")); + assertNotNull(filtered.getComponents().getSchemas().get("RequestBodyDefinition")); + assertNotNull(filtered.getComponents().getSchemas().get("ParameterDefinition")); + assertNotNull(filtered.getComponents().getSchemas().get("HeaderDefinition")); + assertNotNull(filtered.getComponents().getSchemas().get("CallbackDefinition")); + assertNotNull(filtered.getComponents().getSchemas().get("PathItemDefinition")); + assertNull(filtered.getComponents().getSchemas().get("UnusedDefinition")); + } + private Set getTagNames(OpenAPI openAPI) { Set result = new HashSet<>(); if (openAPI.getTags() != null) { @@ -469,4 +497,14 @@ private OpenAPI getOpenAPI31(String path) throws IOException { final String json = ResourceUtils.loadClassResource(getClass(), path); return Json31.mapper().readValue(json, OpenAPI.class); } + + private OpenAPI getOpenAPIYaml(String path) throws IOException { + final String yaml = ResourceUtils.loadClassResource(getClass(), path); + return Yaml.mapper().readValue(yaml, OpenAPI.class); + } + + private OpenAPI getOpenAPIYaml31(String path) throws IOException { + final String yaml = ResourceUtils.loadClassResource(getClass(), path); + return Yaml31.mapper().readValue(yaml, OpenAPI.class); + } } diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/AllofResolvingTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/AllofResolvingTest.java new file mode 100644 index 0000000000..bbec9310c1 --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/AllofResolvingTest.java @@ -0,0 +1,165 @@ +package io.swagger.v3.core.resolving; + +import io.swagger.v3.core.converter.AnnotatedType; +import io.swagger.v3.core.converter.ModelConverterContextImpl; +import io.swagger.v3.core.jackson.ModelResolver; +import io.swagger.v3.core.matchers.SerializationMatchers; +import io.swagger.v3.oas.annotations.media.Schema; +import org.testng.annotations.Test; + +public class AllofResolvingTest extends SwaggerTestBase { + + @Test + public void testAllofResolving() { + + final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(false).schemaResolution(io.swagger.v3.oas.models.media.Schema.SchemaResolution.ALL_OF); + final ModelConverterContextImpl c = new ModelConverterContextImpl(modelResolver); + // ModelConverters c = ModelConverters.getInstance(false, io.swagger.v3.oas.models.media.Schema.SchemaResolution.INLINE); + c.resolve(new AnnotatedType(UserSchema.class)); + + String expectedYaml = "UserProperty:\n" + + " type: object\n" + + " description: Represents a user-specific property\n" + + " example: User-specific example value\n" + + "UserSchema:\n" + + " type: object\n" + + " properties:\n" + + " propertyOne:\n" + + " allOf:\n" + + " - type: object\n" + + " description: First user schema property\n" + + " nullable: true\n" + + " - $ref: '#/components/schemas/UserProperty'\n" + + " propertyTwo:\n" + + " allOf:\n" + + " - type: object\n" + + " description: Second user schema property\n" + + " example: example value for propertyTwo\n" + + " - $ref: '#/components/schemas/UserProperty'\n" + + " propertyThree:\n" + + " allOf:\n" + + " - type: object\n" + + " description: \"Third user schema property, with example for testing\"\n" + + " example: example value for propertyThree\n" + + " - $ref: '#/components/schemas/UserProperty'\n"; + + SerializationMatchers.assertEqualsToYaml(c.getDefinedModels(), expectedYaml); + // stringSchemaMap = c.readAll(InlineSchemaSecond.class); + c.resolve(new AnnotatedType(OrderSchema.class)); + expectedYaml = "BasicProperty:\n" + + " type: object\n" + + " description: Represents a basic schema property\n" + + "OrderProperty:\n" + + " type: object\n" + + " properties:\n" + + " basicProperty:\n" + + " $ref: '#/components/schemas/BasicProperty'\n" + + " description: Represents an order-specific property\n" + + " example: Order-specific example value\n" + + "OrderSchema:\n" + + " type: object\n" + + " properties:\n" + + " propertyOne:\n" + + " allOf:\n" + + " - type: object\n" + + " description: First order schema property\n" + + " nullable: true\n" + + " - $ref: '#/components/schemas/OrderProperty'\n" + + " userProperty:\n" + + " allOf:\n" + + " - type: object\n" + + " description: \"Order schema property, references UserProperty\"\n" + + " example: example value for userProperty\n" + + " - $ref: '#/components/schemas/UserProperty'\n" + + "UserProperty:\n" + + " type: object\n" + + " description: Represents a user-specific property\n" + + " example: User-specific example value\n" + + "UserSchema:\n" + + " type: object\n" + + " properties:\n" + + " propertyOne:\n" + + " allOf:\n" + + " - type: object\n" + + " description: First user schema property\n" + + " nullable: true\n" + + " - $ref: '#/components/schemas/UserProperty'\n" + + " propertyTwo:\n" + + " allOf:\n" + + " - type: object\n" + + " description: Second user schema property\n" + + " example: example value for propertyTwo\n" + + " - $ref: '#/components/schemas/UserProperty'\n" + + " propertyThree:\n" + + " allOf:\n" + + " - type: object\n" + + " description: \"Third user schema property, with example for testing\"\n" + + " example: example value for propertyThree\n" + + " - $ref: '#/components/schemas/UserProperty'\n"; + SerializationMatchers.assertEqualsToYaml(c.getDefinedModels(), expectedYaml); + } + + // Renamed class to better describe what it represents + static class UserSchema { + + @Schema(description = "First user schema property", nullable = true) + public UserProperty propertyOne; + + private UserProperty propertyTwo; + + @Schema(description = "Second user schema property", example = "example value for propertyTwo") + public UserProperty getPropertyTwo() { + return propertyTwo; + } + + // Third property with no specific annotation. It's good to add some description or example for clarity + @Schema(description = "Third user schema property, with example for testing", example = "example value for propertyThree") + public UserProperty getPropertyThree() { + return null; // returning null as per the test scenario + } + } + + // Renamed class to represent a different entity for the schema test + static class OrderSchema { + + @Schema(description = "First order schema property", nullable = true) + public OrderProperty propertyOne; + + private UserProperty userProperty; + + @Schema(description = "Order schema property, references UserProperty", example = "example value for userProperty") + public UserProperty getUserProperty() { + return userProperty; + } + } + + // Renamed properties to make them clearer about their role in the schema + @Schema(description = "Represents a user-specific property", example = "User-specific example value") + static class UserProperty { + // public String value; + } + + @Schema(description = "Represents an order-specific property", example = "Order-specific example value") + static class OrderProperty { + public BasicProperty basicProperty; + } + + static class BasicSchema { + + @Schema(description = "First basic schema property") + public BasicProperty propertyOne; + + private BasicProperty propertyTwo; + + @Schema(description = "Second basic schema property", example = "example value for propertyTwo") + public BasicProperty getPropertyTwo() { + return propertyTwo; + } + } + + // Renamed to represent a basic property common in various schemas + @Schema(description = "Represents a basic schema property") + static class BasicProperty { + // public String value; + } +} diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/InlineResolvingTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/InlineResolvingTest.java new file mode 100644 index 0000000000..42afe5c0f9 --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/InlineResolvingTest.java @@ -0,0 +1,172 @@ +package io.swagger.v3.core.resolving; + +import io.swagger.v3.core.converter.AnnotatedType; +import io.swagger.v3.core.converter.ModelConverterContextImpl; +import io.swagger.v3.core.jackson.ModelResolver; +import io.swagger.v3.core.matchers.SerializationMatchers; +import io.swagger.v3.oas.annotations.media.Schema; +import org.testng.annotations.Test; + +public class InlineResolvingTest extends SwaggerTestBase{ + + @Test + public void testInlineResolving() { + + final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(false).schemaResolution(io.swagger.v3.oas.models.media.Schema.SchemaResolution.INLINE); + final ModelConverterContextImpl c = new ModelConverterContextImpl(modelResolver); + // ModelConverters c = ModelConverters.getInstance(false, io.swagger.v3.oas.models.media.Schema.SchemaResolution.INLINE); + c.resolve(new AnnotatedType(InlineSchemaFirst.class)); + + String expectedYaml = "InlineSchemaFirst:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " description: InlineSchemaFirst property 1\n" + + " nullable: true\n" + + " example: example\n" + + " property2:\n" + + " type: object\n" + + " description: ' InlineSchemaFirst property 2'\n" + + " example: example 2\n" + + "InlineSchemaPropertyFirst:\n" + + " type: object\n" + + " description: property\n" + + " example: example\n"; + + SerializationMatchers.assertEqualsToYaml(c.getDefinedModels(), expectedYaml); + // stringSchemaMap = c.readAll(InlineSchemaSecond.class); + c.resolve(new AnnotatedType(InlineSchemaSecond.class)); + expectedYaml = "InlineSchemaFirst:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " description: InlineSchemaFirst property 1\n" + + " nullable: true\n" + + " example: example\n" + + " property2:\n" + + " type: object\n" + + " description: ' InlineSchemaFirst property 2'\n" + + " example: example 2\n" + + "InlineSchemaPropertyFirst:\n" + + " type: object\n" + + " description: property\n" + + " example: example\n" + + "InlineSchemaPropertySecond:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " description: property 2\n" + + " example: example\n" + + " description: propertysecond\n" + + " nullable: true\n" + + " example: examplesecond\n" + + "InlineSchemaPropertySimple:\n" + + " type: object\n" + + " description: property\n" + + " example: example\n" + + "InlineSchemaSecond:\n" + + " type: object\n" + + " properties:\n" + + " propertySecond1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " description: property 2\n" + + " example: example\n" + + " description: InlineSchemaSecond property 1\n" + + " nullable: true\n" + + " example: examplesecond\n" + + " property2:\n" + + " type: object\n" + + " description: InlineSchemaSecond property 2\n" + + " example: InlineSchemaSecond example 2\n" + + "InlineSchemaSimple:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " description: property 2\n" + + " example: example\n"; + SerializationMatchers.assertEqualsToYaml(c.getDefinedModels(), expectedYaml); + } + + static class InlineSchemaFirst { + + // public String foo; + + @Schema(description = "InlineSchemaFirst property 1", nullable = true) + public InlineSchemaPropertyFirst property1; + + + private InlineSchemaPropertyFirst property2; + + @Schema(description = " InlineSchemaFirst property 2", example = "example 2") + public InlineSchemaPropertyFirst getProperty2() { + return null; + } + } + + static class InlineSchemaSecond { + + // public String foo; + + @Schema(description = "InlineSchemaSecond property 1", nullable = true) + public InlineSchemaPropertySecond propertySecond1; + + + private InlineSchemaPropertyFirst property2; + + @Schema(description = "InlineSchemaSecond property 2", example = "InlineSchemaSecond example 2") + public InlineSchemaPropertyFirst getProperty2() { + return null; + } + } + + @Schema(description = "property", example = "example") + static class InlineSchemaPropertyFirst { + // public String bar; + } + + @Schema(description = "propertysecond", example = "examplesecond") + static class InlineSchemaPropertySecond { + public InlineSchemaSimple bar; + } + + static class InlineSchemaSimple { + + @Schema(description = "property 1") + public InlineSchemaPropertySimple property1; + + + private InlineSchemaPropertySimple property2; + + @Schema(description = "property 2", example = "example") + public InlineSchemaPropertySimple getProperty2() { + return null; + } + } + + @Schema(description = "property") + static class InlineSchemaPropertySimple { + // public String bar; + } +} diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket4679Test.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket4679Test.java index 9998f8b6a2..8c51838b3a 100644 --- a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket4679Test.java +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket4679Test.java @@ -15,6 +15,7 @@ public class Ticket4679Test extends SwaggerTestBase{ public void testCustomSchemaImplementation() { String expectedYaml = "ModelWithCustomSchemaImplementationInProperty:\n" + + " type: object\n" + " properties:\n" + " exampleField:\n" + " type: integer\n" + diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/resources/TestArrayType.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/resources/TestArrayType.java new file mode 100644 index 0000000000..3c180c22b7 --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/resources/TestArrayType.java @@ -0,0 +1,30 @@ +package io.swagger.v3.core.resolving.resources; + +import io.swagger.v3.oas.annotations.media.ArraySchema; + +import java.util.ArrayList; +import java.util.List; + +public class TestArrayType { + + private Integer id; + + @ArraySchema(maxItems = 10) + private List names; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public List getNames() { + return names; + } + + public void setNames(List names) { + this.names = names; + } +} diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/resources/TestObject4715.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/resources/TestObject4715.java new file mode 100644 index 0000000000..f3e9914135 --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/resources/TestObject4715.java @@ -0,0 +1,34 @@ +package io.swagger.v3.core.resolving.resources; + +public class TestObject4715 { + + private String foo; + + private String bar; + + private Integer id; + + public String getFoo() { + return foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } + + public String getBar() { + return bar; + } + + public void setBar(String bar) { + this.bar = bar; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } +} diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/ModelResolverOAS31Test.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/ModelResolverOAS31Test.java index f9aa3ce83c..12e2ca78fc 100644 --- a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/ModelResolverOAS31Test.java +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/ModelResolverOAS31Test.java @@ -2,16 +2,23 @@ import io.swagger.v3.core.converter.AnnotatedType; import io.swagger.v3.core.converter.ModelConverterContextImpl; +import io.swagger.v3.core.converter.ModelConverters; import io.swagger.v3.core.jackson.ModelResolver; import io.swagger.v3.core.matchers.SerializationMatchers; import io.swagger.v3.core.resolving.SwaggerTestBase; +import io.swagger.v3.core.resolving.resources.TestArrayType; +import io.swagger.v3.core.resolving.resources.TestObject4715; import io.swagger.v3.core.resolving.v31.model.AnnotatedArray; import io.swagger.v3.core.resolving.v31.model.ModelWithDependentSchema; import io.swagger.v3.core.resolving.v31.model.ModelWithOAS31Stuff; -import io.swagger.v3.core.resolving.v31.model.ModelWithOAS31StuffMinimal; -import io.swagger.v3.core.util.Yaml31; import io.swagger.v3.oas.models.media.Schema; import org.testng.annotations.Test; +import javax.validation.constraints.DecimalMax; +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import java.util.List; +import java.util.Map; public class ModelResolverOAS31Test extends SwaggerTestBase { @@ -39,6 +46,7 @@ public void testOAS31Fields() { final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver); Schema model = context.resolve(new AnnotatedType(ModelWithOAS31Stuff.class)); SerializationMatchers.assertEqualsToYaml31(context.getDefinedModels(), "Address:\n" + + " type: object\n" + " if:\n" + " $ref: '#/components/schemas/AnnotatedCountry'\n" + " then:\n" + @@ -59,10 +67,12 @@ public void testOAS31Fields() { " propertyNames:\n" + " $ref: '#/components/schemas/PropertyNamesPattern'\n" + "AnnotatedCountry:\n" + + " type: object\n" + " properties:\n" + " country:\n" + " const: United States\n" + "Client:\n" + + " type: object\n" + " properties:\n" + " name:\n" + " type: string\n" + @@ -70,6 +80,7 @@ public void testOAS31Fields() { " type: integer\n" + " format: int32\n" + "CreditCard:\n" + + " type: object\n" + " properties:\n" + " billingAddress:\n" + " type: string\n" + @@ -124,6 +135,7 @@ public void testOAS31Fields() { " properties:\n" + " extraObject: {}\n" + "MultipleBaseBean:\n" + + " type: object\n" + " description: MultipleBaseBean\n" + " properties:\n" + " beanType:\n" + @@ -152,14 +164,17 @@ public void testOAS31Fields() { " format: int32\n" + " description: MultipleSub2Bean\n" + "PostalCodeNumberPattern:\n" + + " type: object\n" + " properties:\n" + " postalCode:\n" + " pattern: \"[0-9]{5}(-[0-9]{4})?\"\n" + "PostalCodePattern:\n" + + " type: object\n" + " properties:\n" + " postalCode:\n" + " pattern: \"[A-Z][0-9][A-Z] [0-9][A-Z][0-9]\"\n" + "PropertyNamesPattern:\n" + + " type: object\n" + " pattern: \"^[A-Za-z_][A-Za-z0-9_]*$\"\n"); } @@ -170,10 +185,12 @@ public void testDependentSchemasAnnotation() { io.swagger.v3.oas.models.media.Schema model = context.resolve(new AnnotatedType(ModelWithDependentSchema.class)); SerializationMatchers.assertEqualsToYaml31(context.getDefinedModels(), "BooleanFakeClass:\n" + + " type: object\n" + " properties:\n" + " type:\n" + " type: boolean\n" + "ModelWithDependentSchema:\n" + + " type: object\n" + " dependentSchemas:\n" + " value:\n" + " properties:\n" + @@ -187,4 +204,147 @@ public void testDependentSchemasAnnotation() { " format: int32\n"); } + @Test(description = "Top level type:object should appear in OAS31") + public void testObjectTypeSchemaOAS31(){ + final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(true); + final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver); + io.swagger.v3.oas.models.media.Schema model = context.resolve(new AnnotatedType(TestObject4715.class)); + SerializationMatchers.assertEqualsToYaml31(model, "type: object\n" + + "properties:\n" + + " foo:\n" + + " type: string\n" + + " bar:\n" + + " type: string\n" + + " id:\n" + + " type: integer\n" + + " format: int32"); + } + + @Test + public void testFieldArraySchemaAnnotation() { + final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(true); + final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver); + io.swagger.v3.oas.models.media.Schema model = context.resolve(new AnnotatedType(TestArrayType.class)); + SerializationMatchers.assertEqualsToYaml31(model, " type: object\n" + + " properties:\n" + + " id:\n" + + " type: integer\n" + + " format: int32\n" + + " names:\n" + + " type: array\n" + + " items:\n" + + " type: string\n" + + " maxItems: 10"); + } + + @Test(description = "@Pattern correctly handled in type parameters of properties using collections when using oas 3.1.0") + public void testModelUsingCollectionTypePropertyHandlesPatternAnnotationForOas31() { + String expectedYaml = "ClassWithUsingPatternOnCollection:\n" + + " type: object\n" + + " properties:\n" + + " myField:\n" + + " type: array\n" + + " items:\n" + + " pattern: myPattern\n" + + " type: string"; + + Map stringSchemaMap = ModelConverters.getInstance(true).readAll(ClassWithUsingPatternOnCollection.class); + SerializationMatchers.assertEqualsToYaml31(stringSchemaMap, expectedYaml); + } + + private static class ClassWithUsingPatternOnCollection { + private List<@Pattern(regexp = "myPattern") String> myField; + + public List getMyField() { + return myField; + } + + public void setMyField(List myField) { + this.myField = myField; + } + } + + @Test(description = "@Size correctly handled in properties using collections when using oas 3.1.0") + public void testModelUsingCollectionTypePropertyHandleSizeAnnotationForOas31() { + String expectedYaml = "ClassWithUsingSizeOnCollection:\n" + + " type: object\n" + + " properties:\n" + + " myField:\n" + + " maxItems: 100\n" + + " minItems: 1\n" + + " type: array\n" + + " items:\n" + + " type: string"; + + Map stringSchemaMap = ModelConverters.getInstance(true).readAll(ClassWithUsingSizeOnCollection.class); + SerializationMatchers.assertEqualsToYaml31(stringSchemaMap, expectedYaml); + } + + private static class ClassWithUsingSizeOnCollection { + @Size(min = 1, max = 100) + private List myField; + + public List getMyField() { + return myField; + } + + public void setMyField(List myField) { + this.myField = myField; + } + } + + @Test(description = "@Size correctly handled for field type String using OAS 3.1.0") + public void testSizeAnnotationOnFieldForOAS31() { + String expectedYaml = "ClassWithUsingSizeOnField:\n" + + " type: object\n" + + " properties:\n" + + " myField:\n" + + " type: string\n" + + " maxLength: 100\n" + + " minLength: 1"; + + Map stringSchemaMap = ModelConverters.getInstance(true).readAll(ClassWithUsingSizeOnField.class); + SerializationMatchers.assertEqualsToYaml31(stringSchemaMap, expectedYaml); + } + + private static class ClassWithUsingSizeOnField { + @Size(min = 1, max = 100) + private String myField; + + public String getMyField() { + return myField; + } + + public void setMyField(String myField) { + this.myField = myField; + } + } + + @Test(description = "@DecimalMax/Min annotations correctly handled for field type Number using OAS 3.1.0") + public void testDecimalAnnotationsOnField() { + String expectedYaml = "ClassWithUsingDecimalAnnotationsOnField:\n" + + " type: object\n" + + " properties:\n" + + " myField:\n" + + " type: number\n" + + " maximum: 100\n" + + " minimum: 1"; + + Map stringSchemaMap = ModelConverters.getInstance(true).readAll(ClassWithUsingDecimalAnnotationsOnField.class); + SerializationMatchers.assertEqualsToYaml31(stringSchemaMap, expectedYaml); + } + + private static class ClassWithUsingDecimalAnnotationsOnField { + @DecimalMin("1") + @DecimalMax("100") + private Number myField; + + public Number getMyField() { + return myField; + } + + public void setMyField(Number myField) { + this.myField = myField; + } + } } diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/OpenAPI3_1SerializationTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/OpenAPI3_1SerializationTest.java index 97c5d02d98..d0da2377d7 100644 --- a/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/OpenAPI3_1SerializationTest.java +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/OpenAPI3_1SerializationTest.java @@ -1468,11 +1468,11 @@ public void testBooleanAdditionalPropertiesSerialization() throws Exception{ openAPI = Yaml31.mapper().readValue(expectedYaml, OpenAPI.class); ser = Yaml31.pretty(openAPI); - assertEquals(ser, withJacksonSystemLineSeparator(expectedYaml)); + assertEquals(ser, expectedYaml); assertTrue(Boolean.TRUE.equals(openAPI.getComponents().getSchemas().get("test").getAdditionalProperties())); openAPI = Yaml.mapper().readValue(expectedYaml, OpenAPI.class); ser = Yaml.pretty(openAPI); - assertEquals(ser, withJacksonSystemLineSeparator(expectedYaml)); + assertEquals(ser, expectedYaml); assertTrue(Boolean.TRUE.equals(openAPI.getComponents().getSchemas().get("test").getAdditionalProperties())); expectedJson = "{\n" + @@ -1505,11 +1505,11 @@ public void testBooleanAdditionalPropertiesSerialization() throws Exception{ openAPI = Yaml31.mapper().readValue(expectedYaml, OpenAPI.class); ser = Yaml31.pretty(openAPI); - assertEquals(ser, withJacksonSystemLineSeparator(expectedYaml)); + assertEquals(ser, expectedYaml); assertTrue(Boolean.TRUE.equals(openAPI.getComponents().getSchemas().get("test").getAdditionalProperties())); openAPI = Yaml.mapper().readValue(expectedYaml, OpenAPI.class); ser = Yaml.pretty(openAPI); - assertEquals(ser, withJacksonSystemLineSeparator(expectedYaml)); + assertEquals(ser, expectedYaml); assertTrue(Boolean.TRUE.equals(openAPI.getComponents().getSchemas().get("test").getAdditionalProperties())); } diff --git a/modules/swagger-core/src/test/resources/specFiles/3.1.0/issue-4737-3.1.yaml b/modules/swagger-core/src/test/resources/specFiles/3.1.0/issue-4737-3.1.yaml new file mode 100644 index 0000000000..1e1615a4f2 --- /dev/null +++ b/modules/swagger-core/src/test/resources/specFiles/3.1.0/issue-4737-3.1.yaml @@ -0,0 +1,29 @@ +openapi: 3.1.0 +info: + title: OpenAPI definition + version: v0 +servers: + - url: http://localhost + description: Generated server url +paths: {} +components: + schemas: + RequestDto: + properties: + personalNumber: + type: string +webhooks: + newPet: + post: + requestBody: + description: Information about a new pet in the system + content: + application/json: + schema: + $ref: '#/components/schemas/RequestDto' + description: Webhook Pet + responses: + '200': + description: >- + Return a 200 status to indicate that the data was received + successfully \ No newline at end of file diff --git a/modules/swagger-core/src/test/resources/specFiles/3.1.0/specWithReferredSchemas-3.1.yaml b/modules/swagger-core/src/test/resources/specFiles/3.1.0/specWithReferredSchemas-3.1.yaml new file mode 100644 index 0000000000..bfc9e05cf4 --- /dev/null +++ b/modules/swagger-core/src/test/resources/specFiles/3.1.0/specWithReferredSchemas-3.1.yaml @@ -0,0 +1,124 @@ +openapi: 3.1.0 +info: + title: OpenAPI definition + version: v0 +servers: + - url: http://localhost + description: Generated server url +paths: + /users: + get: + summary: Get list of all users + operationId: usersList + responses: + 200: + description: OK + content: + application/json: + schema: + type: string + maxLength: 100 + minLength: 1 +components: + schemas: + ResponseDefinition: + properties: + personalNumber: + type: string + WebhookResponseDefinition: + properties: + personalNumber: + type: integer + WebhookOperationDefinition: + properties: + personalNumber: + type: number + RequestBodyDefinition: + properties: + personalNumber: + type: string + ParameterDefinition: + properties: + parameterName: + type: string + HeaderDefinition: + type: string + description: header ref + CallbackDefinition: + description: callback ref + type: string + PathItemDefinition: + description: pathItem ref + type: string + UnusedDefinition: + description: unused definition + type: string + minLength: 1 + maxLength: 100 + responses: + OK: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ResponseDefinition" + requestBodies: + requestBody1: + description: request body + content: + application/json: + schema: + $ref: "#/components/schemas/RequestBodyDefinition" + parameters: + parameter1: + description: request body + name: parameter + in: query + content: + application/json: + schema: + $ref: "#/components/schemas/ParameterDefinition" + headers: + header1: + content: + application/json: + schema: + $ref: "#/components/schemas/HeaderDefinition" + callbacks: + myEvent: # Event name + "{$request.body#/callbackUrl}": + post: + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CallbackDefinition" + pathItems: + pathItem1: + get: + summary: Get list of all users + operationId: usersList + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/PathItemDefinition" +webhooks: + newPet: + post: + requestBody: + description: Information about a new pet in the system + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookOperationDefinition' + description: Webhook Pet + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookResponseDefinition' diff --git a/modules/swagger-eclipse-transformer-maven-plugin/pom.xml b/modules/swagger-eclipse-transformer-maven-plugin/pom.xml index d9f23d22fd..b9d5a61509 100644 --- a/modules/swagger-eclipse-transformer-maven-plugin/pom.xml +++ b/modules/swagger-eclipse-transformer-maven-plugin/pom.xml @@ -4,7 +4,7 @@ io.swagger.core.v3 swagger-project - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../.. 4.0.0 @@ -13,7 +13,7 @@ swagger-eclipse-transformer-maven-plugin maven-plugin - 3.9.6 + 3.9.9 0.20 3.3.0 1.2.6 @@ -94,7 +94,7 @@ org.apache.maven.plugin-tools maven-plugin-annotations - 3.10.2 + 3.15.0 provided @@ -127,7 +127,7 @@ org.codehaus.plexus plexus-component-annotations - 2.1.1 + 2.2.0 org.apache.maven diff --git a/modules/swagger-gradle-plugin/README.md b/modules/swagger-gradle-plugin/README.md index b353bc3240..b613c51807 100644 --- a/modules/swagger-gradle-plugin/README.md +++ b/modules/swagger-gradle-plugin/README.md @@ -26,7 +26,7 @@ Alternatively provide as value a classpath with the following dependencies (repl ``` plugins { - id "io.swagger.core.v3.swagger-gradle-plugin" version "2.2.23" + id "io.swagger.core.v3.swagger-gradle-plugin" version "2.2.25" } ``` ### Gradle 1.x and 2.0 @@ -43,7 +43,7 @@ buildscript { } } dependencies { - classpath "io.swagger.core.v3:swagger-gradle-plugin:2.2.23" + classpath "io.swagger.core.v3:swagger-gradle-plugin:2.2.25" } } @@ -97,6 +97,8 @@ Parameter | Description | Required | Default `contextId`|see [Context](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#context)|false| `outputPath`|**DEPRECATED** output path where file(s) are saved|false| `defaultResponseCode`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false| +`openapi31`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false | +`schemaResolution`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| DEFAULT | **Note** parameter `openApiFile` corresponds to [config](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties) openAPI. It points to a location of a file in YAML or JSON format representing the input spec that will be merged with the resolved spec. Typically used to add Info section, or any other meta data. An example of such file: @@ -121,5 +123,8 @@ info: Since version 2.1.6, `sortOutput` parameter is available, allowing to sort object properties and map keys alphabetically. Since version 2.1.6, `objectMapperProcessorClass` allows to configure also the ObjectMapper instance used to serialize the resolved OpenAPI Since version 2.1.9, `alwaysResolveAppPath` parameter is available, allowing to trigger resolving of Application Path from annotation also not in runtime (e.g. using servlet in separate application, or in maven plugin at build time, etc) +Since version 2.2.12, `openapi31` parameter is available, if set to true the resolved spec will be processed into a 3.1.0 specification by resolving according to OAS 3.1 rules Since version 2.1.15, `skipResolveAppPath` parameter is available, allowing to skip resolving of Application Path from annotation -Since version 2.2.17, `defaultResponseCode` parameter is available, allowing to set the code used when resolving responses with no http status code annotation \ No newline at end of file +Since version 2.2.17, `defaultResponseCode` parameter is available, allowing to set the code used when resolving responses with no http status code annotation +Since version 2.2.17, `defaultResponseCode` parameter is available, allowing to set the code used when resolving responses with no http status code annotation +Since version 2.2.24, `schemaResolution` parameter is available, allowing to specify how object schemas and object properties within schemas are resolved for OAS 3.0 specification \ No newline at end of file diff --git a/modules/swagger-gradle-plugin/gradle.properties b/modules/swagger-gradle-plugin/gradle.properties index 34cd5d274e..51973e53a2 100644 --- a/modules/swagger-gradle-plugin/gradle.properties +++ b/modules/swagger-gradle-plugin/gradle.properties @@ -1,2 +1,2 @@ -version=2.2.24-SNAPSHOT +version=2.2.26-SNAPSHOT jettyVersion=9.4.53.v20231009 diff --git a/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/SwaggerPlugin.java b/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/SwaggerPlugin.java index c68341250b..824e56c294 100644 --- a/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/SwaggerPlugin.java +++ b/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/SwaggerPlugin.java @@ -16,7 +16,7 @@ public void apply(Project project) { config.defaultDependencies(new Action() { public void execute(DependencySet dependencies) { dependencies.add(project.getDependencies().create("org.apache.commons:commons-lang3:3.12.0")); - dependencies.add(project.getDependencies().create("io.swagger.core.v3:swagger-jaxrs2:2.2.24-SNAPSHOT")); + dependencies.add(project.getDependencies().create("io.swagger.core.v3:swagger-jaxrs2:2.2.26-SNAPSHOT")); dependencies.add(project.getDependencies().create("javax.ws.rs:javax.ws.rs-api:2.1")); dependencies.add(project.getDependencies().create("javax.servlet:javax.servlet-api:3.1.0")); } diff --git a/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/tasks/ResolveTask.java b/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/tasks/ResolveTask.java index 87fcdc415f..c885bd593a 100644 --- a/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/tasks/ResolveTask.java +++ b/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/tasks/ResolveTask.java @@ -77,6 +77,8 @@ public enum Format {JSON, YAML, JSONANDYAML}; private Boolean convertToOpenAPI31 = false; + private String schemaResolution; + private String defaultResponseCode; @Input @@ -386,6 +388,19 @@ public void setConvertToOpenAPI31(Boolean convertToOpenAPI31) { } } + /** + * @since 2.2.24 + */ + @Input + @Optional + public String getSchemaResolution() { + return schemaResolution; + } + + public void setSchemaResolution(String schemaResolution) { + this.schemaResolution = schemaResolution; + } + @TaskAction public void resolve() throws GradleException { if (skip) { @@ -505,6 +520,9 @@ public void resolve() throws GradleException { method=swaggerLoaderClass.getDeclaredMethod("setConvertToOpenAPI31", Boolean.class); method.invoke(swaggerLoader, convertToOpenAPI31); + method=swaggerLoaderClass.getDeclaredMethod("setSchemaResolution", String.class); + method.invoke(swaggerLoader, schemaResolution); + method=swaggerLoaderClass.getDeclaredMethod("resolve"); Map specs = (Map)method.invoke(swaggerLoader); diff --git a/modules/swagger-gradle-plugin/src/test/java/io/swagger/v3/plugins/gradle/SwaggerResolveTest.java b/modules/swagger-gradle-plugin/src/test/java/io/swagger/v3/plugins/gradle/SwaggerResolveTest.java index 9768aa6c97..a0b6c294e7 100644 --- a/modules/swagger-gradle-plugin/src/test/java/io/swagger/v3/plugins/gradle/SwaggerResolveTest.java +++ b/modules/swagger-gradle-plugin/src/test/java/io/swagger/v3/plugins/gradle/SwaggerResolveTest.java @@ -81,7 +81,7 @@ public void testSwaggerResolveTask() throws IOException { " mavenCentral()\n" + "}\n" + "dependencies { \n" + - " implementation 'io.swagger.core.v3:swagger-jaxrs2:2.2.24-SNAPSHOT'\n" + + " implementation 'io.swagger.core.v3:swagger-jaxrs2:2.2.26-SNAPSHOT'\n" + " implementation 'javax.ws.rs:javax.ws.rs-api:2.1'\n" + " implementation 'javax.servlet:javax.servlet-api:3.1.0'\n" + " testImplementation 'com.github.tomakehurst:wiremock:2.27.2'\n" + @@ -154,7 +154,7 @@ public void testSwaggerResolveWithOAS31OptionTask() throws IOException { " mavenCentral()\n" + "}\n" + "dependencies { \n" + - " implementation 'io.swagger.core.v3:swagger-jaxrs2:2.2.24-SNAPSHOT'\n" + + " implementation 'io.swagger.core.v3:swagger-jaxrs2:2.2.26-SNAPSHOT'\n" + " implementation 'javax.ws.rs:javax.ws.rs-api:2.1'\n" + " implementation 'javax.servlet:javax.servlet-api:3.1.0'\n" + " testImplementation 'com.github.tomakehurst:wiremock:2.27.2'\n" + diff --git a/modules/swagger-integration/pom.xml b/modules/swagger-integration/pom.xml index 55383f6672..e6be0c4e13 100644 --- a/modules/swagger-integration/pom.xml +++ b/modules/swagger-integration/pom.xml @@ -6,7 +6,7 @@ io.swagger.core.v3 swagger-project - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../.. swagger-integration diff --git a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/GenericOpenApiContext.java b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/GenericOpenApiContext.java index ff14aa1c5e..27a4216144 100644 --- a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/GenericOpenApiContext.java +++ b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/GenericOpenApiContext.java @@ -76,6 +76,8 @@ public class GenericOpenApiContext implements O private Boolean convertToOpenAPI31; + private Schema.SchemaResolution schemaResolution; + public long getCacheTTL() { return cacheTTL; } @@ -330,6 +332,28 @@ public T convertToOpenAPI31(Boolean convertToOpenAPI31) { return (T) this; } + /** + * @since 2.2.24 + */ + public Schema.SchemaResolution getSchemaResolution() { + return schemaResolution; + } + + /** + * @since 2.2.24 + */ + public void setSchemaResolution(Schema.SchemaResolution schemaResolution) { + this.schemaResolution = schemaResolution; + } + + /** + * @since 2.2.24 + */ + public T schemaResolution(Schema.SchemaResolution schemaResolution) { + this.schemaResolution = schemaResolution; + return (T) this; + } + protected void register() { OpenApiContextLocator.getInstance().putOpenApiContext(id, this); } @@ -467,6 +491,9 @@ public T init() throws OpenApiConfigurationException { ((SwaggerConfiguration) openApiConfiguration).setId(id); ((SwaggerConfiguration) openApiConfiguration).setOpenAPI31(openAPI31); ((SwaggerConfiguration) openApiConfiguration).setConvertToOpenAPI31(convertToOpenAPI31); + if (schemaResolution != null) { + ((SwaggerConfiguration) openApiConfiguration).setSchemaResolution(schemaResolution); + } } openApiConfiguration = mergeParentConfiguration(openApiConfiguration, parent); @@ -525,7 +552,7 @@ public T init() throws OpenApiConfigurationException { if (objectMapperProcessor != null) { ObjectMapper mapper = IntegrationObjectMapperFactory.createJson(); objectMapperProcessor.processJsonObjectMapper(mapper); - ModelConverters.getInstance(Boolean.TRUE.equals(openApiConfiguration.isOpenAPI31())).addConverter(new ModelResolver(mapper)); + ModelConverters.getInstance(Boolean.TRUE.equals(openApiConfiguration.isOpenAPI31()), openApiConfiguration.getSchemaResolution()).addConverter(new ModelResolver(mapper)); objectMapperProcessor.processOutputJsonObjectMapper(outputJsonMapper); objectMapperProcessor.processOutputYamlObjectMapper(outputYamlMapper); @@ -559,6 +586,10 @@ public T init() throws OpenApiConfigurationException { if (openApiConfiguration.isConvertToOpenAPI31() != null && this.convertToOpenAPI31 == null) { this.convertToOpenAPI31 = openApiConfiguration.isConvertToOpenAPI31(); } + + if (openApiConfiguration.getSchemaResolution() != null && this.getSchemaResolution() == null) { + this.schemaResolution = openApiConfiguration.getSchemaResolution(); + } register(); return (T) this; } @@ -635,6 +666,10 @@ private OpenAPIConfiguration mergeParentConfiguration(OpenAPIConfiguration confi merged.setDefaultResponseCode(parentConfig.getDefaultResponseCode()); } + if (merged.getSchemaResolution() == null) { + merged.setSchemaResolution(parentConfig.getSchemaResolution()); + } + return merged; } diff --git a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/SwaggerConfiguration.java b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/SwaggerConfiguration.java index 1bd9041905..c9896626e3 100644 --- a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/SwaggerConfiguration.java +++ b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/SwaggerConfiguration.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.integration.api.OpenAPIConfiguration; import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; import java.util.Collection; import java.util.Map; @@ -40,6 +41,8 @@ public class SwaggerConfiguration implements OpenAPIConfiguration { private Boolean convertToOpenAPI31; + private Schema.SchemaResolution schemaResolution = Schema.SchemaResolution.DEFAULT; + @Override public String getDefaultResponseCode() { return defaultResponseCode; @@ -373,4 +376,18 @@ public SwaggerConfiguration convertToOpenAPI31(Boolean convertToOpenAPI31) { this.setConvertToOpenAPI31(convertToOpenAPI31); return this; } + + @Override + public Schema.SchemaResolution getSchemaResolution() { + return schemaResolution; + } + + public void setSchemaResolution(Schema.SchemaResolution schemaResolution) { + this.schemaResolution = schemaResolution; + } + + public SwaggerConfiguration schemaResolution(Schema.SchemaResolution schemaResolution) { + this.setSchemaResolution(schemaResolution); + return this; + } } diff --git a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/OpenAPIConfiguration.java b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/OpenAPIConfiguration.java index ae52702a9c..4854c2f5a1 100644 --- a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/OpenAPIConfiguration.java +++ b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/OpenAPIConfiguration.java @@ -1,6 +1,7 @@ package io.swagger.v3.oas.integration.api; import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; import java.util.Collection; import java.util.Map; @@ -68,4 +69,9 @@ public interface OpenAPIConfiguration { * @since 2.2.17 */ public String getDefaultResponseCode(); + + /** + * @since 2.2.24 + */ + public Schema.SchemaResolution getSchemaResolution(); } diff --git a/modules/swagger-java17-support/pom.xml b/modules/swagger-java17-support/pom.xml new file mode 100644 index 0000000000..6bbd74ab9c --- /dev/null +++ b/modules/swagger-java17-support/pom.xml @@ -0,0 +1,91 @@ + + 4.0.0 + + io.swagger.core.v3 + swagger-project + 2.2.26-SNAPSHOT + ../../pom.xml + + swagger-java17-support + jar + swagger-java17-support + Module for Java 17 specific tests + + + org.testng + testng + test + + + io.swagger.core.v3 + swagger-jaxrs2 + ${project.version} + + + io.swagger.core.v3 + swagger-core + ${project.version} + + + io.swagger.core.v3 + swagger-annotations + ${project.version} + + + io.swagger.core.v3 + swagger-models + ${project.version} + + + org.glassfish.jersey.media + jersey-media-multipart + ${jersey2-version} + test + + + javax.annotation + javax.annotation-api + + + org.javassist + javassist + + + + + org.glassfish.jersey.inject + jersey-hk2 + ${jersey2-version} + test + + + javax.annotation + javax.annotation-api + + + org.javassist + javassist + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + + + + + + 17 + + + diff --git a/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/Reader/ReaderTest.java b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/Reader/ReaderTest.java new file mode 100644 index 0000000000..bd4ff8d8c1 --- /dev/null +++ b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/Reader/ReaderTest.java @@ -0,0 +1,108 @@ +package io.swagger.v3.java17.Reader; + +import io.swagger.v3.java17.matchers.SerializationMatchers; +import io.swagger.v3.java17.resources.JavaRecordWithPathResource; +import io.swagger.v3.java17.resources.OtherJavaRecordWithPathsResource; +import io.swagger.v3.java17.resources.TestControllerWithRecordResource; +import io.swagger.v3.jaxrs2.Reader; +import io.swagger.v3.oas.integration.SwaggerConfiguration; +import io.swagger.v3.oas.models.OpenAPI; +import org.testng.annotations.Test; + +import java.util.HashSet; +import java.util.Set; + +public class ReaderTest { + + @Test + public void TestJavaRecordRef(){ + Reader reader = new Reader(new SwaggerConfiguration().openAPI(new OpenAPI()).openAPI31(true)); + + OpenAPI openAPI = reader.read(TestControllerWithRecordResource.class); + String yaml = "openapi: 3.1.0\n" + + "paths:\n" + + " /v17:\n" + + " post:\n" + + " operationId: opsRecordID\n" + + " responses:\n" + + " default:\n" + + " description: Successful operation\n" + + " content:\n" + + " application/json:\n" + + " schema:\n" + + " $ref: '#/components/schemas/JavaRecordResource'\n" + + "components:\n" + + " schemas:\n" + + " JavaRecordResource:\n" + + " type: object\n" + + " properties:\n" + + " test:\n" + + " type: string\n" + + " description: Testing of Java Record Processing\n" + + " isLatest:\n" + + " type: boolean\n" + + " id:\n" + + " type: string\n" + + " age:\n" + + " type: integer\n" + + " format: int32"; + SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); + } + + @Test + public void TestSetOfRecords(){ + Set> classes = new HashSet<>(); + classes.add(JavaRecordWithPathResource.class); + classes.add(OtherJavaRecordWithPathsResource.class); + + Reader reader = new Reader(new OpenAPI()); + OpenAPI openAPI = reader.read(classes); + String yaml = "openapi: 3.0.1\n" + + "paths:\n" + + " /sample/1:\n" + + " post:\n" + + " description: description 1\n" + + " operationId: id 1\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*': {}\n" + + " /sample/2:\n" + + " post:\n" + + " description: description 2\n" + + " operationId: id 2\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*': {}\n" + + " /sample2:\n" + + " get:\n" + + " description: description\n" + + " operationId: Operation Id\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*': {}\n" + + " security:\n" + + " - security_key:\n" + + " - write:pets\n" + + " - read:pets\n" + + " /sample2/2:\n" + + " get:\n" + + " description: description 2\n" + + " operationId: Operation Id 2\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*': {}\n" + + " security:\n" + + " - security_key2:\n" + + " - write:pets\n" + + " - read:pets"; + SerializationMatchers.assertEqualsToYaml(openAPI, yaml); + } +} diff --git a/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/Reader/SchemaResolutionRecordsTest.java b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/Reader/SchemaResolutionRecordsTest.java new file mode 100644 index 0000000000..e7fc559933 --- /dev/null +++ b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/Reader/SchemaResolutionRecordsTest.java @@ -0,0 +1,383 @@ +package io.swagger.v3.java17.Reader; + +import io.swagger.v3.core.converter.ModelConverters; +import io.swagger.v3.java17.matchers.SerializationMatchers; +import io.swagger.v3.java17.resources.SchemaResolutionWithRecordSimpleResource; +import io.swagger.v3.java17.resources.SchemaResolutionWithRecordsResource; +import io.swagger.v3.jaxrs2.Reader; +import io.swagger.v3.oas.integration.SwaggerConfiguration; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; +import org.testng.annotations.Test; + +public class SchemaResolutionRecordsTest { + + @Test + public void testSchemaResolutionInlineWithRecords(){ + ModelConverters.reset(); + Reader reader = new Reader(new SwaggerConfiguration().openAPI(new OpenAPI()).schemaResolution(Schema.SchemaResolution.INLINE)); + OpenAPI openAPI = reader.read(SchemaResolutionWithRecordsResource.class); + String yaml = "openapi: 3.0.1\n" + + "paths:\n" + + " /test/inlineSchemaFirst:\n" + + " get:\n" + + " operationId: inlineSchemaFirst\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " example: example\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " example: example\n" + + " /test/inlineSchemaSecond:\n" + + " get:\n" + + " operationId: inlineSchemaSecond\n" + + " requestBody:\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " type: object\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " propertySecond1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 2\n" + + " example: example\n" + + " description: InlineSchemaSecond property 1\n" + + " nullable: true\n" + + " example: exampleSecond\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: InlineSchemaSecond property 2\n" + + " nullable: true\n" + + " example: example\n" + + " description: InlineSchemaSecond API\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " type: object\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " propertySecond1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 2\n" + + " example: example\n" + + " description: InlineSchemaSecond property 1\n" + + " nullable: true\n" + + " example: exampleSecond\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: InlineSchemaSecond property 2\n" + + " nullable: true\n" + + " example: example\n" + + "components:\n" + + " schemas:\n" + + " InlineSchemaPropertyFirst:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " nullable: true\n" + + " example: example\n" + + " InlineSchemaRecordFirst:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " example: example\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " example: example\n" + + " InlineSchemaPropertySecond:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 2\n" + + " example: example\n" + + " description: propertySecond\n" + + " nullable: true\n" + + " example: exampleSecond\n" + + " InlineSchemaPropertySimple:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " example: example\n" + + " InlineSchemaRecordSecond:\n" + + " type: object\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " propertySecond1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 2\n" + + " example: example\n" + + " description: InlineSchemaSecond property 1\n" + + " nullable: true\n" + + " example: exampleSecond\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: InlineSchemaSecond property 2\n" + + " nullable: true\n" + + " example: example\n" + + " description: InlineSchemaSecond API\n" + + " InlineSchemaSimple:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 2\n" + + " example: example\n"; + SerializationMatchers.assertEqualsToYaml(openAPI, yaml); + ModelConverters.reset(); + } + + @Test + public void testSchemaResolutionAllOfWithRecordTest(){ + ModelConverters.reset(); + Reader reader = new Reader(new SwaggerConfiguration().openAPI(new OpenAPI()).schemaResolution(Schema.SchemaResolution.ALL_OF)); + OpenAPI openAPI = reader.read(SchemaResolutionWithRecordSimpleResource.class); + String yaml = "openapi: 3.0.1\n" + + "paths:\n" + + " /test/inlineSchemaFirst:\n" + + " get:\n" + + " operationId: inlineSchemaFirst\n" + + " responses:\n" + + " default:\n" + + " description: InlineSchemaFirst Response API\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " $ref: '#/components/schemas/SchemaRecordFirst'\n" + + " /test/inlineSchemaSecond:\n" + + " get:\n" + + " operationId: inlineSchemaFirst_1\n" + + " requestBody:\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " allOf:\n" + + " - description: InlineSchemaSecond API\n" + + " - $ref: '#/components/schemas/SchemaRecordFirst'\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*': {}\n" + + "components:\n" + + " schemas:\n" + + " InlineSchemaPropertyFirst:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " nullable: true\n" + + " example: example\n" + + " SchemaRecordFirst:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " $ref: '#/components/schemas/InlineSchemaPropertyFirst'\n"; + SerializationMatchers.assertEqualsToYaml(openAPI, yaml); + ModelConverters.reset(); + } + + @Test + public void testSchemaResolutionAllOfRefWithRecordsTest(){ + Reader reader = new Reader(new SwaggerConfiguration().openAPI(new OpenAPI()).schemaResolution(Schema.SchemaResolution.ALL_OF_REF)); + OpenAPI openAPI = reader.read(SchemaResolutionWithRecordsResource.class); + String yaml = "openapi: 3.0.1\n" + + "paths:\n" + + " /test/inlineSchemaFirst:\n" + + " get:\n" + + " operationId: inlineSchemaFirst\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " $ref: '#/components/schemas/InlineSchemaRecordFirst'\n" + + " /test/inlineSchemaSecond:\n" + + " get:\n" + + " operationId: inlineSchemaSecond\n" + + " requestBody:\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " description: InlineSchemaSecond API\n" + + " allOf:\n" + + " - $ref: '#/components/schemas/InlineSchemaRecordSecond'\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " $ref: '#/components/schemas/InlineSchemaRecordSecond'\n" + + "components:\n" + + " schemas:\n" + + " InlineSchemaPropertyFirst:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " example: example\n" + + " InlineSchemaRecordFirst:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " $ref: '#/components/schemas/InlineSchemaPropertyFirst'\n" + + " property2:\n" + + " $ref: '#/components/schemas/InlineSchemaPropertyFirst'\n" + + " InlineSchemaPropertySecond:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " $ref: '#/components/schemas/InlineSchemaSimple'\n" + + " description: propertySecond\n" + + " example: exampleSecond\n" + + " InlineSchemaPropertySimple:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " InlineSchemaRecordSecond:\n" + + " type: object\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " propertySecond1:\n" + + " description: InlineSchemaSecond property 1\n" + + " nullable: true\n" + + " allOf:\n" + + " - $ref: '#/components/schemas/InlineSchemaPropertySecond'\n" + + " property2:\n" + + " description: InlineSchemaSecond property 2\n" + + " nullable: true\n" + + " allOf:\n" + + " - $ref: '#/components/schemas/InlineSchemaPropertyFirst'\n" + + " InlineSchemaSimple:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " description: property 1\n" + + " allOf:\n" + + " - $ref: '#/components/schemas/InlineSchemaPropertySimple'\n" + + " property2:\n" + + " description: property 2\n" + + " example: example\n" + + " allOf:\n" + + " - $ref: '#/components/schemas/InlineSchemaPropertySimple'\n"; + SerializationMatchers.assertEqualsToYaml(openAPI, yaml); + } +} diff --git a/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/matchers/SerializationMatchers.java b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/matchers/SerializationMatchers.java new file mode 100644 index 0000000000..01e5c00de2 --- /dev/null +++ b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/matchers/SerializationMatchers.java @@ -0,0 +1,88 @@ +package io.swagger.v3.java17.matchers; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.NumericNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.swagger.v3.core.util.Json; +import io.swagger.v3.core.util.Json31; +import io.swagger.v3.core.util.Yaml; +import io.swagger.v3.core.util.Yaml31; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Comparator; + +import static org.testng.Assert.assertEquals; + +public class SerializationMatchers { + private static final Logger LOGGER = LoggerFactory.getLogger(SerializationMatchers.class); + + public static void assertEqualsToYaml(Object objectToSerialize, String yamlStr) { + apply(objectToSerialize, yamlStr, Yaml.mapper(), false); + } + + public static void assertEqualsToYamlExact(Object objectToSerialize, String yamlStr) { + apply(objectToSerialize, yamlStr, Yaml.mapper(), true); + } + + public static void assertEqualsToJson(Object objectToSerialize, String jsonStr) { + apply(objectToSerialize, jsonStr, Json.mapper(), false); + } + + public static void assertEqualsToYaml31(Object objectToSerialize, String yamlStr) { + apply31(objectToSerialize, yamlStr, Yaml31.mapper()); + } + + public static void assertEqualsToJson31(Object objectToSerialize, String jsonStr) { + apply31(objectToSerialize, jsonStr, Json31.mapper()); + } + + private static void apply(Object objectToSerialize, String str, ObjectMapper mapper, boolean exactMatch) { + final ObjectNode lhs = mapper.convertValue(objectToSerialize, ObjectNode.class); + ObjectNode rhs = null; + try { + rhs = mapper.readValue(str, ObjectNode.class); + } catch (IOException e) { + LOGGER.error("Failed to read value", e); + } + if (exactMatch || !lhs.equals(new ObjectNodeComparator(), rhs)) { + assertEquals(Yaml.pretty(lhs), Yaml.pretty(rhs)); + //fail(String.format("Serialized object:\n%s\ndoes not equal to expected serialized string:\n%s", lhs, rhs)); + } + } + + private static void apply31(Object objectToSerialize, String str, ObjectMapper mapper) { + final ObjectNode lhs = mapper.convertValue(objectToSerialize, ObjectNode.class); + ObjectNode rhs = null; + try { + rhs = mapper.readValue(str, ObjectNode.class); + } catch (IOException e) { + LOGGER.error("Failed to read value", e); + } + if (!lhs.equals(new ObjectNodeComparator(), rhs)) { + assertEquals(Yaml31.pretty(lhs), Yaml31.pretty(rhs)); + } + } + + static final class ObjectNodeComparator implements Comparator { + @Override + public int compare(JsonNode o1, JsonNode o2) { + if (o1.equals(o2)) { + return 0; + } + if ((o1 instanceof NumericNode) && (o2 instanceof NumericNode)) { + double d1 = ((NumericNode) o1).asDouble(); + double d2 = ((NumericNode) o2).asDouble(); + return Double.compare(d1, d2); + } + int comp = o1.asText().compareTo(o2.asText()); + if (comp == 0) { + return Integer.compare(o1.hashCode(), o2.hashCode()); + } + return comp; + } + } + +} diff --git a/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resolving/JavaRecordTest.java b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resolving/JavaRecordTest.java new file mode 100644 index 0000000000..5e483a1f94 --- /dev/null +++ b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resolving/JavaRecordTest.java @@ -0,0 +1,83 @@ +package io.swagger.v3.java17.resolving; + +import io.swagger.v3.core.converter.ModelConverters; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.java17.matchers.SerializationMatchers; +import org.testng.annotations.Test; + +import javax.validation.constraints.*; +import java.util.List; +import java.util.Map; + +public class JavaRecordTest { + + @Test + public void testJavaRecordWithSchema() { + String expectedYaml = "JavaRecordClassWithSchema:\n" + + " type: object\n" + + " description: Java Record with Schema Test\n" + + " properties:\n" + + " test:\n" + + " type: string\n" + + " description: Testing of Schema on fields processing in Java Records\n" + + " isLatest:\n" + + " type: boolean"; + + Map stringSchemaMap = ModelConverters.getInstance(false).readAll(JavaRecordClassWithSchema.class); + SerializationMatchers.assertEqualsToYaml(stringSchemaMap, expectedYaml); + } + + @Test + public void testJavaRecordWithBeanValidation() { + String expectedYaml = "JavaRecordClassWithBeanValidation:\n" + + " type: object\n" + + " description: Java Record with Bean Validation Test\n" + + " properties:\n" + + " test:\n" + + " maxLength: 100\n" + + " minLength: 1\n" + + " type: string\n" + + " isLatest:\n" + + " type: boolean\n" + + " randomList:\n" + + " maxItems: 101\n" + + " minItems: 2\n" + + " type: array\n" + + " items:\n" + + " type: string\n" + + " myField:\n" + + " maximum: 100\n" + + " exclusiveMaximum: false\n" + + " minimum: 1\n" + + " exclusiveMinimum: false\n" + + " type: number\n" + + " email:\n" + + " pattern: (.+?)@(.+?)\n" + + " type: string"; + + Map stringSchemaMap = ModelConverters.getInstance(false).readAll(JavaRecordClassWithBeanValidation.class); + SerializationMatchers.assertEqualsToYaml(stringSchemaMap, expectedYaml); + } + + @io.swagger.v3.oas.annotations.media.Schema(description = "Java Record with Schema Test") + public record JavaRecordClassWithSchema( + @io.swagger.v3.oas.annotations.media.Schema(description = "Testing of Schema on fields processing in Java Records") String test, + boolean isLatest + ){ + } + + @io.swagger.v3.oas.annotations.media.Schema(description = "Java Record with Bean Validation Test") + public record JavaRecordClassWithBeanValidation( + @Size(min = 1, max = 100) String test, + boolean isLatest, + @Size(min = 2, max = 101) + List randomList, + @DecimalMin("1") + @DecimalMax("100") + Number myField, + @Pattern(regexp = "(.+?)@(.+?)") + String email + ){ + } + +} diff --git a/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resolving/SwaggerTestBase.java b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resolving/SwaggerTestBase.java new file mode 100644 index 0000000000..2735cb788a --- /dev/null +++ b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resolving/SwaggerTestBase.java @@ -0,0 +1,34 @@ +package io.swagger.v3.java17.resolving; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import io.swagger.v3.core.jackson.ModelResolver; + +public abstract class SwaggerTestBase { + static ObjectMapper mapper; + + public static ObjectMapper mapper() { + if (mapper == null) { + mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + return mapper; + } + + protected ModelResolver modelResolver() { + return new ModelResolver(new ObjectMapper()); + } + + protected void prettyPrint(Object o) { + try { + System.out.println(mapper().writer(new DefaultPrettyPrinter()).writeValueAsString(o)); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resolving/v31/ModelResolverOAS31Test.java b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resolving/v31/ModelResolverOAS31Test.java new file mode 100644 index 0000000000..902f4336da --- /dev/null +++ b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resolving/v31/ModelResolverOAS31Test.java @@ -0,0 +1,79 @@ +package io.swagger.v3.java17.resolving.v31; + +import io.swagger.v3.core.converter.ModelConverters; +import io.swagger.v3.java17.resolving.SwaggerTestBase; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.models.media.Schema; +import org.testng.annotations.Test; +import io.swagger.v3.java17.matchers.SerializationMatchers; +import java.util.List; +import java.util.Map; + +public class ModelResolverOAS31Test extends SwaggerTestBase { + + @Test + public void testOAS31JavaRecord() { + String expectedYaml = "JavaRecordWithOAS31Fields:\n" + + " type: object\n" + + " $comment: Random comment at schema level\n" + + " $id: http://yourdomain.com/schemas/myschema.json\n" + + " description: this is model for testing OAS 3.1 Java Record resolving\n" + + " properties:\n" + + " test:\n" + + " type: string\n" + + " isLatest:\n" + + " type: boolean\n" + + " randomList:\n" + + " type: array\n" + + " contains:\n" + + " type: string\n" + + " items:\n" + + " type: string\n" + + " maxContains: 10\n" + + " minContains: 1\n" + + " prefixItems:\n" + + " - type: string\n" + + " unevaluatedItems:\n" + + " type: number\n" + + " Status:\n" + + " type:\n" + + " - string\n" + + " - number\n"; + + Map stringSchemaMap = ModelConverters.getInstance(true).readAll(JavaRecordWithOAS31Fields.class); + SerializationMatchers.assertEqualsToYaml31(stringSchemaMap, expectedYaml); + } + + @io.swagger.v3.oas.annotations.media.Schema( + $id = "http://yourdomain.com/schemas/myschema.json", + description = "this is model for testing OAS 3.1 Java Record resolving", + $comment = "Random comment at schema level", + types = "object" + ) + private record JavaRecordWithOAS31Fields( + String test, + boolean isLatest, + @ArraySchema( + maxContains = 10, + minContains = 1, + contains = @io.swagger.v3.oas.annotations.media.Schema( + types = "string" + ), + unevaluatedItems = @io.swagger.v3.oas.annotations.media.Schema( + types = "number" + ), + prefixItems = { + @io.swagger.v3.oas.annotations.media.Schema( + types = "string" + ) + } + ) + List randomList, + @io.swagger.v3.oas.annotations.media.Schema(types = { + "string", + "number" + }) + Object Status + ){ + } +} diff --git a/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/JavaRecordResource.java b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/JavaRecordResource.java new file mode 100644 index 0000000000..eb1d392e40 --- /dev/null +++ b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/JavaRecordResource.java @@ -0,0 +1,9 @@ +package io.swagger.v3.java17.resources; + +public record JavaRecordResource( + @io.swagger.v3.oas.annotations.media.Schema(description = "Testing of Java Record Processing") String test, + boolean isLatest, + String id, + Integer age +) { +} diff --git a/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/JavaRecordWithPathResource.java b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/JavaRecordWithPathResource.java new file mode 100644 index 0000000000..45b0af2967 --- /dev/null +++ b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/JavaRecordWithPathResource.java @@ -0,0 +1,24 @@ +package io.swagger.v3.java17.resources; + +import io.swagger.v3.oas.annotations.Operation; + +import javax.ws.rs.POST; +import javax.ws.rs.Path; + +@Path("sample") +public record JavaRecordWithPathResource() { + + @POST + @Path("/1") + @Operation(description = "description 1", operationId = "id 1") + public void postExample(){ + + } + + @POST + @Path("/2") + @Operation(description = "description 2", operationId = "id 2") + public void postExample2(){ + + } +} diff --git a/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/OtherJavaRecordWithPathsResource.java b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/OtherJavaRecordWithPathsResource.java new file mode 100644 index 0000000000..e4a5437f54 --- /dev/null +++ b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/OtherJavaRecordWithPathsResource.java @@ -0,0 +1,30 @@ +package io.swagger.v3.java17.resources; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.*; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("sample2") +public record OtherJavaRecordWithPathsResource() { + @GET + @Path("/") + @Operation(operationId = "Operation Id", + description = "description") + @SecurityRequirement(name = "security_key", + scopes = {"write:pets", "read:pets"} + ) + public void getSecurity() { + } + + @GET + @Path("/2") + @Operation(operationId = "Operation Id 2", + description = "description 2") + @SecurityRequirement(name = "security_key2", + scopes = {"write:pets", "read:pets"} + ) + public void getSecurity2() { + } +} diff --git a/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/SchemaResolutionWithRecordSimpleResource.java b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/SchemaResolutionWithRecordSimpleResource.java new file mode 100644 index 0000000000..a2952bbe4d --- /dev/null +++ b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/SchemaResolutionWithRecordSimpleResource.java @@ -0,0 +1,39 @@ +package io.swagger.v3.java17.resources; + +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; + +@Path("test") +public class SchemaResolutionWithRecordSimpleResource{ + + + @GET + @Path("/inlineSchemaFirst") + @ApiResponse(description = "InlineSchemaFirst Response API", content = @Content(schema = @Schema(implementation = SchemaRecordFirst.class))) + public Response inlineSchemaFirst() { + return null; + } + + + @GET + @Path("/inlineSchemaSecond") + public void inlineSchemaFirst(@Schema(description = "InlineSchemaSecond API") SchemaRecordFirst inlineSchemaFirst) { + } + + + + public record SchemaRecordFirst ( + @Schema(description = "InlineSchemaFirst property 1", nullable = true) InlineSchemaPropertyFirst property1 + ){ + } + + @Schema(description = "property", example = "example") + static class InlineSchemaPropertyFirst { + public String bar; + } +} diff --git a/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/SchemaResolutionWithRecordsResource.java b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/SchemaResolutionWithRecordsResource.java new file mode 100644 index 0000000000..0271762a58 --- /dev/null +++ b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/SchemaResolutionWithRecordsResource.java @@ -0,0 +1,68 @@ +package io.swagger.v3.java17.resources; + + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("test") +public class SchemaResolutionWithRecordsResource { + + @GET + @Path("/inlineSchemaSecond") + public InlineSchemaRecordSecond inlineSchemaSecond(@Schema(description = "InlineSchemaSecond API") InlineSchemaRecordSecond inlineSchemaSecond) { + return null; + } + + @GET + @Path("/inlineSchemaFirst") + public InlineSchemaRecordFirst inlineSchemaFirst() { + return null; + } + + + public record InlineSchemaRecordFirst( + InlineSchemaPropertyFirst property1, + InlineSchemaPropertyFirst property2 + ){ + } + + public record InlineSchemaRecordSecond ( + String foo, + @Schema(description = "InlineSchemaSecond property 1", nullable = true) + InlineSchemaPropertySecond propertySecond1, + @Schema(description = "InlineSchemaSecond property 2", nullable = true) + InlineSchemaPropertyFirst property2 + ){ + } + + @Schema(description = "property", example = "example") + static class InlineSchemaPropertyFirst { + public String bar; + } + + @Schema(description = "propertySecond", example = "exampleSecond") + static class InlineSchemaPropertySecond { + public InlineSchemaSimple bar; + } + + static class InlineSchemaSimple { + + @Schema(description = "property 1") + public InlineSchemaPropertySimple property1; + + + private InlineSchemaPropertySimple property2; + + @Schema(description = "property 2", example = "example") + public InlineSchemaPropertySimple getProperty2() { + return null; + } + } + + @Schema(description = "property") + static class InlineSchemaPropertySimple { + public String bar; + } +} diff --git a/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/TestControllerWithRecordResource.java b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/TestControllerWithRecordResource.java new file mode 100644 index 0000000000..3ba8205b51 --- /dev/null +++ b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resources/TestControllerWithRecordResource.java @@ -0,0 +1,25 @@ +package io.swagger.v3.java17.resources; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; + +@Path("/v17") +public class TestControllerWithRecordResource { + + @POST + @Operation( + operationId = "opsRecordID", + responses = @ApiResponse(description = "Successful operation", + content = @Content(mediaType = "application/json",schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = JavaRecordResource.class)) + ) + ) + @Consumes({"application/json", "application/xml"}) + public void postRecord(){} + +} + diff --git a/modules/swagger-jaxrs2-servlet-initializer-v2/pom.xml b/modules/swagger-jaxrs2-servlet-initializer-v2/pom.xml index cebc663de5..7331302c9b 100644 --- a/modules/swagger-jaxrs2-servlet-initializer-v2/pom.xml +++ b/modules/swagger-jaxrs2-servlet-initializer-v2/pom.xml @@ -5,7 +5,7 @@ swagger-project io.swagger.core.v3 - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../../ 4.0.0 diff --git a/modules/swagger-jaxrs2-servlet-initializer/pom.xml b/modules/swagger-jaxrs2-servlet-initializer/pom.xml index e90c67d2d3..3dde3b4945 100644 --- a/modules/swagger-jaxrs2-servlet-initializer/pom.xml +++ b/modules/swagger-jaxrs2-servlet-initializer/pom.xml @@ -5,7 +5,7 @@ swagger-project io.swagger.core.v3 - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../../ 4.0.0 diff --git a/modules/swagger-jaxrs2/pom.xml b/modules/swagger-jaxrs2/pom.xml index 43a4a18c20..13423677f0 100644 --- a/modules/swagger-jaxrs2/pom.xml +++ b/modules/swagger-jaxrs2/pom.xml @@ -5,7 +5,7 @@ swagger-project io.swagger.core.v3 - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../../ 4.0.0 @@ -96,7 +96,8 @@ maven-surefire-plugin ${surefire-version} - --add-opens java.base/java.lang=ALL-UNNAMED + -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 --add-opens java.base/java.lang=ALL-UNNAMED + 0 diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/DefaultParameterExtension.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/DefaultParameterExtension.java index ed958c0a33..541dda6b52 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/DefaultParameterExtension.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/DefaultParameterExtension.java @@ -120,7 +120,7 @@ public ResolvedParameter extractParameters(List annotations, annotations, components, classConsumes == null ? new String[0] : classConsumes.value(), - methodConsumes == null ? new String[0] : methodConsumes.value(), jsonViewAnnotation, openapi31); + methodConsumes == null ? new String[0] : methodConsumes.value(), jsonViewAnnotation, openapi31, this.schemaResolution); if (unknownParameter != null) { if (StringUtils.isNotBlank(unknownParameter.getIn()) && !"form".equals(unknownParameter.getIn())) { extractParametersResult.parameters.add(unknownParameter); @@ -141,7 +141,8 @@ public ResolvedParameter extractParameters(List annotations, classConsumes == null ? new String[0] : classConsumes.value(), methodConsumes == null ? new String[0] : methodConsumes.value(), jsonViewAnnotation, - openapi31); + openapi31, + this.schemaResolution); if (processedParameter != null) { extractParametersResult.parameters.add(processedParameter); } @@ -264,7 +265,9 @@ private boolean handleAdditionalAnnotation(List parameters, List parameters, List cls, final List globalParameters = new ArrayList<>(); // look for constructor-level annotated properties - globalParameters.addAll(ReaderUtils.collectConstructorParameters(cls, components, classConsumes, null)); + globalParameters.addAll(ReaderUtils.collectConstructorParameters(cls, components, classConsumes, null, config.getSchemaResolution())); // look for field-level annotated properties globalParameters.addAll(ReaderUtils.collectFieldParameters(cls, components, classConsumes, null)); @@ -1165,7 +1165,7 @@ protected Operation parseMethod( final Class subResource = getSubResourceWithJaxRsSubresourceLocatorSpecs(method); Schema returnTypeSchema = null; if (!shouldIgnoreClass(returnType.getTypeName()) && !method.getGenericReturnType().equals(subResource)) { - ResolvedSchema resolvedSchema = ModelConverters.getInstance(config.isOpenAPI31()).resolveAsResolvedSchema(new AnnotatedType(returnType).resolveAsRef(true).jsonViewAnnotation(jsonViewAnnotation).components(components)); + ResolvedSchema resolvedSchema = ModelConverters.getInstance(config.isOpenAPI31(), config.getSchemaResolution()).resolveAsResolvedSchema(new AnnotatedType(returnType).resolveAsRef(true).jsonViewAnnotation(jsonViewAnnotation).components(components)); if (resolvedSchema.schema != null) { returnTypeSchema = resolvedSchema.schema; Content content = new Content(); @@ -1318,7 +1318,7 @@ private boolean shouldIgnoreClass(String className) { } ignore = rawClassName.startsWith("javax.ws.rs."); ignore = ignore || rawClassName.equalsIgnoreCase("void"); - ignore = ignore || ModelConverters.getInstance(config.isOpenAPI31()).isRegisteredAsSkippedClass(rawClassName); + ignore = ignore || ModelConverters.getInstance(config.isOpenAPI31(), config.getSchemaResolution()).isRegisteredAsSkippedClass(rawClassName); return ignore; } @@ -1525,8 +1525,11 @@ protected ResolvedParameter getParameters(Type type, List annotation LOGGER.debug("trying extension {}", extension); extension.setOpenAPI31(Boolean.TRUE.equals(config.isOpenAPI31())); - - return extension.extractParameters(annotations, type, typesToSkip, components, classConsumes, methodConsumes, true, jsonViewAnnotation, chain); + Schema.SchemaResolution curSchemaResolution = config.getSchemaResolution(); + extension.setSchemaResolution(config.getSchemaResolution()); + ResolvedParameter resolvedParameter = extension.extractParameters(annotations, type, typesToSkip, components, classConsumes, methodConsumes, true, jsonViewAnnotation, chain); + ((SwaggerConfiguration)config).setSchemaResolution(curSchemaResolution); + return resolvedParameter; } private Set extractOperationIdFromPathItem(PathItem path) { diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/AbstractOpenAPIExtension.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/AbstractOpenAPIExtension.java index a8792d4e2a..197e248b24 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/AbstractOpenAPIExtension.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/AbstractOpenAPIExtension.java @@ -6,6 +6,7 @@ import io.swagger.v3.jaxrs2.ResolvedParameter; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.Schema; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @@ -17,6 +18,7 @@ public abstract class AbstractOpenAPIExtension implements OpenAPIExtension { protected boolean openapi31; + protected Schema.SchemaResolution schemaResolution; @Override public String extractOperationMethod(Method method, Iterator chain) { @@ -68,4 +70,9 @@ protected JavaType constructType(Type type) { public void setOpenAPI31(boolean openapi31) { this.openapi31 = openapi31; } + + @Override + public void setSchemaResolution(Schema.SchemaResolution schemaResolution) { + this.schemaResolution = schemaResolution; + } } diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/OpenAPIExtension.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/OpenAPIExtension.java index d966120a0d..b82e8b581d 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/OpenAPIExtension.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/OpenAPIExtension.java @@ -4,6 +4,7 @@ import io.swagger.v3.jaxrs2.ResolvedParameter; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.Schema; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @@ -31,4 +32,8 @@ ResolvedParameter extractParameters(List annotations, Type type, Set default void setOpenAPI31(boolean openapi31) { //todo: override me! } + + default void setSchemaResolution(Schema.SchemaResolution schemaResolution) { + //todo: override me! + } } diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletConfigContextUtils.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletConfigContextUtils.java index aa26058aaa..a2555f4f0b 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletConfigContextUtils.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletConfigContextUtils.java @@ -63,6 +63,11 @@ public class ServletConfigContextUtils { public static final String OPENAPI_CONFIGURATION_CONVERT_TO_OPENAPI_31_KEY = "openApi.configuration.convertToOpenAPI31"; + /** + * @since 2.2.24 + */ + public static final String OPENAPI_CONFIGURATION_SCHEMA_RESOLUTION_KEY = "openApi.configuration.schemaResolution"; + public static Set resolveResourcePackages(ServletConfig servletConfig) { if (!isServletConfigAvailable(servletConfig)) { return null; diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletOpenApiConfigurationLoader.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletOpenApiConfigurationLoader.java index baeb5dce76..f54f53769f 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletOpenApiConfigurationLoader.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletOpenApiConfigurationLoader.java @@ -6,6 +6,7 @@ import io.swagger.v3.oas.integration.api.OpenAPIConfigBuilder; import io.swagger.v3.oas.integration.api.OpenAPIConfiguration; import io.swagger.v3.oas.integration.api.OpenApiConfigurationLoader; +import io.swagger.v3.oas.models.media.Schema; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,6 +25,7 @@ import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_READALLRESOURCES_KEY; import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_READER_KEY; import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_SCANNER_KEY; +import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_SCHEMA_RESOLUTION_KEY; import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_SKIPRESOLVEAPPPATH_KEY; import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_SORTOUTPUT_KEY; import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_ALWAYSRESOLVEAPPPATH_KEY; @@ -70,7 +72,9 @@ public OpenAPIConfiguration load(String path) throws IOException { .openAPI31(getBooleanInitParam(servletConfig, OPENAPI_CONFIGURATION_OPENAPI_31_KEY)) .convertToOpenAPI31(getBooleanInitParam(servletConfig, OPENAPI_CONFIGURATION_CONVERT_TO_OPENAPI_31_KEY)) .modelConverterClasses(resolveModelConverterClasses(servletConfig)); - + if (getInitParam(servletConfig, OPENAPI_CONFIGURATION_SCHEMA_RESOLUTION_KEY) != null) { + configuration.schemaResolution(Schema.SchemaResolution.valueOf(getInitParam(servletConfig, OPENAPI_CONFIGURATION_SCHEMA_RESOLUTION_KEY))); + } return configuration; } diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/SwaggerLoader.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/SwaggerLoader.java index c9375dbe5d..36e5987c5c 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/SwaggerLoader.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/SwaggerLoader.java @@ -10,6 +10,7 @@ import io.swagger.v3.oas.integration.SwaggerConfiguration; import io.swagger.v3.oas.integration.api.OpenApiContext; import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; @@ -49,6 +50,8 @@ public class SwaggerLoader { private Boolean convertToOpenAPI31 = false; + private String schemaResolution; + /** * @since 2.0.6 */ @@ -250,6 +253,20 @@ public void setConvertToOpenAPI31(Boolean convertToOpenAPI31) { this.convertToOpenAPI31 = convertToOpenAPI31; } + /** + * @since 2.2.24 + */ + public String getSchemaResolution() { + return schemaResolution; + } + + /** + * @since 2.2.24 + */ + public void setSchemaResolution(String schemaResolution) { + this.schemaResolution = schemaResolution; + } + public Map resolve() throws Exception{ Set ignoredRoutesSet = null; @@ -301,6 +318,9 @@ public Map resolve() throws Exception{ .skipResolveAppPath(skipResolveAppPath) .openAPI31(openAPI31) .convertToOpenAPI31(convertToOpenAPI31); + if (schemaResolution != null) { + config.schemaResolution(Schema.SchemaResolution.valueOf(schemaResolution)); + } try { GenericOpenApiContextBuilder builder = new JaxrsOpenApiContextBuilder() .openApiConfiguration(config); diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/util/ReaderUtils.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/util/ReaderUtils.java index 8933a0497e..581bc0bbb4 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/util/ReaderUtils.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/util/ReaderUtils.java @@ -7,6 +7,7 @@ import io.swagger.v3.jaxrs2.ext.OpenAPIExtensions; import io.swagger.v3.oas.integration.api.OpenAPIConfiguration; import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.parameters.Parameter; import org.apache.commons.lang3.StringUtils; @@ -35,6 +36,9 @@ public class ReaderUtils { private static final String OPTIONS_METHOD = "options"; private static final String PATH_DELIMITER = "/"; + public static List collectConstructorParameters(Class cls, Components components, javax.ws.rs.Consumes classConsumes, JsonView jsonViewAnnotation) { + return collectConstructorParameters(cls, components, classConsumes, jsonViewAnnotation, null); + } /** * Collects constructor-level parameters from class. * @@ -42,7 +46,7 @@ public class ReaderUtils { * @param components * @return the collection of supported parameters */ - public static List collectConstructorParameters(Class cls, Components components, javax.ws.rs.Consumes classConsumes, JsonView jsonViewAnnotation) { + public static List collectConstructorParameters(Class cls, Components components, javax.ws.rs.Consumes classConsumes, JsonView jsonViewAnnotation, Schema.SchemaResolution schemaResolution) { if (cls.isLocalClass() || (cls.isMemberClass() && !Modifier.isStatic(cls.getModifiers()))) { return Collections.emptyList(); } @@ -77,7 +81,7 @@ public static List collectConstructorParameters(Class cls, Compone components, classConsumes == null ? new String[0] : classConsumes.value(), null, - jsonViewAnnotation); + jsonViewAnnotation, false, schemaResolution); if (processedParameter != null) { parameters.add(processedParameter); } diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java index 0f32a4dd30..e3ae222c2c 100644 --- a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java @@ -143,9 +143,7 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Collectors; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @@ -3378,6 +3376,7 @@ public void testOas31Petstore() { "components:\n" + " schemas:\n" + " Bar:\n" + + " type: object\n" + " deprecated: true\n" + " description: Bar\n" + " properties:\n" + @@ -3394,6 +3393,7 @@ public void testOas31Petstore() { " - string\n" + " format: int32\n" + " Category:\n" + + " type: object\n" + " properties:\n" + " id:\n" + " type: integer\n" + @@ -3403,6 +3403,7 @@ public void testOas31Petstore() { " xml:\n" + " name: Category\n" + " Foo:\n" + + " type: object\n" + " deprecated: true\n" + " description: Foo\n" + " properties:\n" + @@ -3420,6 +3421,7 @@ public void testOas31Petstore() { " - object\n" + " format: int32\n" + " IfSchema:\n" + + " type: object\n" + " deprecated: true\n" + " description: if schema\n" + " properties:\n" + @@ -3437,6 +3439,7 @@ public void testOas31Petstore() { " - object\n" + " format: int32\n" + " Pet:\n" + + " type: object\n" + " properties:\n" + " id:\n" + " type: integer\n" + @@ -3470,6 +3473,7 @@ public void testOas31Petstore() { " xml:\n" + " name: Pet\n" + " Tag:\n" + + " type: object\n" + " properties:\n" + " id:\n" + " type: integer\n" + @@ -3510,6 +3514,7 @@ public void test31RefSiblings() { "components:\n" + " schemas:\n" + " Foo:\n" + + " type: object\n" + " deprecated: true\n" + " description: Foo\n" + " properties:\n" + @@ -3527,6 +3532,7 @@ public void test31RefSiblings() { " - object\n" + " format: int32\n" + " SimpleTag:\n" + + " type: object\n" + " properties:\n" + " annotated:\n" + " $ref: '#/components/schemas/SimpleCategory'\n" + @@ -3534,7 +3540,8 @@ public void test31RefSiblings() { " properties:\n" + " foo:\n" + " $ref: '#/components/schemas/Foo'\n" + - " SimpleCategory: {}\n"; + " SimpleCategory:\n" + + " type: object\n" ; SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); } @@ -3558,12 +3565,14 @@ public void testSiblings() { "components:\n" + " schemas:\n" + " Category:\n" + + " type: object\n" + " description: parent\n" + " properties:\n" + " id:\n" + " type: integer\n" + " format: int64\n" + " Pet:\n" + + " type: object\n" + " description: Pet\n" + " properties:\n" + " category:\n" + @@ -3606,6 +3615,7 @@ public void testSiblingsOnResource() { "components:\n" + " schemas:\n" + " PetSimple:\n" + + " type: object\n" + " description: Pet\n"; SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); } @@ -3654,6 +3664,7 @@ public void testSiblingsOnResourceResponse() { "components:\n" + " schemas:\n" + " PetSimple:\n" + + " type: object\n" + " description: Pet\n"; SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); } @@ -3699,6 +3710,7 @@ public void testSiblingsOnResourceRequestBody() { "components:\n" + " schemas:\n" + " PetSimple:\n" + + " type: object\n" + " description: Pet\n"; SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); } @@ -3775,6 +3787,7 @@ public void testSiblingsOnResourceRequestBodyMultiple() { "components:\n" + " schemas:\n" + " PetSimple:\n" + + " type: object\n" + " description: Pet\n"; SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); } @@ -3838,12 +3851,14 @@ public void testSiblingsOnProperty() { "components:\n" + " schemas:\n" + " Category:\n" + + " type: object\n" + " description: parent\n" + " properties:\n" + " id:\n" + " type: integer\n" + " format: int64\n" + " Pet:\n" + + " type: object\n" + " description: Pet\n" + " properties:\n" + " category:\n" + @@ -3934,6 +3949,7 @@ public void testMisc31() { " creditCard:\n" + " $ref: '#/components/schemas/CreditCard'\n" + " MultipleBaseBean:\n" + + " type: object\n" + " description: MultipleBaseBean\n" + " properties:\n" + " beanType:\n" + @@ -3962,6 +3978,7 @@ public void testMisc31() { " format: int32\n" + " description: MultipleSub2Bean\n" + " Address:\n" + + " type: object\n" + " if:\n" + " $ref: '#/components/schemas/AnnotatedCountry'\n" + " then:\n" + @@ -3982,22 +3999,27 @@ public void testMisc31() { " propertyNames:\n" + " $ref: '#/components/schemas/PropertyNamesPattern'\n" + " AnnotatedCountry:\n" + + " type: object\n" + " properties:\n" + " country:\n" + " const: United States\n" + " CreditCard:\n" + + " type: object\n" + " properties:\n" + " billingAddress:\n" + " type: string\n" + " PostalCodeNumberPattern:\n" + + " type: object\n" + " properties:\n" + " postalCode:\n" + " pattern: \"[0-9]{5}(-[0-9]{4})?\"\n" + " PostalCodePattern:\n" + + " type: object\n" + " properties:\n" + " postalCode:\n" + " pattern: \"[A-Z][0-9][A-Z] [0-9][A-Z][0-9]\"\n" + " PropertyNamesPattern:\n" + + " type: object\n" + " pattern: \"^[A-Za-z_][A-Za-z0-9_]*$\"\n"; SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); } diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionAllOfRefTest.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionAllOfRefTest.java new file mode 100644 index 0000000000..f2a5cb29eb --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionAllOfRefTest.java @@ -0,0 +1,112 @@ +package io.swagger.v3.jaxrs2; + +import io.swagger.v3.core.converter.ModelConverters; +import io.swagger.v3.jaxrs2.matchers.SerializationMatchers; +import io.swagger.v3.jaxrs2.schemaResolution.SchemaResolutionResource; +import io.swagger.v3.oas.integration.SwaggerConfiguration; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; +import org.testng.annotations.Test; + +public class SchemaResolutionAllOfRefTest { + + @Test + public void testSchemaResolutionAllOfRef() { + ModelConverters.reset(); + Reader reader = new Reader(new SwaggerConfiguration().openAPI(new OpenAPI()).schemaResolution(Schema.SchemaResolution.ALL_OF_REF)); + OpenAPI openAPI = reader.read(SchemaResolutionResource.class); + String yaml = "openapi: 3.0.1\n" + + "paths:\n" + + " /test/inlineSchemaFirst:\n" + + " get:\n" + + " operationId: inlineSchemaFirst\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " $ref: \"#/components/schemas/InlineSchemaFirst\"\n" + + " /test/inlineSchemaSecond:\n" + + " get:\n" + + " operationId: inlineSchemaSecond\n" + + " requestBody:\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " description: InlineSchemaSecond API\n" + + " allOf:\n" + + " - $ref: \"#/components/schemas/InlineSchemaSecond\"\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " $ref: \"#/components/schemas/InlineSchemaSecond\"\n" + + "components:\n" + + " schemas:\n" + + " InlineSchemaFirst:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " description: InlineSchemaFirst property 1\n" + + " nullable: true\n" + + " allOf:\n" + + " - $ref: \"#/components/schemas/InlineSchemaPropertyFirst\"\n" + + " property2:\n" + + " description: ' InlineSchemaFirst property 2'\n" + + " example: example 2\n" + + " allOf:\n" + + " - $ref: \"#/components/schemas/InlineSchemaPropertyFirst\"\n" + + " InlineSchemaPropertyFirst:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " example: example\n" + + " InlineSchemaPropertySecond:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " $ref: \"#/components/schemas/InlineSchemaSimple\"\n" + + " description: propertysecond\n" + + " example: examplesecond\n" + + " InlineSchemaPropertySimple:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " InlineSchemaSecond:\n" + + " type: object\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " propertySecond1:\n" + + " description: InlineSchemaSecond property 1\n" + + " nullable: true\n" + + " allOf:\n" + + " - $ref: \"#/components/schemas/InlineSchemaPropertySecond\"\n" + + " property2:\n" + + " description: InlineSchemaSecond property 2\n" + + " example: InlineSchemaSecond example 2\n" + + " allOf:\n" + + " - $ref: \"#/components/schemas/InlineSchemaPropertyFirst\"\n" + + " InlineSchemaSimple:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " description: property 1\n" + + " allOf:\n" + + " - $ref: \"#/components/schemas/InlineSchemaPropertySimple\"\n" + + " property2:\n" + + " description: property 2\n" + + " example: example\n" + + " allOf:\n" + + " - $ref: \"#/components/schemas/InlineSchemaPropertySimple\"\n"; + SerializationMatchers.assertEqualsToYaml(openAPI, yaml); + ModelConverters.reset(); + } +} diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionAllOfTest.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionAllOfTest.java new file mode 100644 index 0000000000..4235b7aecc --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionAllOfTest.java @@ -0,0 +1,60 @@ +package io.swagger.v3.jaxrs2; + +import io.swagger.v3.jaxrs2.matchers.SerializationMatchers; +import io.swagger.v3.jaxrs2.schemaResolution.SchemaResolutionResourceSimple; +import io.swagger.v3.oas.integration.SwaggerConfiguration; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; +import org.testng.annotations.Test; + +public class SchemaResolutionAllOfTest { + + @Test + public void testSchemaResolutionAllOf() { + Reader reader = new Reader(new SwaggerConfiguration().openAPI(new OpenAPI()).schemaResolution(Schema.SchemaResolution.ALL_OF)); + OpenAPI openAPI = reader.read(SchemaResolutionResourceSimple.class); + String yaml = "openapi: 3.0.1\n" + + "paths:\n" + + " /test/inlineSchemaFirst:\n" + + " get:\n" + + " operationId: inlineSchemaFirst\n" + + " responses:\n" + + " default:\n" + + " description: InlineSchemaFirst Response API\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " $ref: '#/components/schemas/InlineSchemaFirst'\n" + + " /test/inlineSchemaSecond:\n" + + " get:\n" + + " operationId: inlineSchemaFirst_1\n" + + " requestBody:\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " allOf:\n" + + " - description: InlineSchemaSecond API\n" + + " - $ref: '#/components/schemas/InlineSchemaFirst'\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*': {}\n" + + "components:\n" + + " schemas:\n" + + " InlineSchemaFirst:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " $ref: '#/components/schemas/InlineSchemaPropertyFirst'\n" + + " InlineSchemaPropertyFirst:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " nullable: true\n" + + " example: example\n"; + SerializationMatchers.assertEqualsToYaml(openAPI, yaml); + } +} diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionAnnotationTest.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionAnnotationTest.java new file mode 100644 index 0000000000..c5e77c3492 --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionAnnotationTest.java @@ -0,0 +1,117 @@ +package io.swagger.v3.jaxrs2; + +import io.swagger.v3.core.converter.ModelConverters; +import io.swagger.v3.jaxrs2.matchers.SerializationMatchers; +import io.swagger.v3.jaxrs2.schemaResolution.SchemaResolutionAnnotatedResource; +import io.swagger.v3.oas.integration.SwaggerConfiguration; +import io.swagger.v3.oas.models.OpenAPI; +import org.testng.annotations.Test; + +public class SchemaResolutionAnnotationTest { + + @Test + public void testSchemaResolutionAnnotation() { + ModelConverters.reset(); + Reader reader = new Reader(new SwaggerConfiguration().openAPI(new OpenAPI())); + OpenAPI openAPI = reader.read(SchemaResolutionAnnotatedResource.class); + String yaml = "openapi: 3.0.1\n" + + "paths:\n" + + " /test/inlineSchemaFirst:\n" + + " get:\n" + + " operationId: inlineSchemaFirst\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " $ref: '#/components/schemas/InlineSchemaFirst'\n" + + " /test/inlineSchemaSecond:\n" + + " get:\n" + + " operationId: inlineSchemaSecond\n" + + " requestBody:\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " type: object\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " propertySecond1:\n" + + " $ref: '#/components/schemas/InlineSchemaPropertySecond'\n" + + " property2:\n" + + " $ref: '#/components/schemas/InlineSchemaPropertyFirst'\n" + + " description: InlineSchemaSecond API\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " $ref: '#/components/schemas/InlineSchemaSecond'\n" + + "components:\n" + + " schemas:\n" + + " InlineSchemaFirst:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " description: InlineSchemaFirst property 1\n" + + " nullable: true\n" + + " allOf:\n" + + " - $ref: '#/components/schemas/InlineSchemaPropertyFirst'\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " example: example\n" + + " InlineSchemaPropertyFirst:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " example: example\n" + + " InlineSchemaPropertySecond:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " $ref: '#/components/schemas/InlineSchemaSimple'\n" + + " description: propertysecond\n" + + " nullable: true\n" + + " example: examplesecond\n" + + " InlineSchemaPropertySimple:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " InlineSchemaSecond:\n" + + " type: object\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " propertySecond1:\n" + + " $ref: '#/components/schemas/InlineSchemaPropertySecond'\n" + + " property2:\n" + + " $ref: '#/components/schemas/InlineSchemaPropertyFirst'\n" + + " description: InlineSchemaSecond API\n" + + " InlineSchemaSimple:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " property2:\n" + + " description: property 2\n" + + " example: example\n" + + " allOf:\n" + + " - $ref: '#/components/schemas/InlineSchemaPropertySimple'\n"; + SerializationMatchers.assertEqualsToYaml(openAPI, yaml); + ModelConverters.reset(); + } +} diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionInlineTest.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionInlineTest.java new file mode 100644 index 0000000000..320e99b751 --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionInlineTest.java @@ -0,0 +1,235 @@ +package io.swagger.v3.jaxrs2; + +import io.swagger.v3.core.converter.ModelConverters; +import io.swagger.v3.jaxrs2.matchers.SerializationMatchers; +import io.swagger.v3.jaxrs2.schemaResolution.SchemaResolutionResource; +import io.swagger.v3.oas.integration.SwaggerConfiguration; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; +import org.testng.annotations.Test; + +public class SchemaResolutionInlineTest { + @Test + public void testSchemaResolutionInline() { + ModelConverters.reset(); + Reader reader = new Reader(new SwaggerConfiguration().openAPI(new OpenAPI()).schemaResolution(Schema.SchemaResolution.INLINE)); + OpenAPI openAPI = reader.read(SchemaResolutionResource.class); + String yaml = "openapi: 3.0.1\n" + + "paths:\n" + + " /test/inlineSchemaFirst:\n" + + " get:\n" + + " operationId: inlineSchemaFirst\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: InlineSchemaFirst property 1\n" + + " nullable: true\n" + + " example: example\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: ' InlineSchemaFirst property 2'\n" + + " example: example 2\n" + + " /test/inlineSchemaSecond:\n" + + " get:\n" + + " operationId: inlineSchemaSecond\n" + + " requestBody:\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " type: object\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " propertySecond1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 2\n" + + " example: example\n" + + " description: InlineSchemaSecond property 1\n" + + " nullable: true\n" + + " example: examplesecond\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: InlineSchemaSecond property 2\n" + + " example: InlineSchemaSecond example 2\n" + + " description: InlineSchemaSecond API\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " type: object\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " propertySecond1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 2\n" + + " example: example\n" + + " description: InlineSchemaSecond property 1\n" + + " nullable: true\n" + + " example: examplesecond\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: InlineSchemaSecond property 2\n" + + " example: InlineSchemaSecond example 2\n" + + "components:\n" + + " schemas:\n" + + " InlineSchemaFirst:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: InlineSchemaFirst property 1\n" + + " nullable: true\n" + + " example: example\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: ' InlineSchemaFirst property 2'\n" + + " example: example 2\n" + + " InlineSchemaPropertyFirst:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " example: example\n" + + " InlineSchemaPropertySecond:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 2\n" + + " example: example\n" + + " description: propertysecond\n" + + " nullable: true\n" + + " example: examplesecond\n" + + " InlineSchemaPropertySimple:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " example: example\n" + + " InlineSchemaSecond:\n" + + " type: object\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " propertySecond1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 2\n" + + " example: example\n" + + " description: InlineSchemaSecond property 1\n" + + " nullable: true\n" + + " example: examplesecond\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: InlineSchemaSecond property 2\n" + + " example: InlineSchemaSecond example 2\n" + + " description: InlineSchemaSecond API\n" + + " InlineSchemaSimple:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 2\n" + + " example: example\n\n"; + SerializationMatchers.assertEqualsToYaml(openAPI, yaml); + ModelConverters.reset(); + } +} diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionAnnotatedResource.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionAnnotatedResource.java new file mode 100644 index 0000000000..575e374752 --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionAnnotatedResource.java @@ -0,0 +1,83 @@ +package io.swagger.v3.jaxrs2.schemaResolution; + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("test") +public class SchemaResolutionAnnotatedResource { + + @GET + @Path("/inlineSchemaSecond") + public InlineSchemaSecond inlineSchemaSecond(@Schema(description = "InlineSchemaSecond API", schemaResolution = Schema.SchemaResolution.INLINE) InlineSchemaSecond inlineSchemaSecond) { + return null; + } + @GET + @Path("/inlineSchemaFirst") + public InlineSchemaFirst inlineSchemaFirst() { + return null; + } + + + static class InlineSchemaFirst { + + // public String foo; + + @Schema(description = "InlineSchemaFirst property 1", nullable = true, schemaResolution = Schema.SchemaResolution.ALL_OF_REF) + public InlineSchemaPropertyFirst property1; + + + private InlineSchemaPropertyFirst property2; + + @Schema(description = " InlineSchemaFirst property 2", example = "example 2", schemaResolution = Schema.SchemaResolution.INLINE) + public InlineSchemaPropertyFirst getProperty2() { + return null; + } + } + + static class InlineSchemaSecond { + + public String foo; + + @Schema(description = "InlineSchemaSecond property 1", nullable = true) + public InlineSchemaPropertySecond propertySecond1; + + + private InlineSchemaPropertyFirst property2; + + @Schema(description = "InlineSchemaSecond property 2", example = "InlineSchemaSecond example 2") + public InlineSchemaPropertyFirst getProperty2() { + return null; + } + } + + @Schema(description = "property", example = "example") + static class InlineSchemaPropertyFirst { + public String bar; + } + + @Schema(description = "propertysecond", example = "examplesecond") + static class InlineSchemaPropertySecond { + public InlineSchemaSimple bar; + } + + static class InlineSchemaSimple { + + @Schema(description = "property 1", schemaResolution = Schema.SchemaResolution.INLINE) + public InlineSchemaPropertySimple property1; + + + private InlineSchemaPropertySimple property2; + + @Schema(description = "property 2", example = "example", schemaResolution = Schema.SchemaResolution.ALL_OF_REF) + public InlineSchemaPropertySimple getProperty2() { + return null; + } + } + + @Schema(description = "property") + static class InlineSchemaPropertySimple { + public String bar; + } +} diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionAnnotatedSimpleResource.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionAnnotatedSimpleResource.java new file mode 100644 index 0000000000..013dd4d26e --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionAnnotatedSimpleResource.java @@ -0,0 +1,32 @@ +package io.swagger.v3.jaxrs2.schemaResolution; + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("test") +public class SchemaResolutionAnnotatedSimpleResource { + + @GET + @Path("/inlineSchemaFirst") + public InlineSchemaFirst inlineSchemaFirst() { + return null; + } + + + static class InlineSchemaFirst { + + private InlineSchemaPropertyFirst property2; + + @Schema(description = " InlineSchemaFirst property 2", example = "example 2", schemaResolution = Schema.SchemaResolution.INLINE) + public InlineSchemaPropertyFirst getProperty2() { + return null; + } + } + + @Schema(description = "property", example = "example") + static class InlineSchemaPropertyFirst { + // public String bar; + } +} diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionResource.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionResource.java new file mode 100644 index 0000000000..6f909e1d44 --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionResource.java @@ -0,0 +1,82 @@ +package io.swagger.v3.jaxrs2.schemaResolution; + +import io.swagger.v3.oas.annotations.media.Schema; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("test") +public class SchemaResolutionResource { + + @GET + @Path("/inlineSchemaSecond") + public InlineSchemaSecond inlineSchemaSecond(@Schema(description = "InlineSchemaSecond API") InlineSchemaSecond inlineSchemaSecond) { + return null; + } + @GET + @Path("/inlineSchemaFirst") + public InlineSchemaFirst inlineSchemaFirst() { + return null; + } + + + static class InlineSchemaFirst { + + // public String foo; + + @Schema(description = "InlineSchemaFirst property 1", nullable = true) + public InlineSchemaPropertyFirst property1; + + + private InlineSchemaPropertyFirst property2; + + @Schema(description = " InlineSchemaFirst property 2", example = "example 2") + public InlineSchemaPropertyFirst getProperty2() { + return null; + } + } + + static class InlineSchemaSecond { + + public String foo; + + @Schema(description = "InlineSchemaSecond property 1", nullable = true) + public InlineSchemaPropertySecond propertySecond1; + + + private InlineSchemaPropertyFirst property2; + + @Schema(description = "InlineSchemaSecond property 2", example = "InlineSchemaSecond example 2") + public InlineSchemaPropertyFirst getProperty2() { + return null; + } + } + + @Schema(description = "property", example = "example") + static class InlineSchemaPropertyFirst { + public String bar; + } + + @Schema(description = "propertysecond", example = "examplesecond") + static class InlineSchemaPropertySecond { + public InlineSchemaSimple bar; + } + + static class InlineSchemaSimple { + + @Schema(description = "property 1") + public InlineSchemaPropertySimple property1; + + + private InlineSchemaPropertySimple property2; + + @Schema(description = "property 2", example = "example") + public InlineSchemaPropertySimple getProperty2() { + return null; + } + } + + @Schema(description = "property") + static class InlineSchemaPropertySimple { + public String bar; + } +} diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionResourceSimple.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionResourceSimple.java new file mode 100644 index 0000000000..fc043bc168 --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionResourceSimple.java @@ -0,0 +1,42 @@ +package io.swagger.v3.jaxrs2.schemaResolution; + +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; + +@Path("test") +public class SchemaResolutionResourceSimple { + + + @GET + @Path("/inlineSchemaFirst") + @ApiResponse(description = "InlineSchemaFirst Response API", content = @Content(schema = @Schema(implementation = InlineSchemaFirst.class))) + public Response inlineSchemaFirst() { + return null; + } + + + @GET + @Path("/inlineSchemaSecond") + public void inlineSchemaFirst(@Schema(description = "InlineSchemaSecond API") InlineSchemaFirst inlineSchemaFirst) { + } + + + + static class InlineSchemaFirst { + + // public String foo; + + @Schema(description = "InlineSchemaFirst property 1", nullable = true) + public InlineSchemaPropertyFirst property1; + } + + @Schema(description = "property", example = "example") + static class InlineSchemaPropertyFirst { + public String bar; + } +} diff --git a/modules/swagger-jaxrs2/src/test/resources/petstore/WebHookResource.yaml b/modules/swagger-jaxrs2/src/test/resources/petstore/WebHookResource.yaml index 34ed3ea8f9..8569aafe81 100644 --- a/modules/swagger-jaxrs2/src/test/resources/petstore/WebHookResource.yaml +++ b/modules/swagger-jaxrs2/src/test/resources/petstore/WebHookResource.yaml @@ -2,6 +2,7 @@ openapi: 3.1.0 components: schemas: SubscriptionResponse: + type: object properties: subscriptionId: type: string diff --git a/modules/swagger-jaxrs2/src/test/resources/petstore/callbacks/ComplexCallback31Resource.yaml b/modules/swagger-jaxrs2/src/test/resources/petstore/callbacks/ComplexCallback31Resource.yaml index 3fe539678e..65b5cec4f2 100644 --- a/modules/swagger-jaxrs2/src/test/resources/petstore/callbacks/ComplexCallback31Resource.yaml +++ b/modules/swagger-jaxrs2/src/test/resources/petstore/callbacks/ComplexCallback31Resource.yaml @@ -50,6 +50,7 @@ paths: components: schemas: User: + type: object properties: id: type: integer diff --git a/modules/swagger-jaxrs2/src/test/resources/petstore/parameters/Parameters31Resource.yaml b/modules/swagger-jaxrs2/src/test/resources/petstore/parameters/Parameters31Resource.yaml index 3cb83e1350..e6b22d1bcc 100644 --- a/modules/swagger-jaxrs2/src/test/resources/petstore/parameters/Parameters31Resource.yaml +++ b/modules/swagger-jaxrs2/src/test/resources/petstore/parameters/Parameters31Resource.yaml @@ -100,6 +100,7 @@ paths: components: schemas: Category: + type: object properties: id: type: integer @@ -109,6 +110,7 @@ components: xml: name: Category Pet: + type: object properties: id: type: integer @@ -139,6 +141,7 @@ components: xml: name: Pet Tag: + type: object properties: id: type: integer @@ -146,8 +149,9 @@ components: name: type: string xml: - name: Tag + name: tag User: + type: object properties: id: type: integer @@ -171,6 +175,7 @@ components: xml: name: User SubscriptionResponse: + type: object properties: subscriptionId: type: string diff --git a/modules/swagger-jaxrs2/src/test/resources/petstore/requestbody/RequestBody31Resource.yaml b/modules/swagger-jaxrs2/src/test/resources/petstore/requestbody/RequestBody31Resource.yaml index 3229ea10b2..f01cdb9024 100644 --- a/modules/swagger-jaxrs2/src/test/resources/petstore/requestbody/RequestBody31Resource.yaml +++ b/modules/swagger-jaxrs2/src/test/resources/petstore/requestbody/RequestBody31Resource.yaml @@ -59,6 +59,7 @@ paths: components: schemas: User: + type: object properties: id: type: integer diff --git a/modules/swagger-maven-plugin/README.md b/modules/swagger-maven-plugin/README.md index 1e76742b2b..fc6c0fc244 100644 --- a/modules/swagger-maven-plugin/README.md +++ b/modules/swagger-maven-plugin/README.md @@ -22,7 +22,7 @@ Both `javax` and `jakarta` examples are provided below io.swagger.core.v3 swagger-maven-plugin - 2.2.23 + 2.2.25 openapi ${project.build.directory}/generatedtest @@ -47,7 +47,7 @@ Both `javax` and `jakarta` examples are provided below io.swagger.core.v3 swagger-jaxrs2 - 2.2.23 + 2.2.25 @@ -73,7 +73,7 @@ Both `javax` and `jakarta` examples are provided below io.swagger.core.v3 swagger-maven-plugin-jakarta - 2.2.23 + 2.2.25 openapi ${project.build.directory}/generatedtest @@ -98,7 +98,7 @@ Both `javax` and `jakarta` examples are provided below io.swagger.core.v3 swagger-jaxrs2-jakarta - 2.2.23 + 2.2.25 @@ -126,7 +126,7 @@ Both `javax` and `jakarta` examples are provided below io.swagger.core.v3 swagger-maven-plugin - 2.2.23 + 2.2.25 openapi ${project.build.directory}/generatedtest @@ -155,7 +155,7 @@ Both `javax` and `jakarta` examples are provided below io.swagger.core.v3 swagger-maven-plugin-jakarta - 2.2.23 + 2.2.25 openapi ${project.build.directory}/generatedtest @@ -177,29 +177,31 @@ Both `javax` and `jakarta` examples are provided below #### Parameters Parameter | Description | Required | Default ---------- | ----------- | --------- | ------- -`outputPath`|output path where file(s) are saved|true| -`outputFileName`|file name (no extension)|false|`openapi` -`outputFormat`|file format (`JSON`, `YAML`, `JSONANDYAML`|false|`JSON` -`skip`|if `TRUE` skip execution|false|`FALSE` -`encoding`|encoding of output file(s)|false| -`resourcePackages`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false| -`resourceClasses`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false| -`prettyPrint`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|`TRUE` -`sortOutput`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|`FALSE` -`alwaysResolveAppPath`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|`FALSE` -`skipResolveAppPath`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|`FALSE` -`openapiFilePath`|path to openapi file to be merged with resolved specification, see [config](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false| -`configurationFilePath`|path to swagger config file to be merged with resolved specification, see [config](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration)|false| -`filterClass`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false| -`readerClass`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false| -`scannerClass`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false| -`readAllResources`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false| -`ignoredRoutes`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false| -`objectMapperProcessorClass`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false| -`defaultResponseCode`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false| -`modelConverterClasses`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false| -`contextId`|see [Context](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#context)|false|${project.artifactId} +--------- | ----------- |---------| ------- +`outputPath`|output path where file(s) are saved| true | +`outputFileName`|file name (no extension)| false |`openapi` +`outputFormat`|file format (`JSON`, `YAML`, `JSONANDYAML`| false |`JSON` +`skip`|if `TRUE` skip execution| false |`FALSE` +`encoding`|encoding of output file(s)| false | +`resourcePackages`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false | +`resourceClasses`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false | +`prettyPrint`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false |`TRUE` +`sortOutput`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false |`FALSE` +`alwaysResolveAppPath`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false |`FALSE` +`skipResolveAppPath`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false |`FALSE` +`openapiFilePath`|path to openapi file to be merged with resolved specification, see [config](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false | +`configurationFilePath`|path to swagger config file to be merged with resolved specification, see [config](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration)| false | +`filterClass`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false | +`readerClass`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false | +`scannerClass`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false | +`readAllResources`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false | +`ignoredRoutes`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false | +`objectMapperProcessorClass`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false | +`defaultResponseCode`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false | +`modelConverterClasses`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false | +`contextId`|see [Context](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#context)| false |${project.artifactId} +`openapi31`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| false | +`schemaResolution`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)| DEFAULT | *** @@ -207,6 +209,8 @@ Since version 2.0.8, `configurationFilePath` parameter is available, allowing to Since version 2.1.6, `sortOutput` parameter is available, allowing to sort object properties and map keys alphabetically. Since version 2.1.6, `objectMapperProcessorClass` allows to configure also the ObjectMapper instance used to serialize the resolved OpenAPI -Since version 2.1.9, `alwaysResolveAppPath` parameter is available, allowing to trigger resolving of Application Path from annotaion also not in runtime (e.g. using servlet in separate application, or in maven plugin at build time, etc) +Since version 2.1.9, `alwaysResolveAppPath` parameter is available, allowing to trigger resolving of Application Path from annotation also not in runtime (e.g. using servlet in separate application, or in maven plugin at build time, etc) +Since version 2.2.12, `openapi31` parameter is available, if set to true the resolved spec will be processed into a 3.1.0 specification by resolving according to OAS 3.1 rules Since version 2.1.15, `skipResolveAppPath` parameter is available, allowing to skip resolving of Application Path from annotation Since version 2.2.17, `defaultResponseCode` parameter is available, allowing to set the code used when resolving responses with no http status code annotation +Since version 2.2.24, `schemaResolution` parameter is available, allowing to specify how object schemas and object properties within schemas are resolved for OAS 3.0 specification \ No newline at end of file diff --git a/modules/swagger-maven-plugin/pom.xml b/modules/swagger-maven-plugin/pom.xml index e314cab31f..a21fa1d680 100644 --- a/modules/swagger-maven-plugin/pom.xml +++ b/modules/swagger-maven-plugin/pom.xml @@ -4,7 +4,7 @@ io.swagger.core.v3 swagger-project - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../.. 4.0.0 @@ -17,7 +17,7 @@ org.codehaus.plexus plexus-component-metadata - 2.0.0 + 2.1.1 @@ -88,7 +88,7 @@ org.apache.maven.plugin-tools maven-plugin-annotations - 3.10.2 + 3.15.0 provided @@ -150,7 +150,7 @@ org.codehaus.plexus plexus-component-annotations - 2.1.1 + 2.2.0 io.swagger.core.v3 @@ -290,7 +290,7 @@ UTF-8 - 3.9.6 + 3.9.9 4.13.2 9.4.53.v20231009 1.26.1 diff --git a/modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/SwaggerMojo.java b/modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/SwaggerMojo.java index 05911233d7..60bafc3d44 100644 --- a/modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/SwaggerMojo.java +++ b/modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/SwaggerMojo.java @@ -11,6 +11,7 @@ import io.swagger.v3.oas.integration.SwaggerConfiguration; import io.swagger.v3.oas.integration.api.OpenApiContext; import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -184,6 +185,7 @@ private void setDefaultsIfMissing(SwaggerConfiguration config) { if (config.isConvertToOpenAPI31() == null) { config.setConvertToOpenAPI31(convertToOpenAPI31); } + } /** @@ -355,6 +357,10 @@ private SwaggerConfiguration mergeConfig(OpenAPI openAPIInput, SwaggerConfigurat config.openAPI31(openapi31); } + if (StringUtils.isNotBlank(schemaResolution)) { + config.schemaResolution(Schema.SchemaResolution.valueOf(schemaResolution)); + } + return config; } @@ -453,6 +459,12 @@ private boolean isCollectionNotBlank(Collection collection) { @Parameter(property = "resolve.convertToOpenAPI31") private Boolean convertToOpenAPI31; + /** + * @since 2.2.24 + */ + @Parameter(property = "resolve.schemaResolution") + private String schemaResolution; + private String projectEncoding = "UTF-8"; private SwaggerConfiguration config; diff --git a/modules/swagger-models/pom.xml b/modules/swagger-models/pom.xml index 3b0cabbe4e..82a1ad0b4c 100644 --- a/modules/swagger-models/pom.xml +++ b/modules/swagger-models/pom.xml @@ -4,7 +4,7 @@ io.swagger.core.v3 swagger-project - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../.. 4.0.0 diff --git a/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/Schema.java b/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/Schema.java index 8872a74c2c..d17c2a4f62 100644 --- a/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/Schema.java +++ b/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/Schema.java @@ -1,6 +1,7 @@ package io.swagger.v3.oas.models.media; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.models.annotations.OpenAPI30; import io.swagger.v3.oas.models.annotations.OpenAPI31; import io.swagger.v3.oas.models.Components; @@ -44,6 +45,29 @@ public String toString() { } } + public static final String SCHEMA_RESOLUTION_PROPERTY = "schema-resolution"; + public enum SchemaResolution { + @JsonProperty("default") + DEFAULT("default"), + @JsonProperty("inline") + INLINE("inline"), + @JsonProperty("all-of") + ALL_OF("all-of"), + @JsonProperty("all-of-ref") + ALL_OF_REF("all-of-ref"); + + private String value; + + SchemaResolution(String value) { + this.value = value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + } + protected T _default; private String name; diff --git a/modules/swagger-project-jakarta/modules/swagger-annotations-jakarta/pom.xml b/modules/swagger-project-jakarta/modules/swagger-annotations-jakarta/pom.xml index caf6327b60..c5abf8bd9e 100644 --- a/modules/swagger-project-jakarta/modules/swagger-annotations-jakarta/pom.xml +++ b/modules/swagger-project-jakarta/modules/swagger-annotations-jakarta/pom.xml @@ -4,7 +4,7 @@ io.swagger.core.v3 swagger-project-jakarta - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../.. 4.0.0 diff --git a/modules/swagger-project-jakarta/modules/swagger-core-jakarta/pom.xml b/modules/swagger-project-jakarta/modules/swagger-core-jakarta/pom.xml index 896ca77849..0ca2bcc767 100644 --- a/modules/swagger-project-jakarta/modules/swagger-core-jakarta/pom.xml +++ b/modules/swagger-project-jakarta/modules/swagger-core-jakarta/pom.xml @@ -4,7 +4,7 @@ io.swagger.core.v3 swagger-project-jakarta - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../.. 4.0.0 diff --git a/modules/swagger-project-jakarta/modules/swagger-integration-jakarta/pom.xml b/modules/swagger-project-jakarta/modules/swagger-integration-jakarta/pom.xml index d9091ea7d8..7f41dae87d 100644 --- a/modules/swagger-project-jakarta/modules/swagger-integration-jakarta/pom.xml +++ b/modules/swagger-project-jakarta/modules/swagger-integration-jakarta/pom.xml @@ -4,7 +4,7 @@ io.swagger.core.v3 swagger-project-jakarta - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../.. 4.0.0 diff --git a/modules/swagger-project-jakarta/modules/swagger-jaxrs2-jakarta/pom.xml b/modules/swagger-project-jakarta/modules/swagger-jaxrs2-jakarta/pom.xml index 044997af99..f3322ba40b 100644 --- a/modules/swagger-project-jakarta/modules/swagger-jaxrs2-jakarta/pom.xml +++ b/modules/swagger-project-jakarta/modules/swagger-jaxrs2-jakarta/pom.xml @@ -4,7 +4,7 @@ io.swagger.core.v3 swagger-project-jakarta - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../.. 4.0.0 diff --git a/modules/swagger-project-jakarta/modules/swagger-jaxrs2-servlet-initializer-jakarta/pom.xml b/modules/swagger-project-jakarta/modules/swagger-jaxrs2-servlet-initializer-jakarta/pom.xml index ecd6390232..141d80b286 100644 --- a/modules/swagger-project-jakarta/modules/swagger-jaxrs2-servlet-initializer-jakarta/pom.xml +++ b/modules/swagger-project-jakarta/modules/swagger-jaxrs2-servlet-initializer-jakarta/pom.xml @@ -4,7 +4,7 @@ io.swagger.core.v3 swagger-project-jakarta - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../.. 4.0.0 diff --git a/modules/swagger-project-jakarta/modules/swagger-jaxrs2-servlet-initializer-v2-jakarta/pom.xml b/modules/swagger-project-jakarta/modules/swagger-jaxrs2-servlet-initializer-v2-jakarta/pom.xml index 0d2a616115..9d2f9eec0f 100644 --- a/modules/swagger-project-jakarta/modules/swagger-jaxrs2-servlet-initializer-v2-jakarta/pom.xml +++ b/modules/swagger-project-jakarta/modules/swagger-jaxrs2-servlet-initializer-v2-jakarta/pom.xml @@ -4,7 +4,7 @@ io.swagger.core.v3 swagger-project-jakarta - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../.. 4.0.0 diff --git a/modules/swagger-project-jakarta/modules/swagger-maven-plugin-jakarta/pom.xml b/modules/swagger-project-jakarta/modules/swagger-maven-plugin-jakarta/pom.xml index 67364ce61e..a38eca28fb 100644 --- a/modules/swagger-project-jakarta/modules/swagger-maven-plugin-jakarta/pom.xml +++ b/modules/swagger-project-jakarta/modules/swagger-maven-plugin-jakarta/pom.xml @@ -4,7 +4,7 @@ io.swagger.core.v3 swagger-project-jakarta - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../.. 4.0.0 @@ -141,7 +141,7 @@ org.apache.maven.plugin-tools maven-plugin-annotations - 3.10.2 + 3.15.0 provided @@ -163,7 +163,7 @@ org.codehaus.plexus plexus-component-annotations - 2.1.1 + 2.2.0 @@ -176,6 +176,6 @@ UTF-8 - 3.9.6 + 3.9.9 diff --git a/modules/swagger-project-jakarta/modules/swagger-models-jakarta/pom.xml b/modules/swagger-project-jakarta/modules/swagger-models-jakarta/pom.xml index c9bce80e88..438b2f4594 100644 --- a/modules/swagger-project-jakarta/modules/swagger-models-jakarta/pom.xml +++ b/modules/swagger-project-jakarta/modules/swagger-models-jakarta/pom.xml @@ -4,7 +4,7 @@ io.swagger.core.v3 swagger-project-jakarta - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT ../.. 4.0.0 diff --git a/modules/swagger-project-jakarta/pom.xml b/modules/swagger-project-jakarta/pom.xml index 11639bc6c5..005a70316a 100644 --- a/modules/swagger-project-jakarta/pom.xml +++ b/modules/swagger-project-jakarta/pom.xml @@ -6,7 +6,7 @@ pom swagger-project-jakarta swagger-project-jakarta - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT https://github.com/swagger-api/swagger-core scm:git:git@github.com:swagger-api/swagger-core.git @@ -475,26 +475,26 @@ 8 2.2.3 - 2.12.7 - 2.2 + 2.13.0 + 2.3 3.1.0 - 3.0.2 + 3.1.0 3.0.1 - 2.1.2 + 2.1.3 6.4.0 5.0.0 - 3.1.5 + 3.1.8 4.13.2 2.16.2 2.16.2 - 1.5.3 - 4.8.165 + 1.5.8 + 4.8.176 32.1.3-jre 1.10.14 - 3.14.0 - 2.15.1 + 3.17.0 + 2.17.0 2.0.9 - 9.4.53.v20231009 + 9.4.56.v20240826g 7.9.0 2.28.2 4.5.1 @@ -512,7 +512,7 @@ UTF-8 https://oss.sonatype.org/content/repositories/snapshots/ 0.5.0 - 3.9.6 + 3.9.9 1.26.1 diff --git a/pom.xml b/pom.xml index 355de17851..22979c7f2f 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ pom swagger-project swagger-project - 2.2.24-SNAPSHOT + 2.2.26-SNAPSHOT https://github.com/swagger-api/swagger-core scm:git:git@github.com:swagger-api/swagger-core.git @@ -395,6 +395,16 @@ + + java-17-modules + + [17,) + + + modules/swagger-java17-support + + + modules/swagger-annotations @@ -629,8 +639,8 @@ 8 2.2.3 - 2.12.7 - 2.2 + 2.13.0 + 2.3 2.1.6 2.3.3 1.2.2 @@ -640,21 +650,21 @@ 4.13.2 2.16.2 2.16.2 - 1.5.3 - 4.8.165 + 1.5.8 + 4.8.176 32.1.3-jre 1.10.14 - 3.14.0 - 2.15.1 + 3.17.0 + 2.17.0 2.0.9 - 9.4.53.v20231009 + 9.4.56.v20240826 7.8.0 2.28.2 4.5.1 4.5.14 1.16.0 - 2.22.2 + 3.5.0 3.2.1 2.22.2