diff --git a/.github/ISSUE_TEMPLATE/01_bug_report.md b/.github/ISSUE_TEMPLATE/01_bug_report.md
new file mode 100644
index 0000000000..87df8b5cca
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/01_bug_report.md
@@ -0,0 +1,59 @@
+---
+name: Bug Report
+about: Report an issue in swagger-core
+title: "[Bug]: "
+labels: Bug
+assignees: ''
+---
+
+## Description of the problem/issue
+
+
+
+## Affected Version
+
+
+e.g. 2.2.21
+
+
+Earliest version the bug appears in (if known):
+e.g. 2.2.17
+
+## Steps to Reproduce
+
+
+
+1. ...
+2. ...
+3. ...
+
+## Expected Behavior
+
+
+
+## Actual Behavior
+
+
+
+## Logs / Stack Traces
+
+
+
+## Additional Context
+
+
+
+## Checklist
+
+- [ ] I have searched the [existing issues](https://github.com/swagger-api/swagger-core/issues) and this is not a duplicate.
+- [ ] I have provided sufficient information for maintainers to reproduce the issue.
diff --git a/.github/ISSUE_TEMPLATE/02_question.md b/.github/ISSUE_TEMPLATE/02_question.md
new file mode 100644
index 0000000000..45f6b5c21f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/02_question.md
@@ -0,0 +1,43 @@
+---
+name: Question
+about: Ask a question about swagger-core usage or behavior
+title: "[Question]: "
+labels: Question
+assignees: ''
+---
+
+## Question
+
+
+
+## Affected Version
+
+
+e.g. 2.2.21
+
+## Context
+
+
+
+```java
+// Example code snippet here
+```
+
+## Additional Details
+
+
+
+## Checklist
+
+- [ ] I have searched the [existing issues](https://github.com/swagger-api/swagger-core/issues) and documentation before asking.
+- [ ] I have provided enough information for others to understand my question.
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/03_feature_request.md b/.github/ISSUE_TEMPLATE/03_feature_request.md
new file mode 100644
index 0000000000..9450a7b501
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/03_feature_request.md
@@ -0,0 +1,50 @@
+---
+name: Feature Request
+about: Suggest a new feature or enhancement for swagger-core
+title: "[Feature]: "
+labels: Feature
+assignees: ''
+---
+
+## Feature Description
+
+
+
+## Use Case
+
+
+
+## Suggested Solution (optional)
+
+
+
+## Alternatives Considered
+
+
+
+## Additional Context
+
+
+
+## Checklist
+
+- [ ] I have searched the [existing issues](https://github.com/swagger-api/swagger-core/issues) to ensure this is not a duplicate.
+- [ ] This feature would be useful to more than just my use case.
+- [ ] I have provided enough detail for the maintainers to understand the scope of the request.
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000000..d8d9a24e9d
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,43 @@
+# Pull Request
+
+Thank you for contributing to **swagger-core**!
+
+Please fill out the following information to help us review your PR efficiently.
+
+---
+
+## Description
+
+
+
+Fixes:
+
+## Type of Change
+
+
+
+- [ ] ๐ Bug fix
+- [ ] โจ New feature
+- [ ] โป๏ธ Refactor (non-breaking change)
+- [ ] ๐งช Tests
+- [ ] ๐ Documentation
+- [ ] ๐งน Chore (build or tooling)
+
+## Checklist
+
+
+
+- [ ] I have added/updated tests as needed
+- [ ] I have added/updated documentation where applicable
+- [ ] The PR title is descriptive
+- [ ] The code builds and passes tests locally
+- [ ] I have linked related issues (if any)
+
+## Screenshots / Additional Context
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 71f9b737cd..ce9f733d77 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,13 @@ The OpenAPI Specification has undergone several revisions since initial creation
Swagger core Version | Release Date | OpenAPI Spec compatibility | Notes | Status
------------------------- | ------------ | -------------------------- | ----- | ----
-2.2.33 (**current stable**)| 2025-06-12 | 3.x | [tag v2.2.33](https://github.com/swagger-api/swagger-core/tree/v2.2.33) | Supported
+2.2.39 (**current stable**)| 2025-10-13 | 3.x | [tag v2.2.39](https://github.com/swagger-api/swagger-core/tree/v2.2.39) | Supported
+2.2.38 | 2025-09-29 | 3.x | [tag v2.2.38](https://github.com/swagger-api/swagger-core/tree/v2.2.38) | Supported
+2.2.37 | 2025-09-16 | 3.x | [tag v2.2.37](https://github.com/swagger-api/swagger-core/tree/v2.2.37) | Supported
+2.2.36 | 2025-08-18 | 3.x | [tag v2.2.36](https://github.com/swagger-api/swagger-core/tree/v2.2.36) | Supported
+2.2.35 | 2025-07-31 | 3.x | [tag v2.2.35](https://github.com/swagger-api/swagger-core/tree/v2.2.35) | Supported
+2.2.34 | 2025-06-20 | 3.x | [tag v2.2.34](https://github.com/swagger-api/swagger-core/tree/v2.2.34) | Supported
+2.2.33 | 2025-06-12 | 3.x | [tag v2.2.33](https://github.com/swagger-api/swagger-core/tree/v2.2.33) | Supported
2.2.32 | 2025-05-14 | 3.x | [tag v2.2.32](https://github.com/swagger-api/swagger-core/tree/v2.2.32) | Supported
2.2.31 | 2025-05-13 | 3.x | [tag v2.2.31](https://github.com/swagger-api/swagger-core/tree/v2.2.31) | Supported
2.2.30 | 2025-04-07 | 3.x | [tag v2.2.30](https://github.com/swagger-api/swagger-core/tree/v2.2.30) | Supported
@@ -126,7 +132,7 @@ You need the following installed and available in your $PATH:
* Jackson 2.4.5 or greater
-### To build from source (currently 2.2.34-SNAPSHOT)
+### To build from source (currently 2.2.40-SNAPSHOT)
```
# first time building locally
mvn -N
diff --git a/modules/swagger-annotations/pom.xml b/modules/swagger-annotations/pom.xml
index b1771bd99d..14fbb1a6f9 100644
--- a/modules/swagger-annotations/pom.xml
+++ b/modules/swagger-annotations/pom.xml
@@ -3,7 +3,7 @@
io.swagger.core.v3swagger-project
- 2.2.34-SNAPSHOT
+ 2.2.40-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 1271ee3fd6..fb3c90fe81 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
@@ -176,8 +176,11 @@
/**
* Allows to specify the required mode (RequiredMode.AUTO, REQUIRED, NOT_REQUIRED)
- *
- * RequiredMode.AUTO: will let the library decide based on its heuristics.
+ * RequiredMode.AUTO: the library decides using heuristics:
+ * - Bean Validation / nullability annotations (@NotNull, @NonNull, @NotBlank, @NotEmpty) - required
+ * - Optional - not required
+ * - Primitive types (int, boolean, etc.) - not required unless annotated
+ * - Other object fields without any constraints - not required
* RequiredMode.REQUIRED: will force the item to be considered as required regardless of heuristics.
* RequiredMode.NOT_REQUIRED: will force the item to be considered as not required regardless of heuristics.
*
diff --git a/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/responses/FailedApiResponse.java b/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/responses/FailedApiResponse.java
new file mode 100644
index 0000000000..59be127026
--- /dev/null
+++ b/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/responses/FailedApiResponse.java
@@ -0,0 +1,57 @@
+package io.swagger.v3.oas.annotations.responses;
+
+import io.swagger.v3.oas.annotations.extensions.Extension;
+import io.swagger.v3.oas.annotations.media.Content;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A meta-annotation that bundles common error response definitions for API operations.
+ *
+ * Includes default definitions for:
+ *
+ *
400 Bad Request
+ *
401 Unauthorized
+ *
403 Forbidden
+ *
404 Not Found
+ *
429 Too Many Requests
+ *
500 Internal Server Error
+ *
503 Service Unavailable
+ *
+ * Can be used at type level to apply to all operations in a controller,
+ * or at method level for individual operations.
+ *
+ * @see ApiResponse
+ * @see ApiResponses
+ * @since 2.2.32
+ */
+
+@Target({ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@ApiResponse(responseCode = "400", description = "Bad Request", content = @Content)
+@ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content)
+@ApiResponse(responseCode = "403", description = "Forbidden", content = @Content)
+@ApiResponse(responseCode = "404", description = "Not Found", content = @Content)
+@ApiResponse(responseCode = "429", description = "Too Many Requests", content = @Content)
+@ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content)
+@ApiResponse(responseCode = "503", description = "Service Unavailable", content = @Content)
+public @interface FailedApiResponse {
+ /**
+ * A reference to a response definition in components responses.
+ *
+ * @return the reference
+ * @since 2.2.32
+ */
+ String ref() default "";
+
+ /**
+ * The list of optional extensions.
+ *
+ * @return an optional array of extensions
+ * @since 2.2.32
+ */
+ Extension[] extensions() default {};
+}
diff --git a/modules/swagger-core/pom.xml b/modules/swagger-core/pom.xml
index fa0adfdcdc..9c69430078 100644
--- a/modules/swagger-core/pom.xml
+++ b/modules/swagger-core/pom.xml
@@ -3,7 +3,7 @@
io.swagger.core.v3swagger-project
- 2.2.34-SNAPSHOT
+ 2.2.40-SNAPSHOT../..4.0.0
diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/AnnotatedType.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/AnnotatedType.java
index da0b642f0d..e9a252fcd8 100644
--- a/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/AnnotatedType.java
+++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/AnnotatedType.java
@@ -8,9 +8,11 @@
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
+import java.util.stream.Collectors;
public class AnnotatedType {
private Type type;
@@ -21,6 +23,7 @@ public class AnnotatedType {
private boolean schemaProperty;
private Annotation[] ctxAnnotations;
private boolean resolveAsRef;
+ private boolean resolveEnumAsRef;
private JsonView jsonViewAnnotation;
private boolean includePropertiesWithoutJSONView = true;
private boolean skipSchemaName;
@@ -88,6 +91,19 @@ public AnnotatedType resolveAsRef(boolean resolveAsRef) {
return this;
}
+ public boolean isResolveEnumAsRef() {
+ return resolveEnumAsRef;
+ }
+
+ public void setResolveEnumAsRef(boolean resolveEnumAsRef) {
+ this.resolveEnumAsRef = resolveEnumAsRef;
+ }
+
+ public AnnotatedType resolveEnumAsRef(boolean resolveEnumAsRef) {
+ this.resolveEnumAsRef = resolveEnumAsRef;
+ return this;
+ }
+
public boolean isSchemaProperty() {
return schemaProperty;
}
@@ -141,11 +157,11 @@ public AnnotatedType name(String name) {
}
public Annotation[] getCtxAnnotations() {
- return ctxAnnotations;
+ return ctxAnnotations == null ? null : Arrays.copyOf(ctxAnnotations, ctxAnnotations.length);
}
public void setCtxAnnotations(Annotation[] ctxAnnotations) {
- this.ctxAnnotations = ctxAnnotations;
+ this.ctxAnnotations = ctxAnnotations == null ? null : Arrays.copyOf(ctxAnnotations, ctxAnnotations.length);
}
public AnnotatedType ctxAnnotations(Annotation[] ctxAnnotations) {
@@ -227,49 +243,35 @@ public AnnotatedType propertyName(String propertyName) {
return this;
}
+ private List getProcessedAnnotations(Annotation[] annotations) {
+ if (annotations == null || annotations.length == 0) {
+ return new ArrayList<>();
+ }
+ return Arrays.stream(annotations)
+ .filter(a -> {
+ String pkg = a.annotationType().getPackage().getName();
+ return !pkg.startsWith("java.") && !pkg.startsWith("jdk.") && !pkg.startsWith("sun.");
+ })
+ .sorted(Comparator.comparing(a -> a.annotationType().getName()))
+ .collect(Collectors.toList());
+ }
+
@Override
public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof AnnotatedType)) {
- return false;
- }
+ if (this == o) return true;
+ if (!(o instanceof AnnotatedType)) return false;
AnnotatedType that = (AnnotatedType) o;
-
- if ((type == null && that.type != null) || (type != null && that.type == null)) {
- return false;
- }
-
- if (type != null && that.type != null && !type.equals(that.type)) {
- return false;
- }
- return Arrays.equals(this.ctxAnnotations, that.ctxAnnotations);
+ List thisAnnotatinons = getProcessedAnnotations(this.ctxAnnotations);
+ List thatAnnotatinons = getProcessedAnnotations(that.ctxAnnotations);
+ return includePropertiesWithoutJSONView == that.includePropertiesWithoutJSONView &&
+ Objects.equals(type, that.type) &&
+ Objects.equals(thisAnnotatinons, thatAnnotatinons) &&
+ Objects.equals(jsonViewAnnotation, that.jsonViewAnnotation);
}
-
@Override
public int hashCode() {
- if (ctxAnnotations == null || ctxAnnotations.length == 0) {
- return Objects.hash(type, "fixed");
- }
- List meaningfulAnnotations = new ArrayList<>();
-
- boolean hasDifference = false;
- for (Annotation a: ctxAnnotations) {
- if(!a.annotationType().getName().startsWith("sun") && !a.annotationType().getName().startsWith("jdk")) {
- meaningfulAnnotations.add(a);
- } else {
- hasDifference = true;
- }
- }
- int result = 1;
- result = 31 * result + (type == null ? 0 : Objects.hash(type, "fixed"));
- if (hasDifference) {
- result = 31 * result + meaningfulAnnotations.hashCode();
- } else {
- result = 31 * result + Arrays.hashCode(ctxAnnotations);
- }
- return result;
+ List processedAnnotations = getProcessedAnnotations(this.ctxAnnotations);
+ return Objects.hash(type, jsonViewAnnotation, includePropertiesWithoutJSONView, processedAnnotations);
}
}
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 98bcb4ddeb..7c00931b23 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
@@ -391,7 +391,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
}
List> composedSchemaReferencedClasses = getComposedSchemaReferencedClasses(type.getRawClass(), annotatedType.getCtxAnnotations(), resolvedSchemaAnnotation);
- boolean isComposedSchema = composedSchemaReferencedClasses != null;
+ boolean hasCompositionKeywords = composedSchemaReferencedClasses != null;
if (isPrimitive) {
XML xml = resolveXml(beanDesc.getClassInfo(), annotatedType.getCtxAnnotations(), resolvedSchemaAnnotation);
@@ -411,14 +411,14 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
schema.setItems(model);
return schema;
}
- if (type.isEnumType() && shouldResolveEnumAsRef(resolvedSchemaAnnotation)) {
+ if (type.isEnumType() && shouldResolveEnumAsRef(resolvedSchemaAnnotation, annotatedType.isResolveEnumAsRef())) {
// Store off the ref and add the enum as a top-level model
context.defineModel(name, model, annotatedType, null);
// Return the model as a ref only property
model = openapi31 ? new JsonSchema() : new Schema();
model.$ref(Components.COMPONENTS_SCHEMAS_REF + name);
}
- if (!isComposedSchema) {
+ if (!hasCompositionKeywords) {
if (schemaRefFromAnnotation != null && model != null) {
model.raw$ref(schemaRefFromAnnotation);
}
@@ -464,7 +464,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
if (type.isContainerType()) {
// TODO currently a MapSchema or ArraySchema don't also support composed schema props (oneOf,..)
- isComposedSchema = false;
+ hasCompositionKeywords = false;
JavaType keyType = type.getKeyType();
JavaType valueType = type.getContentType();
String pName = null;
@@ -540,6 +540,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
.propertyName(annotatedType.getPropertyName())
.jsonViewAnnotation(annotatedType.getJsonViewAnnotation())
.components(annotatedType.getComponents())
+ .resolveEnumAsRef(annotatedType.isResolveEnumAsRef())
.parent(annotatedType.getParent()));
if (items == null) {
@@ -593,7 +594,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
return null;
}
}
- } else if (isComposedSchema) {
+ } else if (hasCompositionKeywords) {
model = openapi31 ? new JsonSchema() : new ComposedSchema();
model.name(name);
if (
@@ -750,10 +751,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
Annotation[] ctxAnnotation31 = null;
Schema.SchemaResolution resolvedSchemaResolution = AnnotationsUtils.resolveSchemaResolution(this.schemaResolution, ctxSchema);
- if (
- Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) ||
- Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) ||
- openapi31) {
+ if (AnnotationsUtils.areSiblingsAllowed(resolvedSchemaResolution, openapi31)) {
List ctxAnnotations31List = new ArrayList<>();
if (annotations != null) {
for (Annotation a : annotations) {
@@ -762,9 +760,6 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
!(a instanceof io.swagger.v3.oas.annotations.media.ArraySchema)) {
ctxAnnotations31List.add(a);
}
- if ((ctxSchema != null) && (!ctxSchema.implementation().equals(Void.class) || StringUtils.isNotEmpty(ctxSchema.type()))) {
- ctxAnnotations31List.add(a);
- }
}
ctxAnnotation31 = ctxAnnotations31List.toArray(new Annotation[ctxAnnotations31List.size()]);
}
@@ -794,7 +789,8 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
.skipSchemaName(true)
.schemaProperty(true)
.components(annotatedType.getComponents())
- .propertyName(propName);
+ .propertyName(propName)
+ .resolveEnumAsRef(AnnotationsUtils.computeEnumAsRef(ctxSchema, ctxArraySchema));
if (
Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) ||
Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) ||
@@ -821,6 +817,9 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
return openapi31 ? new JsonSchema() : new Schema();
}
});
+
+ boolean areSiblingsAllowed = AnnotationsUtils.areSiblingsAllowed(resolvedSchemaResolution, openapi31);
+ aType = AnnotationsUtils.addTypeWhenSiblingsAllowed(aType, ctxSchema, areSiblingsAllowed);
property = context.resolve(aType);
property = clone(property);
Schema ctxProperty = null;
@@ -1041,9 +1040,9 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
}
}
- if (isComposedSchema) {
+ if (hasCompositionKeywords) {
- ComposedSchema composedSchema = (ComposedSchema) model;
+ Schema schemaWithCompositionKeys = model;
Class>[] allOf = resolvedSchemaAnnotation.allOf();
Class>[] anyOf = resolvedSchemaAnnotation.anyOf();
@@ -1061,12 +1060,12 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
refSchema = allOfRef;
}
// allOf could have already being added during subtype resolving
- if (composedSchema.getAllOf() == null || !composedSchema.getAllOf().contains(refSchema)) {
- composedSchema.addAllOfItem(refSchema);
+ if (schemaWithCompositionKeys.getAllOf() == null || !schemaWithCompositionKeys.getAllOf().contains(refSchema)) {
+ schemaWithCompositionKeys.addAllOfItem(refSchema);
}
// remove shared properties defined in the parent
if (isSubtype(beanDesc.getClassInfo(), c)) {
- removeParentProperties(composedSchema, allOfRef);
+ removeParentProperties(schemaWithCompositionKeys, allOfRef);
}
});
@@ -1079,14 +1078,14 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
Schema anyOfRef = context.resolve(new AnnotatedType().components(annotatedType.getComponents()).type(c).jsonViewAnnotation(annotatedType.getJsonViewAnnotation()));
if (anyOfRef != null) {
if (StringUtils.isNotBlank(anyOfRef.getName())) {
- composedSchema.addAnyOfItem(new Schema().$ref(Components.COMPONENTS_SCHEMAS_REF + anyOfRef.getName()));
+ schemaWithCompositionKeys.addAnyOfItem(new Schema().$ref(Components.COMPONENTS_SCHEMAS_REF + anyOfRef.getName()));
} else {
- composedSchema.addAnyOfItem(anyOfRef);
+ schemaWithCompositionKeys.addAnyOfItem(anyOfRef);
}
}
// remove shared properties defined in the parent
if (isSubtype(beanDesc.getClassInfo(), c)) {
- removeParentProperties(composedSchema, anyOfRef);
+ removeParentProperties(schemaWithCompositionKeys, anyOfRef);
}
});
@@ -1100,25 +1099,26 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
Schema oneOfRef = context.resolve(new AnnotatedType().components(annotatedType.getComponents()).type(c).jsonViewAnnotation(annotatedType.getJsonViewAnnotation()));
if (oneOfRef != null) {
if (StringUtils.isBlank(oneOfRef.getName())) {
- composedSchema.addOneOfItem(oneOfRef);
+ schemaWithCompositionKeys.addOneOfItem(oneOfRef);
} else {
- composedSchema.addOneOfItem(new Schema().$ref(Components.COMPONENTS_SCHEMAS_REF + oneOfRef.getName()));
+ schemaWithCompositionKeys.addOneOfItem(new Schema().$ref(Components.COMPONENTS_SCHEMAS_REF + oneOfRef.getName()));
}
// remove shared properties defined in the parent
if (isSubtype(beanDesc.getClassInfo(), c)) {
- removeParentProperties(composedSchema, oneOfRef);
+ removeParentProperties(schemaWithCompositionKeys, oneOfRef);
}
}
+ dropRootRefIfComposed(schemaWithCompositionKeys);
});
if (!composedModelPropertiesAsSibling) {
- if (composedSchema.getAllOf() != null && !composedSchema.getAllOf().isEmpty()) {
- if (composedSchema.getProperties() != null && !composedSchema.getProperties().isEmpty()) {
+ if (schemaWithCompositionKeys.getAllOf() != null && !schemaWithCompositionKeys.getAllOf().isEmpty()) {
+ if (schemaWithCompositionKeys.getProperties() != null && !schemaWithCompositionKeys.getProperties().isEmpty()) {
Schema propSchema = openapi31 ? new JsonSchema().typesItem("object") : new ObjectSchema();
- propSchema.properties(composedSchema.getProperties());
- composedSchema.setProperties(null);
- composedSchema.addAllOfItem(propSchema);
+ propSchema.properties(schemaWithCompositionKeys.getProperties());
+ schemaWithCompositionKeys.setProperties(null);
+ schemaWithCompositionKeys.addAllOfItem(propSchema);
}
}
}
@@ -1139,7 +1139,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
Schema.SchemaResolution resolvedSchemaResolution = AnnotationsUtils.resolveSchemaResolution(this.schemaResolution, resolvedSchemaAnnotation);
if (model != null && annotatedType.isResolveAsRef() &&
- (isComposedSchema || isObjectSchema(model) || implicitObject) &&
+ (hasCompositionKeywords || isObjectSchema(model) || implicitObject) &&
StringUtils.isNotBlank(model.getName())) {
if (context.getDefinedModels().containsKey(model.getName())) {
if (!Schema.SchemaResolution.INLINE.equals(resolvedSchemaResolution)) {
@@ -1209,6 +1209,38 @@ private Stream getRecordComponentAnnotations(BeanPropertyDefinition
}
}
+ private void dropRootRefIfComposed(Schema> s) {
+ if (s == null || s.get$ref() == null) {
+ return;
+ }
+
+ if (!isComposedSchema(s)) {
+ return;
+ }
+
+ String ref = s.get$ref();
+ if (refMatchesAnyComposedItem(s, ref)) {
+ s.set$ref(null);
+ }
+ }
+
+ private boolean isComposedSchema(Schema> s) {
+ return (s.getOneOf() != null && !s.getOneOf().isEmpty())
+ || (s.getAnyOf() != null && !s.getAnyOf().isEmpty())
+ || (s.getAllOf() != null && !s.getAllOf().isEmpty());
+ }
+
+ private boolean refMatchesAnyComposedItem(Schema> s, String ref) {
+ return refMatchesInList(s.getOneOf(), ref)
+ || refMatchesInList(s.getAllOf(), ref)
+ || refMatchesInList(s.getAnyOf(), ref);
+ }
+
+ private boolean refMatchesInList(List schemas, String ref) {
+ return schemas != null && schemas.stream()
+ .anyMatch(schema -> ref.equals(schema.get$ref()));
+ }
+
private Boolean isRecordType(BeanPropertyDefinition propDef) {
try {
if (propDef.getPrimaryMember() != null) {
@@ -1239,8 +1271,8 @@ private Stream getGenericTypeArgumentAnnotations(java.lang.reflect.A
.orElseGet(Stream::of);
}
- private boolean shouldResolveEnumAsRef(io.swagger.v3.oas.annotations.media.Schema resolvedSchemaAnnotation) {
- return (resolvedSchemaAnnotation != null && resolvedSchemaAnnotation.enumAsRef()) || ModelResolver.enumsAsRef;
+ private boolean shouldResolveEnumAsRef(io.swagger.v3.oas.annotations.media.Schema resolvedSchemaAnnotation, boolean isResolveEnumAsRef) {
+ return (resolvedSchemaAnnotation != null && resolvedSchemaAnnotation.enumAsRef()) || ModelResolver.enumsAsRef || isResolveEnumAsRef;
}
protected Type findJsonValueType(final BeanDescription beanDesc) {
@@ -2093,43 +2125,7 @@ private boolean resolveSubtypes(Schema model, BeanDescription bean, ModelConvert
ComposedSchema composedSchema = null;
if (!(subtypeModel instanceof ComposedSchema)) {
// create composed schema
- // TODO #2312 - smarter way needs clone implemented in #2227
- composedSchema = (ComposedSchema) new ComposedSchema()
- .title(subtypeModel.getTitle())
- .name(subtypeModel.getName())
- .deprecated(subtypeModel.getDeprecated())
- .additionalProperties(subtypeModel.getAdditionalProperties())
- .description(subtypeModel.getDescription())
- .discriminator(subtypeModel.getDiscriminator())
- .exclusiveMaximum(subtypeModel.getExclusiveMaximum())
- .exclusiveMinimum(subtypeModel.getExclusiveMinimum())
- .externalDocs(subtypeModel.getExternalDocs())
- .format(subtypeModel.getFormat())
- .maximum(subtypeModel.getMaximum())
- .maxItems(subtypeModel.getMaxItems())
- .maxLength(subtypeModel.getMaxLength())
- .maxProperties(subtypeModel.getMaxProperties())
- .minimum(subtypeModel.getMinimum())
- .minItems(subtypeModel.getMinItems())
- .minLength(subtypeModel.getMinLength())
- .minProperties(subtypeModel.getMinProperties())
- .multipleOf(subtypeModel.getMultipleOf())
- .not(subtypeModel.getNot())
- .nullable(subtypeModel.getNullable())
- .pattern(subtypeModel.getPattern())
- .properties(subtypeModel.getProperties())
- .readOnly(subtypeModel.getReadOnly())
- .required(subtypeModel.getRequired())
- .type(subtypeModel.getType())
- .uniqueItems(subtypeModel.getUniqueItems())
- .writeOnly(subtypeModel.getWriteOnly())
- .xml(subtypeModel.getXml())
- .extensions(subtypeModel.getExtensions());
-
- if (subtypeModel.getExample() != null || subtypeModel.getExampleSetFlag()) {
- composedSchema.example(subtypeModel.getExample());
- }
- composedSchema.setEnum(subtypeModel.getEnum());
+ composedSchema = ComposedSchema.from(subtypeModel);
} else {
composedSchema = (ComposedSchema) subtypeModel;
}
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 761bc1b7f7..e331732722 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
@@ -548,12 +548,21 @@ public static Optional getArraySchema(io.swagger.v3.oas.annotations.medi
if (arraySchema.schema() != null) {
if (arraySchema.schema().implementation().equals(Void.class)) {
- getSchemaFromAnnotation(arraySchema.schema(), components, jsonViewAnnotation, openapi31).ifPresent(arraySchemaObject::setItems);
+ getSchemaFromAnnotation(arraySchema.schema(), components, jsonViewAnnotation, openapi31, arraySchemaObject.getItems()).ifPresent(arraySchemaObject::setItems);
} else if (processSchemaImplementation) {
getSchema(arraySchema.schema(), arraySchema, false, arraySchema.schema().implementation(), components, jsonViewAnnotation, openapi31).ifPresent(arraySchemaObject::setItems);
}
}
+ if (arraySchema.arraySchema() != null) {
+ if (StringUtils.isNotBlank(arraySchema.arraySchema().description())) {
+ arraySchemaObject.setDescription(arraySchema.arraySchema().description());
+ }
+ if (StringUtils.isNotBlank(arraySchema.arraySchema().title())) {
+ arraySchemaObject.setTitle(arraySchema.arraySchema().title());
+ }
+ }
+
return Optional.of(arraySchemaObject);
}
@@ -2907,4 +2916,28 @@ public static Schema.SchemaResolution resolveSchemaResolution(Schema.SchemaResol
}
return globalSchemaResolution;
}
+
+ public static boolean computeEnumAsRef(io.swagger.v3.oas.annotations.media.Schema ctxSchema, io.swagger.v3.oas.annotations.media.ArraySchema ctxArraySchema) {
+ if (ctxSchema != null && ctxSchema.enumAsRef()) {
+ return ctxSchema.enumAsRef();
+ } else if(ctxArraySchema != null && ctxArraySchema.schema() != null && ctxArraySchema.schema().enumAsRef()) {
+ return ctxArraySchema.schema().enumAsRef();
+ }
+ return false;
+ }
+
+ public static boolean areSiblingsAllowed(Schema.SchemaResolution resolvedSchemaResolution, boolean openapi31) {
+ return Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) || Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) || openapi31;
+ }
+
+ public static AnnotatedType addTypeWhenSiblingsAllowed(AnnotatedType aType, io.swagger.v3.oas.annotations.media.Schema ctxSchema, boolean areSiblingsAllowed) {
+ if (areSiblingsAllowed && ctxSchema != null) {
+ if (!Void.class.equals(ctxSchema.implementation())) {
+ aType.setType(ctxSchema.implementation());
+ } else if (StringUtils.isNotBlank(ctxSchema.type())) {
+ aType.setType(ctxSchema.type().getClass());
+ }
+ }
+ return aType;
+ }
}
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 899c7476ed..055d31a4eb 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
@@ -3,6 +3,8 @@
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class Json {
@@ -10,6 +12,8 @@ private static final class ObjectMapperHolder {
private static final ObjectMapper MAPPER = ObjectMapperFactory.createJson();
}
+ private static final Logger LOGGER = LoggerFactory.getLogger(Json.class);
+
public static ObjectMapper mapper() {
return ObjectMapperHolder.MAPPER;
}
@@ -22,16 +26,17 @@ public static String pretty(Object o) {
try {
return pretty().writeValueAsString(o);
} catch (Exception e) {
- e.printStackTrace();
+ PrettyPrintHelper.emitError(LOGGER, "Error serializing object to JSON", e);
return null;
}
}
public static void prettyPrint(Object o) {
try {
- System.out.println(pretty().writeValueAsString(o).replace("\r", ""));
+ String prettyString = pretty().writeValueAsString(o).replace("\r", "");
+ PrettyPrintHelper.emit(LOGGER, prettyString);
} catch (Exception e) {
- e.printStackTrace();
+ PrettyPrintHelper.emitError(LOGGER, "Error pretty-printing JSON", e);
}
}
}
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 0ac52783a8..64b7600b89 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
@@ -21,7 +21,7 @@ private static final class ConverterMapperHolder {
private static final ObjectMapper MAPPER = ObjectMapperFactory.createJsonConverter();
}
- static Logger LOGGER = LoggerFactory.getLogger(Json31.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(Json31.class);
public static ObjectMapper mapper() {
return ObjectMapperHolder.MAPPER;
@@ -39,16 +39,17 @@ public static String pretty(Object o) {
try {
return pretty().writeValueAsString(o);
} catch (Exception e) {
- e.printStackTrace();
+ PrettyPrintHelper.emitError(LOGGER, "Error serializing object to JSON (3.1)", e);
return null;
}
}
public static void prettyPrint(Object o) {
try {
- System.out.println(pretty().writeValueAsString(o).replace("\r", ""));
+ String prettyString = pretty().writeValueAsString(o).replace("\r", "");
+ PrettyPrintHelper.emit(LOGGER, prettyString);
} catch (Exception e) {
- e.printStackTrace();
+ PrettyPrintHelper.emitError(LOGGER, "Error pretty-printing JSON (3.1)", e);
}
}
diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/PrettyPrintHelper.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/PrettyPrintHelper.java
new file mode 100644
index 0000000000..a469b1ebcd
--- /dev/null
+++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/PrettyPrintHelper.java
@@ -0,0 +1,48 @@
+package io.swagger.v3.core.util;
+
+import org.slf4j.Logger;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.function.Consumer;
+
+class PrettyPrintHelper {
+
+ private static final ThreadLocal> OVERRIDE = new ThreadLocal<>();
+
+ private PrettyPrintHelper() {
+ // utility class
+ }
+
+ static void setOverride(Consumer consumer) {
+ OVERRIDE.set(consumer);
+ }
+
+ static void clearOverride() {
+ OVERRIDE.remove();
+ }
+
+ static void emit(Logger logger, String message) {
+ Consumer consumer = OVERRIDE.get();
+ if (consumer != null) {
+ consumer.accept(message);
+ } else {
+ logger.debug(message);
+ }
+ }
+
+ static void emitError(Logger logger, String message, Throwable throwable) {
+ Consumer consumer = OVERRIDE.get();
+ if (consumer != null) {
+ StringBuilder builder = new StringBuilder(message);
+ if (throwable != null) {
+ builder.append(System.lineSeparator());
+ StringWriter writer = new StringWriter();
+ throwable.printStackTrace(new PrintWriter(writer));
+ builder.append(writer);
+ }
+ consumer.accept(builder.toString());
+ }
+ logger.error(message, throwable);
+ }
+}
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 2a77b70305..8a7504e1b8 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
@@ -3,6 +3,8 @@
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class Yaml {
@@ -10,6 +12,8 @@ private static final class ObjectMapperHolder {
private static final ObjectMapper MAPPER = ObjectMapperFactory.createYaml();
}
+ private static final Logger LOGGER = LoggerFactory.getLogger(Yaml.class);
+
public static ObjectMapper mapper() {
return ObjectMapperHolder.MAPPER;
}
@@ -22,16 +26,17 @@ public static String pretty(Object o) {
try {
return pretty().writeValueAsString(o);
} catch (Exception e) {
- e.printStackTrace();
+ PrettyPrintHelper.emitError(LOGGER, "Error serializing object to YAML", e);
return null;
}
}
public static void prettyPrint(Object o) {
try {
- System.out.println(pretty().writeValueAsString(o));
+ String prettyString = pretty().writeValueAsString(o);
+ PrettyPrintHelper.emit(LOGGER, prettyString);
} catch (Exception e) {
- e.printStackTrace();
+ PrettyPrintHelper.emitError(LOGGER, "Error pretty-printing YAML", e);
}
}
}
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 75d9819a84..4189d4f8d9 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
@@ -17,7 +17,7 @@ private static final class ObjectMapperHolder {
}
- static Logger LOGGER = LoggerFactory.getLogger(Yaml31.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(Yaml31.class);
public static ObjectMapper mapper() {
return ObjectMapperHolder.MAPPER;
@@ -31,16 +31,17 @@ public static String pretty(Object o) {
try {
return pretty().writeValueAsString(o);
} catch (Exception e) {
- e.printStackTrace();
+ PrettyPrintHelper.emitError(LOGGER, "Error serializing object to YAML (3.1)", e);
return null;
}
}
public static void prettyPrint(Object o) {
try {
- System.out.println(pretty().writeValueAsString(o));
+ String prettyString = pretty().writeValueAsString(o);
+ PrettyPrintHelper.emit(LOGGER, prettyString);
} catch (Exception e) {
- e.printStackTrace();
+ PrettyPrintHelper.emitError(LOGGER, "Error pretty-printing YAML (3.1)", e);
}
}
@@ -60,4 +61,5 @@ public static Map jsonSchemaAsMap(Schema schema) {
LOGGER.error("Exception converting jsonSchema to Map", e);
return null;
}
- }}
+ }
+}
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/AnnotatedTypeCachingTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/AnnotatedTypeCachingTest.java
new file mode 100644
index 0000000000..c79340ca80
--- /dev/null
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/AnnotatedTypeCachingTest.java
@@ -0,0 +1,72 @@
+package io.swagger.v3.core.converting;
+
+import io.swagger.v3.core.converter.AnnotatedType;
+import io.swagger.v3.core.converter.ModelConverter;
+import io.swagger.v3.core.converter.ModelConverterContext;
+import io.swagger.v3.core.converter.ModelConverterContextImpl;
+import io.swagger.v3.oas.models.media.Schema;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Field;
+import java.util.Iterator;
+import java.util.Set;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+public class AnnotatedTypeCachingTest {
+
+ @Test
+ public void testAnnotatedTypeEqualityIgnoresContextualFields() {
+ AnnotatedType type1 = new AnnotatedType(String.class)
+ .propertyName("userStatus");
+ AnnotatedType type2 = new AnnotatedType(String.class)
+ .propertyName("city");
+ assertEquals(type1, type2, "AnnotatedType objects with different contextual fields (e.g., propertyName) should be equal.");
+ assertEquals(type1.hashCode(), type2.hashCode(), "The hash codes of equal AnnotatedType objects must be the same.");
+ }
+
+ static class User {
+ public String username;
+ public String email;
+ public Address address;
+ }
+
+ static class Address {
+ public String street;
+ public String city;
+ }
+
+ private static class DummyModelConverter implements ModelConverter {
+ @Override
+ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator chain) {
+ if (type.getType().equals(User.class)) {
+ context.resolve(new AnnotatedType(String.class).propertyName("username"));
+ context.resolve(new AnnotatedType(String.class).propertyName("email"));
+ context.resolve(new AnnotatedType(Address.class).propertyName("address"));
+ return new Schema();
+ }
+ if (type.getType().equals(Address.class)) {
+ context.resolve(new AnnotatedType(String.class).propertyName("street"));
+ context.resolve(new AnnotatedType(String.class).propertyName("city"));
+ return new Schema();
+ }
+ return new Schema();
+ }
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testCacheHitsForRepeatedStringTypeWithCorrectedEquals() throws Exception {
+ ModelConverterContextImpl context = new ModelConverterContextImpl(new DummyModelConverter());
+ Schema userSchema = context.resolve(new AnnotatedType(User.class));
+ assertNotNull(userSchema);
+ Field processedTypesField = ModelConverterContextImpl.class.getDeclaredField("processedTypes");
+ processedTypesField.setAccessible(true);
+ Set processedTypes = (Set) processedTypesField.get(context);
+ long stringTypeCount = processedTypes.stream()
+ .filter(annotatedType -> annotatedType.getType().equals(String.class))
+ .count();
+ assertEquals(stringTypeCount, 1, "With the correct equals/hashCode, String type should be added to the cache only once.");
+ }
+}
\ No newline at end of file
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/AnnotatedTypeTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/AnnotatedTypeTest.java
new file mode 100644
index 0000000000..855890273c
--- /dev/null
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/AnnotatedTypeTest.java
@@ -0,0 +1,105 @@
+package io.swagger.v3.core.converting;
+
+import io.swagger.v3.core.converter.AnnotatedType;
+import org.testng.annotations.Test;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Type;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertTrue;
+
+public class AnnotatedTypeTest {
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ @interface TestAnnA {}
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ @interface TestAnnB {}
+
+ @TestAnnA
+ @TestAnnB
+ @Deprecated
+ private static class AnnotationHolder {}
+
+ private Annotation getAnnotationInstance(Class extends Annotation> clazz) {
+ return AnnotationHolder.class.getAnnotation(clazz);
+ }
+
+ /**
+ * Tests that equals() and hashCode() are order-insensitive for context annotations.
+ */
+ @Test
+ public void testEqualsAndHashCode_shouldBeOrderInsensitiveForAnnotations() {
+ Annotation annA = getAnnotationInstance(TestAnnA.class);
+ Annotation annB = getAnnotationInstance(TestAnnB.class);
+ AnnotatedType type1 = new AnnotatedType(String.class).ctxAnnotations(new Annotation[]{annA, annB});
+ AnnotatedType type2 = new AnnotatedType(String.class).ctxAnnotations(new Annotation[]{annB, annA});
+ assertEquals(type1, type2, "Objects should be equal even if annotation order is different.");
+ assertEquals(type1.hashCode(), type2.hashCode(), "Hash codes should be equal even if annotation order is different.");
+ }
+
+ /**
+ * Tests that JDK/internal annotations are filtered out for equals() and hashCode() comparison.
+ */
+ @Test
+ public void testEqualsAndHashCode_shouldIgnoreJdkInternalAnnotations() {
+ Annotation annA = getAnnotationInstance(TestAnnA.class);
+ Annotation deprecated = getAnnotationInstance(Deprecated.class);
+ AnnotatedType typeWithUserAnn = new AnnotatedType(String.class).ctxAnnotations(new Annotation[]{annA});
+ AnnotatedType typeWithJdkAnn = new AnnotatedType(String.class).ctxAnnotations(new Annotation[]{annA, deprecated});
+ AnnotatedType typeWithOnlyJdkAnn = new AnnotatedType(String.class).ctxAnnotations(new Annotation[]{deprecated});
+ AnnotatedType typeWithNoAnn = new AnnotatedType(String.class);
+ assertEquals(typeWithUserAnn, typeWithJdkAnn, "JDK annotations should be ignored in equality comparison.");
+ assertEquals(typeWithUserAnn.hashCode(), typeWithJdkAnn.hashCode(), "JDK annotations should be ignored in hashCode calculation.");
+ assertEquals(typeWithOnlyJdkAnn, typeWithNoAnn, "An object with only JDK annotations should be equal to one with no annotations.");
+ assertEquals(typeWithOnlyJdkAnn.hashCode(), typeWithNoAnn.hashCode(), "The hash code of an object with only JDK annotations should be the same as one with no annotations.");
+ }
+
+ /**
+ * Tests that defensive copying prevents Set corruption from external array mutation.
+ */
+ @Test
+ public void testImmutability_shouldPreventCorruptionInHashSet() {
+ Annotation annA = getAnnotationInstance(TestAnnA.class);
+ Annotation annB = getAnnotationInstance(TestAnnB.class);
+ Annotation[] originalAnnotations = new Annotation[]{annA};
+ AnnotatedType type = new AnnotatedType(String.class).ctxAnnotations(originalAnnotations);
+ Set typeSet = new HashSet<>();
+ typeSet.add(type);
+ int initialHashCode = type.hashCode();
+ originalAnnotations[0] = annB;
+ assertEquals(initialHashCode, type.hashCode(), "Hash code must remain the same after mutating the external array.");
+ assertTrue(typeSet.contains(type), "The Set must still contain the object after mutating the external array.");
+ }
+
+ /**
+ * Tests that an instance of a subclass can be equal to an instance of the parent class.
+ */
+ @Test
+ public void testEqualsAndHashCode_shouldAllowSubclassEquality() {
+ class SubAnnotatedType extends AnnotatedType {
+ public SubAnnotatedType(Type type) { super(type); }
+ }
+ Annotation annA = getAnnotationInstance(TestAnnA.class);
+ Annotation[] annotations = {annA};
+ AnnotatedType parent = new AnnotatedType(Integer.class).ctxAnnotations(annotations).name("number");
+ SubAnnotatedType child = new SubAnnotatedType(Integer.class);
+ child.ctxAnnotations(annotations);
+ child.name("number");
+ AnnotatedType differentParent = new AnnotatedType(Long.class).name("number");
+ assertEquals(parent, child, "Parent and child objects should be equal if their properties are the same.");
+ assertEquals(child, parent, "Equality comparison should be symmetric.");
+ assertEquals(parent.hashCode(), child.hashCode(), "Parent and child hash codes should be equal if their properties are the same.");
+ assertNotEquals(parent, differentParent, "Objects with different properties should not be equal.");
+ }
+}
\ No newline at end of file
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/ModelPropertyTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/ModelPropertyTest.java
index 4a749918ef..f55cbcf36d 100644
--- a/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/ModelPropertyTest.java
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/ModelPropertyTest.java
@@ -103,6 +103,9 @@ public void testReadOnlyProperty() {
public void testRequiredProperty() {
final Map models = ModelConverters.getInstance().readAll(RequiredFields.class);
Schema model = models.get("RequiredFields");
+ assertFalse(model.getRequired().contains("optionalField"));
+ assertFalse(model.getRequired().contains("primitiveTypeWithoutConstraint"));
+ assertTrue(model.getRequired().contains("primitiveTypeWithConstraint"));
assertTrue(model.getRequired().contains("required"));
assertFalse(model.getRequired().contains("notRequired"));
assertTrue(model.getRequired().contains("notRequiredWithAnnotation"));
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 9ec9e786d1..7d161876d4 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
@@ -27,6 +27,8 @@
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.tags.Tag;
import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.testng.annotations.Test;
import java.io.IOException;
@@ -44,6 +46,7 @@
import static org.testng.Assert.fail;
public class SpecFilterTest {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SpecFilterTest.class);
private static final String RESOURCE_RECURSIVE_MODELS = "specFiles/recursivemodels.json";
private static final String RESOURCE_PATH = "specFiles/petstore-3.0-v2.json";
@@ -189,7 +192,7 @@ public void run() {
try {
filteredMap.put("filtered " + id, new SpecFilter().filter(openAPI, new NoOpOperationsFilter(), null, null, null));
} catch (Exception e) {
- e.printStackTrace();
+ LOGGER.error("Failed to filter OpenAPI concurrently", e);
}
}
}.start();
@@ -220,7 +223,7 @@ public void run() {
}
}
} catch (Exception e) {
- e.printStackTrace();
+ LOGGER.error("Interrupted while waiting for filtering threads to complete", e);
}
for (OpenAPI filtered : filteredMap.values()) {
assertEquals(Json.pretty(openAPI), Json.pretty(filtered));
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/oas/models/RequiredFields.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/oas/models/RequiredFields.java
index 9a1cf9f8b5..1257fd74be 100644
--- a/modules/swagger-core/src/test/java/io/swagger/v3/core/oas/models/RequiredFields.java
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/oas/models/RequiredFields.java
@@ -3,6 +3,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.NotNull;
+import java.util.Optional;
public class RequiredFields {
@Schema(description = "required", required = true)
@@ -11,10 +12,20 @@ public class RequiredFields {
@Schema(description = "not required")
public Long notRequired;
+ @Schema(description = "Optional field")
+ public Optional optionalField;
+
@Schema(description = "not required with annotation")
@NotNull
public Long notRequiredWithAnnotation;
+ @Schema(description = "primitive type without constraint")
+ public long primitiveTypeWithoutConstraint;
+
+ @Schema(description = "primitive type with constraint")
+ @NotNull
+ public long primitiveTypeWithConstraint;
+
@Schema(description = "mode auto", requiredMode = Schema.RequiredMode.AUTO)
public Long modeAuto;
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/CompositionSuperfluousRefTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/CompositionSuperfluousRefTest.java
new file mode 100644
index 0000000000..4682db0356
--- /dev/null
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/CompositionSuperfluousRefTest.java
@@ -0,0 +1,143 @@
+package io.swagger.v3.core.resolving;
+
+import io.swagger.v3.core.converter.AnnotatedType;
+import io.swagger.v3.core.converter.ModelConverters;
+import io.swagger.v3.core.converter.ResolvedSchema;
+import io.swagger.v3.oas.models.media.Schema;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class CompositionSuperfluousRefTest {
+
+ static class SomeDto {}
+ static class OtherDto {}
+
+ static class MyDtoOneOf {
+ @io.swagger.v3.oas.annotations.media.Schema(oneOf = { SomeDto.class, OtherDto.class })
+ public Object myProperty;
+ }
+
+ static class MyDtoWithAnyOf {
+ @io.swagger.v3.oas.annotations.media.Schema(anyOf = { SomeDto.class, OtherDto.class })
+ public Object myProperty;
+ }
+
+ static class MyDtoWithAllOf {
+ @io.swagger.v3.oas.annotations.media.Schema(allOf = { SomeDto.class, OtherDto.class })
+ public Object myProperty;
+ }
+
+ static class MyDtoWithoutComposition {
+ @io.swagger.v3.oas.annotations.media.Schema(implementation = SomeDto.class)
+ public Object myProperty;
+ }
+
+ static class MyDtoWithNonMatchingRef {
+ @io.swagger.v3.oas.annotations.media.Schema(
+ ref = "#/components/schemas/ThirdDto",
+ oneOf = { SomeDto.class, OtherDto.class }
+ )
+ public Object myProperty;
+ }
+
+ @Test
+ public void oneOf_shouldNotHaveRef() {
+ ResolvedSchema rs = ModelConverters.getInstance(false)
+ .resolveAsResolvedSchema(new AnnotatedType(MyDtoOneOf.class));
+
+ Schema> prop = (Schema>) rs.schema.getProperties().get("myProperty");
+
+ Assert.assertNull(prop.get$ref());
+ Assert.assertNotNull(prop.getOneOf());
+ Assert.assertEquals(prop.getOneOf().size(), 2);
+ }
+
+ @Test
+ public void anyOf_shouldNotHaveRef() {
+ ResolvedSchema rs = ModelConverters.getInstance(false)
+ .resolveAsResolvedSchema(new AnnotatedType(MyDtoWithAnyOf.class));
+
+ Schema> prop = (Schema>) rs.schema.getProperties().get("myProperty");
+
+ Assert.assertNull(prop.get$ref());
+ Assert.assertNotNull(prop.getAnyOf());
+ Assert.assertEquals(prop.getAnyOf().size(), 2);
+ }
+
+ @Test
+ public void allOf_shouldNotHaveRef() {
+
+ ResolvedSchema rs = ModelConverters.getInstance(false)
+ .resolveAsResolvedSchema(new AnnotatedType(MyDtoWithAllOf.class));
+
+ Schema> prop = (Schema>) rs.schema.getProperties().get("myProperty");
+ Assert.assertNull(prop.get$ref());
+ Assert.assertNotNull(prop.getAllOf());
+ Assert.assertEquals(prop.getAllOf().size(), 2);
+ }
+
+ @Test
+ public void testNonMatchingRef_shouldPreserveRef() {
+ ResolvedSchema rs = ModelConverters.getInstance(false)
+ .resolveAsResolvedSchema(new AnnotatedType(MyDtoWithNonMatchingRef.class));
+
+ Schema> prop = (Schema>) rs.schema.getProperties().get("myProperty");
+
+ Assert.assertNotNull(prop.get$ref());
+ Assert.assertEquals(prop.get$ref(), "#/components/schemas/ThirdDto");
+ //In 3.x refs cannot have siblings
+ Assert.assertNull(prop.getOneOf());
+ }
+
+ @Test
+ public void oneOf_shouldNotHaveRef31() {
+ ResolvedSchema rs = ModelConverters.getInstance(true)
+ .resolveAsResolvedSchema(new AnnotatedType(MyDtoOneOf.class));
+
+ Schema> prop = (Schema>) rs.schema.getProperties().get("myProperty");
+
+ Assert.assertNull(prop.get$ref());
+ Assert.assertNotNull(prop.getOneOf());
+ Assert.assertEquals(prop.getOneOf().size(), 2);
+ }
+
+ @Test
+ public void anyOf_shouldNotHaveRef31() {
+ ResolvedSchema rs = ModelConverters.getInstance(true)
+ .resolveAsResolvedSchema(new AnnotatedType(MyDtoWithAnyOf.class));
+
+ Schema> prop = (Schema>) rs.schema.getProperties().get("myProperty");
+
+ Assert.assertNull(prop.get$ref());
+ Assert.assertNotNull(prop.getAnyOf());
+ Assert.assertEquals(prop.getAnyOf().size(), 2);
+ }
+
+ @Test
+ public void allOf_shouldNotHaveRef31() {
+
+ ResolvedSchema rs = ModelConverters.getInstance(true)
+ .resolveAsResolvedSchema(new AnnotatedType(MyDtoWithAllOf.class));
+
+ Schema> prop = (Schema>) rs.schema.getProperties().get("myProperty");
+ Assert.assertNull(prop.get$ref());
+ Assert.assertNotNull(prop.getAllOf());
+ Assert.assertEquals(prop.getAllOf().size(), 2);
+ }
+
+ @Test
+ public void testNonMatchingRef_shouldPreserveRef31() {
+ ResolvedSchema rs = ModelConverters.getInstance(true)
+ .resolveAsResolvedSchema(new AnnotatedType(MyDtoWithNonMatchingRef.class));
+
+ Schema> prop = (Schema>) rs.schema.getProperties().get("myProperty");
+
+ Assert.assertNotNull(prop.get$ref());
+ Assert.assertEquals(prop.get$ref(), "#/components/schemas/ThirdDto");
+ Assert.assertNotNull(prop.getOneOf());
+ Assert.assertEquals(prop.getOneOf().size(), 2);
+ }
+
+}
+
+
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/EnumTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/EnumTest.java
index 72dd08ee12..6f2f552353 100644
--- a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/EnumTest.java
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/EnumTest.java
@@ -9,13 +9,11 @@
import io.swagger.v3.oas.models.media.StringSchema;
import org.testng.annotations.Test;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
+import java.util.*;
+import java.util.stream.Collectors;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.*;
+import static org.testng.AssertJUnit.assertFalse;
public class EnumTest extends SwaggerTestBase {
@@ -33,7 +31,6 @@ public void testEnum() {
new ArrayList(Collections2.transform(Arrays.asList(Currency.values()), Functions.toStringFunction()));
assertEquals(strModel.getEnum(), modelValues);
-
final Schema property = context.resolve(new AnnotatedType().type(Currency.class).schemaProperty(true));
assertNotNull(property);
assertTrue(property instanceof StringSchema);
@@ -50,10 +47,262 @@ public void testEnumGenerics() {
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
final Schema model = context.resolve((new AnnotatedType().type(Contract.class)));
+ assertBasicModelStructure(model, "Contract");
+ assertPropertyExists(model, "type");
+ }
+
+ @Test
+ public void testEnumPropertyWithSchemaAnnotation() {
+ final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(true);
+ final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
+
+ final Schema model = context.resolve(new AnnotatedType().type(ClassWithEnumAsRefProperty.class));
+ assertBasicModelStructure(model, "ClassWithEnumAsRefProperty");
+ assertPropertyExists(model, "enumWithSchemaProperty");
+
+ final Schema enumPropertySchema = (Schema) model.getProperties().get("enumWithSchemaProperty");
+ assertEnumAsRefProperty(enumPropertySchema, "#/components/schemas/EnumWithSchemaProperty");
+ assertEquals(enumPropertySchema.getDescription(), "Property description");
+ }
+
+ @Test
+ public void testEnumPropertyWithGlobalSwitchOnlyOpenApi31() {
+ ModelResolver.enumsAsRef = true;
+ final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(true);
+ final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
+
+ final Schema model = context.resolve(new AnnotatedType().type(ClassWithPlainEnum.class));
+ assertBasicModelStructure(model, "ClassWithPlainEnum");
+ assertPropertyExists(model, "plainEnum");
+
+ final Schema enumPropertySchema = (Schema) model.getProperties().get("plainEnum");
+ assertNotNull(enumPropertySchema.get$ref());
+ assertNull(enumPropertySchema.getEnum());
+ assertEquals(enumPropertySchema.getDescription(), "Plain enum property");
+
+ assertEnumComponentExists(context, ClassWithPlainEnum.PlainEnum.values(), null);
+
+ // Reset the static field
+ ModelResolver.enumsAsRef = false;
+ }
+
+ @Test
+ public void testArrayOfEnumWithSchemaAnnotationOpenApi31() {
+ final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(true);
+ final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
+
+ final Schema model = context.resolve(new AnnotatedType().type(ClassWithEnumArray.class));
+ assertBasicModelStructure(model, "ClassWithEnumArray");
+ assertPropertyExists(model, "enumArray");
+
+ final Schema arrayPropertySchema = (Schema) model.getProperties().get("enumArray");
+ assertArrayWithEnumRef(arrayPropertySchema);
+
+ assertEnumComponentExists(context, ClassWithEnumArray.ArrayEnum.values(), "Enum description");
+ }
+
+ @Test
+ public void testArrayOfEnumWithSchemaAnnotationOpenApi30() {
+ final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(false);
+ final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
+
+ final Schema model = context.resolve(new AnnotatedType().type(ClassWithEnumArray.class));
+ assertBasicModelStructure(model, "ClassWithEnumArray");
+ assertPropertyExists(model, "enumArray");
+
+ final Schema arrayPropertySchema = (Schema) model.getProperties().get("enumArray");
+ assertArrayWithEnumRef(arrayPropertySchema);
+
+ assertEnumComponentExists(context, ClassWithEnumArray.ArrayEnum.values(), "Enum description");
+ }
+
+ @Test
+ public void testControlTestNoRefOpenApi31() {
+ ModelResolver.enumsAsRef = false;
+ final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(true);
+ final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
+
+ final Schema model = context.resolve(new AnnotatedType().type(ClassWithPlainEnum.class));
+ assertBasicModelStructure(model, "ClassWithPlainEnum");
+ assertPropertyExists(model, "plainEnum");
+
+ final Schema enumPropertySchema = (Schema) model.getProperties().get("plainEnum");
+ assertInlineEnumProperty(enumPropertySchema);
+
+ // Apply broad assertions - verify no components are created for inline enums
+ Map components = context.getDefinedModels();
+ if (components != null && !components.isEmpty()) {
+ Set expected = Arrays.stream(ClassWithPlainEnum.PlainEnum.values())
+ .map(Enum::name)
+ .collect(Collectors.toSet());
+ Schema enumComponent = findEnumComponent(components, expected);
+ assertNull(enumComponent, "No enum component should exist for inline enums");
+ }
+ }
+
+ @Test
+ public void testControlTestNoRefOpenApi30() {
+ ModelResolver.enumsAsRef = false;
+ final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(false);
+ final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
+
+ final Schema model = context.resolve(new AnnotatedType().type(ClassWithPlainEnum.class));
+ assertBasicModelStructure(model, "ClassWithPlainEnum");
+ assertPropertyExists(model, "plainEnum");
+
+ final Schema enumPropertySchema = (Schema) model.getProperties().get("plainEnum");
+ assertInlineEnumProperty(enumPropertySchema);
+
+ // Apply broad assertions - verify no components are created for inline enums
+ Map components = context.getDefinedModels();
+ if (components != null && !components.isEmpty()) {
+ Set expected = Arrays.stream(ClassWithPlainEnum.PlainEnum.values())
+ .map(Enum::name)
+ .collect(Collectors.toSet());
+ Schema enumComponent = findEnumComponent(components, expected);
+ assertNull(enumComponent, "No enum component should exist for inline enums");
+ }
+ }
+
+ @Test
+ public void testEnumWithAllOfSchemaResolutionOpenApi30() {
+ final ModelResolver modelResolver = new ModelResolver(mapper())
+ .openapi31(false)
+ .schemaResolution(Schema.SchemaResolution.ALL_OF);
+ final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
+
+ final Schema model = context.resolve(new AnnotatedType().type(ClassWithEnumAsRefProperty.class));
+ assertBasicModelStructure(model, "ClassWithEnumAsRefProperty");
+ assertPropertyExists(model, "enumWithSchemaProperty");
+
+ final Schema enumPropertySchema = (Schema) model.getProperties().get("enumWithSchemaProperty");
+
+ boolean hasEnumRef = false;
+ for (Object allOfItem : enumPropertySchema.getAllOf()) {
+ if (allOfItem instanceof Schema) {
+ Schema allOfSchema = (Schema) allOfItem;
+ if ("#/components/schemas/EnumWithSchemaProperty".equals(allOfSchema.get$ref())) {
+ hasEnumRef = true;
+ break;
+ }
+ }
+ }
+ assertTrue(hasEnumRef, "AllOf should contain reference to enum component");
+ assertEnumComponentExists(context, ClassWithEnumAsRefProperty.EnumWithSchemaProperty.values(), "Enum description");
+ }
+
+ private void assertBasicModelStructure(Schema model, String expectedName) {
assertNotNull(model);
- assertEquals(model.getName(), "Contract");
- assertTrue(model.getProperties().containsKey("type"));
- assertNotNull(model.getProperties().get("type"));
+ assertEquals(model.getName(), expectedName);
+ }
+
+ private void assertPropertyExists(Schema model, String propertyName) {
+ assertTrue(model.getProperties().containsKey(propertyName));
+ assertNotNull(model.getProperties().get(propertyName));
+ }
+
+ private void assertEnumComponentExists(ModelConverterContextImpl context, Enum>[] enumValues, String expectedDescription) {
+ Map components = context.getDefinedModels();
+ assertNotNull(components);
+ assertFalse(components.isEmpty());
+
+ Set expected = Arrays.stream(enumValues)
+ .map(Enum::name)
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+
+ Schema enumComponent = findEnumComponent(components, expected);
+ assertNotNull(enumComponent);
+ assertEquals(enumComponent.getDescription(), expectedDescription);
+ }
+
+ private void assertEnumComponentExistsWithDefault(ModelConverterContextImpl context, Enum>[] enumValues, String expectedDescription, String expectedDefault) {
+ assertEnumComponentExists(context, enumValues, expectedDescription);
+
+ Map components = context.getDefinedModels();
+ Set expected = Arrays.stream(enumValues)
+ .map(Enum::name)
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+
+ Schema enumComponent = findEnumComponent(components, expected);
+ assertEquals(enumComponent.getDefault(), expectedDefault);
+ }
+
+ private Schema findEnumComponent(Map components, Set expectedValues) {
+ return components.values().stream()
+ .filter(Objects::nonNull)
+ .filter(s -> s.getEnum() != null)
+ .filter(s -> {
+ List> ev = s.getEnum();
+ Set vals = ev.stream().map(Object::toString).collect(Collectors.toSet());
+ return vals.containsAll(expectedValues) && expectedValues.containsAll(vals);
+ })
+ .findFirst()
+ .orElse(null);
+ }
+
+ private void assertEnumAsRefProperty(Schema propertySchema, String expectedRef) {
+ assertEquals(propertySchema.get$ref(), expectedRef);
+ assertNull(propertySchema.getEnum());
+ }
+
+ private void assertInlineEnumProperty(Schema propertySchema) {
+ assertNotNull(propertySchema.getEnum());
+ assertNull(propertySchema.get$ref());
+ }
+
+ private void assertArrayWithEnumRef(Schema arrayPropertySchema) {
+ assertNotNull(arrayPropertySchema.getItems());
+ assertNotNull(arrayPropertySchema.getItems().get$ref());
+ }
+
+
+ public static class ClassWithEnumAsRefProperty {
+
+ @io.swagger.v3.oas.annotations.media.Schema(enumAsRef = true, description = "Property description", maximum = "1923234")
+ public final EnumWithSchemaProperty enumWithSchemaProperty;
+
+ public ClassWithEnumAsRefProperty(EnumWithSchemaProperty enumWithSchemaProperty) {
+ this.enumWithSchemaProperty = enumWithSchemaProperty;
+ }
+
+ @io.swagger.v3.oas.annotations.media.Schema(description = "Enum description")
+ public enum EnumWithSchemaProperty {
+ VALUE1,
+ VALUE2
+ }
+ }
+
+ public static class ClassWithEnumArray {
+
+ @io.swagger.v3.oas.annotations.media.ArraySchema(schema = @io.swagger.v3.oas.annotations.media.Schema(enumAsRef = true, description = "Property Description"))
+ public final ArrayEnum[] enumArray;
+
+ public ClassWithEnumArray(ArrayEnum[] enumArray) {
+ this.enumArray = enumArray;
+ }
+
+ @io.swagger.v3.oas.annotations.media.Schema(description = "Enum description")
+ public enum ArrayEnum {
+ FIRST,
+ SECOND,
+ THIRD
+ }
+ }
+
+ public static class ClassWithPlainEnum {
+
+ @io.swagger.v3.oas.annotations.media.Schema(description = "Plain enum property")
+ public final PlainEnum plainEnum;
+
+ public ClassWithPlainEnum(PlainEnum plainEnum) {
+ this.plainEnum = plainEnum;
+ }
+
+ public enum PlainEnum {
+ ONE,
+ TWO,
+ THREE
+ }
}
public enum Currency {
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/SwaggerTestBase.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/SwaggerTestBase.java
index a27d724562..883ce03423 100644
--- a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/SwaggerTestBase.java
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/SwaggerTestBase.java
@@ -6,9 +6,12 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import io.swagger.v3.core.jackson.ModelResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public abstract class SwaggerTestBase {
static ObjectMapper mapper;
+ private static final Logger LOGGER = LoggerFactory.getLogger(SwaggerTestBase.class);
public static ObjectMapper mapper() {
if (mapper == null) {
@@ -26,9 +29,9 @@ protected ModelResolver modelResolver() {
protected void prettyPrint(Object o) {
try {
- System.out.println(mapper().writer(new DefaultPrettyPrinter()).writeValueAsString(o));
+ LOGGER.debug(mapper().writer(new DefaultPrettyPrinter()).writeValueAsString(o));
} catch (Exception e) {
- e.printStackTrace();
+ LOGGER.error("Failed to pretty print object", e);
}
}
}
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket4800Test.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket4800Test.java
new file mode 100644
index 0000000000..a2475a1318
--- /dev/null
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket4800Test.java
@@ -0,0 +1,67 @@
+package io.swagger.v3.core.resolving;
+
+import io.swagger.v3.core.converter.ModelConverters;
+import io.swagger.v3.core.matchers.SerializationMatchers;
+import io.swagger.v3.oas.models.media.Schema;
+import org.testng.annotations.Test;
+
+import java.util.Map;
+
+public class Ticket4800Test extends SwaggerTestBase{
+
+ @Test(description = "Custom schema implementation in property and enum as ref type value")
+ public void testCustomSchemaImplementation() {
+
+ String expectedYaml = "ModelWithCustomSchemaImplementationInProperty:\n" +
+ " type: object\n" +
+ " properties:\n" +
+ " enumExampleFieldWithImplementationProp:\n" +
+ " $ref: \"#/components/schemas/MyEnum\"\n" +
+ " default: \"yes\"\n" +
+ " description: Prop description\n" +
+ " secondExampleFieldWithTypeProp:\n" +
+ " type: string\n" +
+ "MyEnum:\n" +
+ " type: string\n" +
+ " enum:\n" +
+ " - \"yes\"\n" +
+ " - \"no\"";
+
+ Map stringSchemaMap = ModelConverters.getInstance(true).readAll(Ticket4800Test.ModelWithCustomSchemaImplementationInProperty.class);
+ SerializationMatchers.assertEqualsToYaml31(stringSchemaMap, expectedYaml);
+ }
+
+ static class ModelWithCustomSchemaImplementationInProperty {
+
+ @io.swagger.v3.oas.annotations.media.Schema(implementation = MyEnum.class, description = "Prop description", defaultValue = "yes", enumAsRef = true)
+ private MyEnum enumExampleFieldWithImplementationProp;
+
+ @io.swagger.v3.oas.annotations.media.Schema(type = "string", enumAsRef = true)
+ private MyEnum2 secondExampleFieldWithTypeProp;
+
+ public MyEnum getEnumExampleFieldWithImplementationProp() {
+ return enumExampleFieldWithImplementationProp;
+ }
+
+ public void setEnumExampleFieldWithImplementationProp(MyEnum enumExampleFieldWithImplementationProp) {
+ this.enumExampleFieldWithImplementationProp = enumExampleFieldWithImplementationProp;
+ }
+
+ public MyEnum2 getSecondExampleFieldWithTypeProp() {
+ return secondExampleFieldWithTypeProp;
+ }
+
+ public void setSecondExampleFieldWithTypeProp(MyEnum2 secondExampleFieldWithTypeProp) {
+ this.secondExampleFieldWithTypeProp = secondExampleFieldWithTypeProp;
+ }
+
+ enum MyEnum {
+ yes, no
+ }
+
+ //Should not be included in the model definitions
+ enum MyEnum2 {
+ si, no
+ }
+ }
+}
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket4904Test.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket4904Test.java
new file mode 100644
index 0000000000..3a61aeeef3
--- /dev/null
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket4904Test.java
@@ -0,0 +1,304 @@
+package io.swagger.v3.core.resolving;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+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.oas.annotations.media.DiscriminatorMapping;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.models.media.ComposedSchema;
+import io.swagger.v3.oas.models.media.JsonSchema;
+import org.testng.annotations.Test;
+
+import static io.swagger.v3.core.resolving.SwaggerTestBase.mapper;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+public class Ticket4904Test {
+
+ @Test
+ public void testComposedSchemaWithDiscriminator() {
+ final ModelResolver modelResolver = new ModelResolver(mapper());
+ modelResolver.setOpenapi31(true);
+ final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
+
+ io.swagger.v3.oas.models.media.Schema schema = context.resolve(new AnnotatedType(ParentClass.class));
+
+ assertNotNull(schema);
+ assertTrue(schema instanceof JsonSchema, "Expected JsonSchema in OpenAPI 3.1 mode, but got: " + schema.getClass());
+ JsonSchema jsonSchema = (JsonSchema) schema;
+ assertNotNull(jsonSchema.getOneOf());
+ assertEquals(jsonSchema.getOneOf().size(), 2);
+ assertEquals(( jsonSchema.getOneOf().get(0)).get$ref(), "#/components/schemas/ChildClassA");
+ assertEquals(( jsonSchema.getOneOf().get(1)).get$ref(), "#/components/schemas/ChildClassB");
+ assertNotNull(jsonSchema.getDiscriminator());
+ assertEquals(jsonSchema.getDiscriminator().getPropertyName(), "objectType");
+ assertNotNull(jsonSchema.getDiscriminator().getMapping());
+ assertEquals(jsonSchema.getDiscriminator().getMapping().size(), 2);
+ assertEquals(jsonSchema.getDiscriminator().getMapping().get("A"), "#/components/schemas/ChildClassA");
+ assertEquals(jsonSchema.getDiscriminator().getMapping().get("B"), "#/components/schemas/ChildClassB");
+ }
+
+ @Test
+ public void testMultiElementAllOf() {
+ final ModelResolver modelResolver = new ModelResolver(mapper());
+ modelResolver.setOpenapi31(true);
+ final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
+
+ io.swagger.v3.oas.models.media.Schema schema = context.resolve(new AnnotatedType(MultiAllOfClass.class));
+
+ assertNotNull(schema);
+ assertTrue(schema instanceof JsonSchema, "Expected JsonSchema in OpenAPI 3.1 mode, but got: " + schema.getClass());
+ assertNotNull(schema.getAllOf());
+ assertEquals(schema.getAllOf().size(), 3);
+ assertEquals(((io.swagger.v3.oas.models.media.Schema) schema.getAllOf().get(0)).get$ref(), "#/components/schemas/ClassA");
+ assertEquals(((io.swagger.v3.oas.models.media.Schema) schema.getAllOf().get(1)).get$ref(), "#/components/schemas/ClassB");
+ assertEquals(((io.swagger.v3.oas.models.media.Schema) schema.getAllOf().get(2)).get$ref(), "#/components/schemas/ClassC");
+ }
+
+ @Test
+ public void testMultiElementAllOfOpenApi30() {
+ final ModelResolver modelResolver = new ModelResolver(mapper());
+ modelResolver.setOpenapi31(false);
+ final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
+
+ io.swagger.v3.oas.models.media.Schema schema = context.resolve(new AnnotatedType(MultiAllOfClass.class));
+
+ assertNotNull(schema);
+ assertTrue(schema instanceof ComposedSchema, "Expected ComposedSchema in OpenAPI 3.0 mode, but got: " + schema.getClass());
+ ComposedSchema composedSchema = (ComposedSchema) schema;
+ assertNotNull(composedSchema.getAllOf());
+ assertEquals(composedSchema.getAllOf().size(), 3);
+ assertEquals(composedSchema.getAllOf().get(0).get$ref(), "#/components/schemas/ClassA");
+ assertEquals(composedSchema.getAllOf().get(1).get$ref(), "#/components/schemas/ClassB");
+ assertEquals(composedSchema.getAllOf().get(2).get$ref(), "#/components/schemas/ClassC");
+ }
+
+ @Test
+ public void testMixedComposition() {
+ final ModelResolver modelResolver = new ModelResolver(mapper());
+ modelResolver.setOpenapi31(true);
+ final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
+
+ io.swagger.v3.oas.models.media.Schema schema = context.resolve(new AnnotatedType(MixedCompositionClass.class));
+
+ assertNotNull(schema);
+ assertTrue(schema instanceof JsonSchema, "Expected JsonSchema in OpenAPI 3.1 mode, but got: " + schema.getClass());
+
+ JsonSchema jsonSchema = (JsonSchema) schema;
+ assertNotNull(jsonSchema.getAllOf());
+ assertEquals(jsonSchema.getAllOf().size(), 3);
+ assertEquals(jsonSchema.getAllOf().get(0).get$ref(), "#/components/schemas/ClassA");
+ assertEquals(jsonSchema.getAllOf().get(1).get$ref(), "#/components/schemas/ClassB");
+ io.swagger.v3.oas.models.media.Schema inlineSchema = jsonSchema.getAllOf().get(2);
+ assertNotNull(inlineSchema);
+ assertEquals(inlineSchema.get$ref(), null);
+ assertNotNull(inlineSchema.getProperties());
+ assertTrue(inlineSchema.getProperties().containsKey("additionalProperty"));
+ io.swagger.v3.oas.models.media.Schema additionalPropertySchema =
+ (io.swagger.v3.oas.models.media.Schema) inlineSchema.getProperties().get("additionalProperty");
+ assertNotNull(additionalPropertySchema);
+ assertEquals(additionalPropertySchema.getDescription(), "Additional property");
+ }
+
+ @Test
+ public void testAnyOfComposition() {
+ final ModelResolver modelResolver = new ModelResolver(mapper());
+ modelResolver.setOpenapi31(true);
+ final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
+
+ io.swagger.v3.oas.models.media.Schema schema = context.resolve(new AnnotatedType(AnyOfClass.class));
+
+ assertNotNull(schema);
+ assertTrue(schema instanceof JsonSchema, "Expected JsonSchema in OpenAPI 3.1 mode, but got: " + schema.getClass());
+ JsonSchema jsonSchema = (JsonSchema) schema;
+ assertNotNull(jsonSchema.getAnyOf());
+ assertEquals(jsonSchema.getAnyOf().size(), 3);
+ assertEquals(((io.swagger.v3.oas.models.media.Schema) jsonSchema.getAnyOf().get(0)).get$ref(), "#/components/schemas/ClassA");
+ assertEquals(((io.swagger.v3.oas.models.media.Schema) jsonSchema.getAnyOf().get(1)).get$ref(), "#/components/schemas/ClassB");
+ assertEquals(((io.swagger.v3.oas.models.media.Schema) jsonSchema.getAnyOf().get(2)).get$ref(), "#/components/schemas/ClassC");
+ }
+
+ @Test
+ public void testOneOfWithReferences() {
+ final ModelResolver modelResolver = new ModelResolver(mapper());
+ modelResolver.setOpenapi31(true);
+ final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
+
+ io.swagger.v3.oas.models.media.Schema schema = context.resolve(new AnnotatedType(OneOfWithRefsClass.class));
+
+ assertNotNull(schema);
+ assertTrue(schema instanceof JsonSchema, "Expected JsonSchema in OpenAPI 3.1 mode, but got: " + schema.getClass());
+ JsonSchema jsonSchema = (JsonSchema) schema;
+ assertNotNull(jsonSchema.getOneOf());
+ assertEquals(jsonSchema.getOneOf().size(), 2);
+ assertEquals((jsonSchema.getOneOf().get(0)).get$ref(), "#/components/schemas/RefSubtypeA");
+ assertEquals((jsonSchema.getOneOf().get(1)).get$ref(), "#/components/schemas/RefSubtypeB");
+ }
+
+ @Test
+ public void testJacksonPolymorphismWithTypeInfo() {
+ final ModelResolver modelResolver = new ModelResolver(mapper());
+ modelResolver.setOpenapi31(true);
+ final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
+
+ io.swagger.v3.oas.models.media.Schema schema = context.resolve(new AnnotatedType(JacksonPolymorphicParent.class));
+
+ assertNotNull(schema);
+ assertTrue(schema instanceof JsonSchema, "Expected JsonSchema in OpenAPI 3.1 mode, but got: " + schema.getClass());
+ JsonSchema jsonSchema = (JsonSchema) schema;
+ if (jsonSchema.getOneOf() != null) {
+ assertEquals(jsonSchema.getOneOf().size(), 2);
+ assertEquals((jsonSchema.getOneOf().get(0)).get$ref(), "#/components/schemas/JacksonChildA");
+ assertEquals((jsonSchema.getOneOf().get(1)).get$ref(), "#/components/schemas/JacksonChildB");
+ assertNotNull(jsonSchema.getDiscriminator());
+ assertEquals(jsonSchema.getDiscriminator().getPropertyName(), "type");
+ } else {
+ assertNotNull(jsonSchema.getProperties());
+ assertTrue(jsonSchema.getProperties().containsKey("commonProperty") ||
+ jsonSchema.getProperties().containsKey("type"),
+ "Expected to find either commonProperty or type discriminator property");
+ }
+ }
+
+ @Test
+ public void testComplexAllOfWithInlineProperties() {
+ final ModelResolver modelResolver = new ModelResolver(mapper());
+ modelResolver.setOpenapi31(true);
+ final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
+
+ io.swagger.v3.oas.models.media.Schema schema = context.resolve(new AnnotatedType(ComplexAllOfClass.class));
+
+ assertNotNull(schema);
+ assertTrue(schema instanceof JsonSchema, "Expected JsonSchema in OpenAPI 3.1 mode, but got: " + schema.getClass());
+ JsonSchema jsonSchema = (JsonSchema) schema;
+ assertNotNull(jsonSchema.getAllOf());
+ assertEquals(jsonSchema.getAllOf().size(), 3);
+ assertEquals((jsonSchema.getAllOf().get(0)).get$ref(), "#/components/schemas/ClassA");
+ assertEquals((jsonSchema.getAllOf().get(1)).get$ref(), "#/components/schemas/ClassB");
+ io.swagger.v3.oas.models.media.Schema inlineSchema = jsonSchema.getAllOf().get(2);
+ assertNotNull(inlineSchema);
+ assertEquals(inlineSchema.get$ref(), null);
+ assertNotNull(inlineSchema.getProperties());
+ assertTrue(inlineSchema.getProperties().containsKey("complexProperty"));
+ assertTrue(inlineSchema.getProperties().containsKey("additionalField"));
+ }
+
+ @Test
+ public void testNestedComposition() {
+ final ModelResolver modelResolver = new ModelResolver(mapper());
+ modelResolver.setOpenapi31(true);
+ final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
+
+ io.swagger.v3.oas.models.media.Schema schema = context.resolve(new AnnotatedType(NestedCompositionClass.class));
+
+ assertNotNull(schema);
+ assertTrue(schema instanceof JsonSchema, "Expected JsonSchema in OpenAPI 3.1 mode, but got: " + schema.getClass());
+ JsonSchema jsonSchema = (JsonSchema) schema;
+ assertNotNull(jsonSchema.getOneOf());
+ assertNotNull(jsonSchema.getAllOf());
+ assertEquals(jsonSchema.getOneOf().size(), 2);
+ assertEquals(jsonSchema.getAllOf().size(), 1);
+ assertEquals((jsonSchema.getOneOf().get(0)).get$ref(), "#/components/schemas/ChildClassA");
+ assertEquals((jsonSchema.getOneOf().get(1)).get$ref(), "#/components/schemas/ChildClassB");
+ assertEquals((jsonSchema.getAllOf().get(0)).get$ref(), "#/components/schemas/ClassA");
+ }
+
+ @Schema(
+ type = "object",
+ discriminatorMapping = {
+ @DiscriminatorMapping(value = "A", schema = ChildClassA.class),
+ @DiscriminatorMapping(value = "B", schema = ChildClassB.class)
+ },
+ oneOf = {ChildClassA.class, ChildClassB.class},
+ discriminatorProperty = "objectType"
+ )
+ public abstract static class ParentClass {
+ }
+
+ public static class ChildClassA extends ParentClass {
+ }
+
+ public static class ChildClassB extends ParentClass {
+ }
+
+ @Schema(allOf = {ClassA.class, ClassB.class, ClassC.class})
+ public static class MultiAllOfClass {
+ }
+
+ @Schema(
+ allOf = {ClassA.class, ClassB.class},
+ type = "object",
+ description = "Mixed composition with allOf and additional properties",
+ format = "custom-format"
+ )
+ public static class MixedCompositionClass {
+ @Schema(description = "Additional property")
+ public String additionalProperty;
+ }
+
+ public static class ClassA {
+ public String propertyA;
+ }
+
+ public static class ClassB {
+ public String propertyB;
+ }
+
+ public static class ClassC {
+ public String propertyC;
+ }
+
+ @Schema(anyOf = {ClassA.class, ClassB.class, ClassC.class})
+ public static class AnyOfClass {
+ }
+
+ @Schema(oneOf = {RefSubtypeA.class, RefSubtypeB.class})
+ public static class OneOfWithRefsClass {
+ }
+
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
+ @JsonSubTypes({
+ @JsonSubTypes.Type(value = JacksonChildA.class, name = "A"),
+ @JsonSubTypes.Type(value = JacksonChildB.class, name = "B")
+ })
+ public static class JacksonPolymorphicParent {
+ public String commonProperty;
+ }
+
+ public static class JacksonChildA extends JacksonPolymorphicParent {
+ public String childAProperty;
+ }
+
+ public static class JacksonChildB extends JacksonPolymorphicParent {
+ public String childBProperty;
+ }
+
+ @Schema(allOf = {ClassA.class, ClassB.class})
+ public static class ComplexAllOfClass {
+ @Schema(description = "Complex property with nested structure")
+ public String complexProperty;
+
+ @Schema(description = "Additional field for testing")
+ public String additionalField;
+ }
+
+ @Schema(
+ oneOf = {ChildClassA.class, ChildClassB.class},
+ allOf = {ClassA.class}
+ )
+ public static class NestedCompositionClass {
+ }
+
+ // Additional classes for $ref subtypes testing
+ public static class RefSubtypeA {
+ public String refPropertyA;
+ }
+
+ public static class RefSubtypeB {
+ public String refPropertyB;
+ }
+
+}
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 542cb87007..13b3d16213 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
@@ -30,8 +30,10 @@ public void testAnnotatedArray() {
SerializationMatchers.assertEqualsToYaml31(model, "type: array\n" +
"contains:\n" +
" type: string\n" +
+ "description: arraydescription\n" +
"items:\n" +
" type: string\n" +
+ " description: itemdescription\n"+
"maxContains: 10\n" +
"minContains: 1\n" +
"prefixItems:\n" +
@@ -85,6 +87,26 @@ public void testOAS31Fields() {
" properties:\n" +
" billingAddress:\n" +
" type: string\n" +
+ " acceptingCountries:\n" +
+ " type: array\n" +
+ " items:\n" +
+ " type: string\n" +
+ " description: accepting country\n" +
+ " enum:\n" +
+ " - UNITED_STATES_OF_AMERICA\n" +
+ " - CANADA\n" +
+ " uniqueItems: true\n" +
+ " availableCurrencies:\n" +
+ " type: array\n" +
+ " items:\n" +
+ " $ref: \"#/components/schemas/Currency\"\n" +
+ " description: available currencies\n" +
+ " uniqueItems: true\n" +
+ "Currency:\n" +
+ " type: object\n" +
+ " properties:\n" +
+ " currencyCode:\n" +
+ " type: string\n" +
"ModelWithOAS31Stuff:\n" +
" type: object\n" +
" $comment: Random comment at schema level\n" +
@@ -95,12 +117,16 @@ public void testOAS31Fields() {
" type: array\n" +
" contains:\n" +
" type: string\n" +
+ " description: arraydescription\n" +
" items:\n" +
" type: string\n" +
+ " description: itemdescription\n" +
+ " title: itemtitle\n" +
" maxContains: 10\n" +
" minContains: 1\n" +
" prefixItems:\n" +
" - type: string\n" +
+ " title: arraytitle\n" +
" unevaluatedItems:\n" +
" type: number\n" +
" status:\n" +
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/AnnotatedArray.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/AnnotatedArray.java
index 9e49f750d6..cc5e4c84f7 100644
--- a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/AnnotatedArray.java
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/AnnotatedArray.java
@@ -13,8 +13,10 @@
types = { "number" }
),
schema = @Schema(
- types = { "string" }
+ types = { "string" },
+ description = "itemdescription"
),
+ arraySchema = @Schema(description = "arraydescription"),
prefixItems = {
@Schema(
types = { "string" }
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/CreditCard.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/CreditCard.java
index fc8d4c900c..7a66fb13fe 100644
--- a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/CreditCard.java
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/CreditCard.java
@@ -1,8 +1,15 @@
package io.swagger.v3.core.resolving.v31.model;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.util.Set;
+
public class CreditCard {
private String billingAddress;
+ private Set acceptingCountries;
+ private Set availableCurrencies;
public String getBillingAddress() {
return billingAddress;
@@ -11,4 +18,22 @@ public String getBillingAddress() {
public void setBillingAddress(String billingAddress) {
this.billingAddress = billingAddress;
}
+
+ @ArraySchema(schema = @Schema(description = "accepting country"))
+ public Set getAcceptingCountries() {
+ return acceptingCountries;
+ }
+
+ public void setAcceptingCountries(Set acceptingCountries) {
+ this.acceptingCountries = acceptingCountries;
+ }
+
+ @ArraySchema(schema = @Schema(description = "available currencies"))
+ public Set getAvailableCurrencies() {
+ return availableCurrencies;
+ }
+
+ public void setAvailableCurrencies(Set availableCurrencies) {
+ this.availableCurrencies = availableCurrencies;
+ }
}
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/Currency.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/Currency.java
new file mode 100644
index 0000000000..b13e5adbf5
--- /dev/null
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/Currency.java
@@ -0,0 +1,14 @@
+package io.swagger.v3.core.resolving.v31.model;
+
+public class Currency {
+
+ private String currencyCode;
+
+ public String getCurrencyCode() {
+ return currencyCode;
+ }
+
+ public void setCurrencyCode(String currencyCode) {
+ this.currencyCode = currencyCode;
+ }
+}
\ No newline at end of file
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/ModelWithOAS31Stuff.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/ModelWithOAS31Stuff.java
index c1ef938175..ec3615dd3e 100644
--- a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/ModelWithOAS31Stuff.java
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/ModelWithOAS31Stuff.java
@@ -23,6 +23,13 @@ public class ModelWithOAS31Stuff {
private Client client;
@ArraySchema(
+ schema = @Schema(
+ types = { "string" },
+ description = "itemdescription",
+ title = "itemtitle"
+
+ ),
+ arraySchema = @Schema(description = "arraydescription", title = "arraytitle"),
maxContains = 10,
minContains = 1,
contains = @Schema(
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 3f1d1a8f85..53e338d0a8 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
@@ -29,6 +29,8 @@
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.security.SecurityScheme;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.testng.annotations.Test;
import org.yaml.snakeyaml.LoaderOptions;
@@ -37,6 +39,7 @@
import static org.testng.Assert.assertTrue;
public class OpenAPI3_1SerializationTest {
+ private static final Logger LOGGER = LoggerFactory.getLogger(OpenAPI3_1SerializationTest.class);
@Test
public void testSerializePetstore() throws Exception {
@@ -1390,7 +1393,7 @@ public void testBooleanSchemaSerialization() {
.openapi("3.1.0")
.components(new Components().addSchemas("test", new Schema().booleanSchemaValue(true)));
- System.out.println("--------- root ----------");
+ LOGGER.debug("--------- root ----------");
Json31.prettyPrint(openAPI);
assertEquals(Json31.pretty(openAPI), withJacksonSystemLineSeparator("{\n" +
" \"openapi\" : \"3.1.0\",\n" +
@@ -1400,19 +1403,19 @@ public void testBooleanSchemaSerialization() {
" }\n" +
" }\n" +
"}"));
- System.out.println("--------- schema ----------");
+ LOGGER.debug("--------- schema ----------");
Json31.prettyPrint(openAPI.getComponents().getSchemas().get("test"));
assertEquals(Json31.pretty(openAPI.getComponents().getSchemas().get("test")), "true");
- System.out.println("--------- root YAML----------");
+ LOGGER.debug("--------- root YAML----------");
Yaml31.prettyPrint(openAPI);
assertEquals(Yaml31.pretty(openAPI), "openapi: 3.1.0\n" +
"components:\n" +
" schemas:\n" +
" test: true\n");
- System.out.println("--------- schema YAML ----------");
+ LOGGER.debug("--------- schema YAML ----------");
Yaml31.prettyPrint(openAPI.getComponents().getSchemas().get("test"));
assertEquals(Yaml31.pretty(openAPI.getComponents().getSchemas().get("test")), "true\n");
- System.out.println("--------- root 3.0 ----------");
+ LOGGER.debug("--------- root 3.0 ----------");
Json.prettyPrint(openAPI);
assertEquals(Json.pretty(openAPI), withJacksonSystemLineSeparator("{\n" +
" \"openapi\" : \"3.1.0\",\n" +
@@ -1422,16 +1425,16 @@ public void testBooleanSchemaSerialization() {
" }\n" +
" }\n" +
"}"));
- System.out.println("--------- schema 3.0 ----------");
+ LOGGER.debug("--------- schema 3.0 ----------");
Json.prettyPrint(openAPI.getComponents().getSchemas().get("test"));
assertEquals(Json.pretty(openAPI.getComponents().getSchemas().get("test")), "{ }");
- System.out.println("--------- root YAML 3.0 ----------");
+ LOGGER.debug("--------- root YAML 3.0 ----------");
Yaml.prettyPrint(openAPI);
assertEquals(Yaml.pretty(openAPI), "openapi: 3.1.0\n" +
"components:\n" +
" schemas:\n" +
" test: {}\n");
- System.out.println("--------- schema YAML 3.0 ----------");
+ LOGGER.debug("--------- schema YAML 3.0 ----------");
Yaml.prettyPrint(openAPI.getComponents().getSchemas().get("test"));
assertEquals(Yaml.pretty(openAPI.getComponents().getSchemas().get("test")), "{}\n");
}
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/properties/PropertySerializationTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/properties/PropertySerializationTest.java
index 719b936be1..3025b23a4a 100644
--- a/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/properties/PropertySerializationTest.java
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/properties/PropertySerializationTest.java
@@ -2,6 +2,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.core.util.Json;
+import io.swagger.v3.core.util.JsonAssert;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.BooleanSchema;
import io.swagger.v3.oas.models.media.DateSchema;
@@ -32,7 +33,7 @@ public void serializeBooleanSchema() throws IOException {
final BooleanSchema p = new BooleanSchema()
._default(true);
final String json = "{\"type\":\"boolean\",\"default\":true}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should deserialize a BooleanSchema")
@@ -43,14 +44,14 @@ public void deserializeBooleanSchema() throws IOException {
assertNull(p.getFormat());
assertEquals(p.getClass(), BooleanSchema.class);
assertEquals(((BooleanSchema) p).getDefault(), Boolean.FALSE);
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should serialize a DateProperty")
public void serializeDateProperty() throws IOException {
final DateSchema p = new DateSchema();
final String json = "{\"type\":\"string\",\"format\":\"date\"}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should deserialize a DateProperty")
@@ -60,14 +61,14 @@ public void deserializeDateProperty() throws IOException {
assertEquals(p.getType(), "string");
assertEquals(p.getFormat(), "date");
assertEquals(p.getClass(), DateSchema.class);
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should serialize a DateTimeProperty")
public void serializeDateTimeProperty() throws IOException {
final DateTimeSchema p = new DateTimeSchema();
final String json = "{\"type\":\"string\",\"format\":\"date-time\"}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should deserialize a DateTimeProperty")
@@ -77,7 +78,7 @@ public void deserializeDateTimeProperty() throws IOException {
assertEquals(p.getType(), "string");
assertEquals(p.getFormat(), "date-time");
assertEquals(p.getClass(), DateTimeSchema.class);
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should serialize a DoubleProperty")
@@ -86,7 +87,7 @@ public void serializeDoubleProperty() throws IOException {
._default(new BigDecimal("3.14159"));
p.format("double");
final String json = "{\"type\":\"number\",\"format\":\"double\",\"default\":3.14159}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should deserialize a DoubleProperty")
@@ -96,7 +97,7 @@ public void deserializeDoubleProperty() throws IOException {
assertEquals(p.getType(), "number");
assertEquals(p.getFormat(), "double");
assertEquals(p.getClass(), NumberSchema.class);
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should serialize a FloatProperty")
@@ -105,7 +106,7 @@ public void serializeFloatProperty() throws IOException {
._default(new BigDecimal("1.2"));
p.format("float");
final String json = "{\"type\":\"number\",\"format\":\"float\",\"default\":1.2}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should deserialize a FloatProperty")
@@ -115,7 +116,7 @@ public void deserializeFloatProperty() throws IOException {
assertEquals(p.getType(), "number");
assertEquals(p.getFormat(), "float");
assertEquals(p.getClass(), NumberSchema.class);
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should serialize an IntegerProperty")
@@ -123,7 +124,7 @@ public void serializeIntegerProperty() throws IOException {
final IntegerSchema p = new IntegerSchema()
._default(32);
final String json = "{\"type\":\"integer\",\"format\":\"int32\",\"default\":32}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should deserialize a IntegerProperty")
@@ -133,7 +134,7 @@ public void deserializeIntegerProperty() throws IOException {
assertEquals(p.getType(), "integer");
assertEquals(p.getFormat(), "int32");
assertEquals(p.getClass(), IntegerSchema.class);
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should serialize a LongProperty")
@@ -142,7 +143,7 @@ public void serializeLongProperty() throws IOException {
.format("int64")
._default(8675309);
final String json = "{\"type\":\"integer\",\"format\":\"int64\",\"default\":8675309}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should deserialize a LongProperty")
@@ -152,14 +153,14 @@ public void deserializeLongProperty() throws IOException {
assertEquals(p.getType(), "integer");
assertEquals(p.getFormat(), "int64");
assertEquals(p.getClass(), IntegerSchema.class);
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should serialize a string MapProperty")
public void serializeStringMapProperty() throws IOException {
final Schema p = new MapSchema().additionalProperties(new StringSchema());
final String json = "{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\"}}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should deserialize a string MapProperty")
@@ -168,14 +169,14 @@ public void deserializeStringMapProperty() throws IOException {
final Schema p = m.readValue(json, Schema.class);
assertEquals(p.getType(), "object");
assertEquals(p.getClass(), MapSchema.class);
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should serialize a integer MapProperty")
public void serializeIntegerMapProperty() throws IOException {
final Schema p = new MapSchema().additionalProperties(new IntegerSchema());
final String json = "{\"type\":\"object\",\"additionalProperties\":{\"type\":\"integer\",\"format\":\"int32\"}}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should deserialize a integer MapProperty")
@@ -184,14 +185,14 @@ public void deserializeIntegerMapProperty() throws IOException {
final Schema p = m.readValue(json, Schema.class);
assertEquals(p.getType(), "object");
assertEquals(p.getClass(), MapSchema.class);
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should serialize a long MapProperty")
public void serializeLongMapProperty() throws IOException {
final Schema p = new MapSchema().additionalProperties(new IntegerSchema().format("int64"));
final String json = "{\"type\":\"object\",\"additionalProperties\":{\"type\":\"integer\",\"format\":\"int64\"}}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should deserialize a long MapProperty")
@@ -200,14 +201,14 @@ public void deserializeLongMapProperty() throws IOException {
final Schema p = m.readValue(json, Schema.class);
assertEquals(p.getType(), "object");
assertEquals(p.getClass(), MapSchema.class);
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should serialize a RefProperty")
public void serializeRefProperty() throws IOException {
final Schema p = new Schema().$ref("#/definitions/Dog");
final String json = "{\"$ref\":\"#/definitions/Dog\"}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should deserialize a RefProperty")
@@ -215,7 +216,7 @@ public void deserializeRefProperty() throws IOException {
final String json = "{\"$ref\":\"#/definitions/Dog\"}";
final Schema p = m.readValue(json, Schema.class);
assertEquals(p.getClass(), Schema.class);
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should serialize a StringProperty")
@@ -223,7 +224,7 @@ public void serializeStringProperty() throws IOException {
final StringSchema p = new StringSchema()
._default("Bob");
final String json = "{\"type\":\"string\",\"default\":\"Bob\"}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should deserialize a StringProperty")
@@ -232,7 +233,7 @@ public void deserializeStringProperty() throws IOException {
final Schema p = m.readValue(json, Schema.class);
assertEquals(p.getType(), "string");
assertEquals(p.getClass(), StringSchema.class);
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should serialize a StringProperty with enums")
@@ -243,7 +244,7 @@ public void serializeEnumStringProperty() throws IOException {
this.add("b");
}});
final String json = "{\"type\":\"string\",\"enum\":[\"a\",\"b\"]}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should deserialize a StringProperty with enums")
@@ -255,7 +256,7 @@ public void deserializeEnumStringProperty() throws IOException {
assertNotNull(_enum);
assertEquals(_enum, Arrays.asList("a", "b"));
assertEquals(p.getClass(), StringSchema.class);
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should deserialize an IntegerProperty with enums")
@@ -267,14 +268,14 @@ public void deserializeEnumIntegerProperty() throws IOException {
assertNotNull(_enum);
assertEquals(_enum, Arrays.asList(1, 2));
assertEquals(p.getClass(), IntegerSchema.class);
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should serialize a string array property")
public void serializeArrayStringProperty() throws IOException {
final Schema p = new ArraySchema().items(new StringSchema());
final String json = "{\"type\":\"array\",\"items\":{\"type\":\"string\"}}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should deserialize a string array property")
@@ -283,14 +284,14 @@ public void deserializeArrayStringProperty() throws IOException {
final Schema p = m.readValue(json, Schema.class);
assertEquals(p.getType(), "array");
assertEquals(p.getClass(), ArraySchema.class);
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should serialize a string property with readOnly set")
public void serializeReadOnlyStringProperty() throws IOException {
final Schema p = new StringSchema().readOnly(true);
final String json = "{\"type\":\"string\",\"readOnly\":true}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should serialize a string property with readOnly unset")
@@ -298,7 +299,7 @@ public void deserializeNotReadOnlyStringProperty() throws IOException {
final StringSchema p = new StringSchema();
p.setReadOnly(false);
final String json = "{\"type\":\"string\",\"readOnly\":false}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should serialize an object property with required set")
@@ -307,7 +308,7 @@ public void serializeObjectPropertyWithRequiredProperties() throws IOException {
.addProperties("stringProperty", new StringSchema());
p.required(Arrays.asList("stringProperty"));
final String json = "{\"required\":[\"stringProperty\"],\"type\":\"object\",\"properties\":{\"stringProperty\":{\"type\":\"string\"}}}";
- assertEquals(m.writeValueAsString(p), json);
+ JsonAssert.assertJsonEquals(m, m.writeValueAsString(p), json);
}
@Test(description = "it should deserialize an object property with required set")
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/util/JsonAssert.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/util/JsonAssert.java
new file mode 100644
index 0000000000..72a1870ced
--- /dev/null
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/util/JsonAssert.java
@@ -0,0 +1,20 @@
+package io.swagger.v3.core.util;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import static org.testng.Assert.assertTrue;
+
+public final class JsonAssert {
+ private JsonAssert() {
+ }
+
+ public static void assertJsonEquals(ObjectMapper mapper, String expectedJson, String actualJson) {
+ try {
+ JsonNode expectedNode = mapper.readTree(expectedJson);
+ JsonNode actualNode = mapper.readTree(actualJson);
+ assertTrue(expectedNode.equals(actualNode));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/util/OutputReplacer.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/util/OutputReplacer.java
index 2e11253784..33348b0092 100644
--- a/modules/swagger-core/src/test/java/io/swagger/v3/core/util/OutputReplacer.java
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/util/OutputReplacer.java
@@ -49,12 +49,18 @@ void setOutputStream(PrintStream outputStream) {
public String run(Function function) {
final PrintStream out = getOutputStream();
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-
+ final PrintStream capture = new PrintStream(outputStream);
try {
- setOutputStream(new PrintStream(outputStream));
+ setOutputStream(capture);
+ PrettyPrintHelper.setOverride(message -> {
+ capture.println(message);
+ capture.flush();
+ });
function.run();
} finally {
+ PrettyPrintHelper.clearOverride();
setOutputStream(out);
+ capture.close();
}
try {
diff --git a/modules/swagger-eclipse-transformer-maven-plugin/pom.xml b/modules/swagger-eclipse-transformer-maven-plugin/pom.xml
index 5b71dd86b1..f686961f26 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.v3swagger-project
- 2.2.34-SNAPSHOT
+ 2.2.40-SNAPSHOT../..4.0.0
diff --git a/modules/swagger-eclipse-transformer-maven-plugin/src/main/java/io/swagger/v3/oas/transformer/TransformMojo.java b/modules/swagger-eclipse-transformer-maven-plugin/src/main/java/io/swagger/v3/oas/transformer/TransformMojo.java
index 14cb87adb3..667f320168 100644
--- a/modules/swagger-eclipse-transformer-maven-plugin/src/main/java/io/swagger/v3/oas/transformer/TransformMojo.java
+++ b/modules/swagger-eclipse-transformer-maven-plugin/src/main/java/io/swagger/v3/oas/transformer/TransformMojo.java
@@ -1,10 +1,17 @@
package io.swagger.v3.oas.transformer;
+import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
@@ -62,6 +69,8 @@ public class TransformMojo extends AbstractMojo {
@Parameter(defaultValue = "transformed")
private String classifier;
+ private static final int MAX_LOG_BUFFER_SIZE = 16 * 1024;
+
@Parameter(defaultValue = "${project.build.directory}", required = true)
private File outputDirectory;
@@ -134,7 +143,9 @@ public void transform(final Transformer transformer, final Artifact sourceArtifa
* @return A configured transformer
*/
public Transformer getTransformer() {
- final Transformer transformer = new Transformer(System.out, System.err);
+ final Transformer transformer = new Transformer(
+ createLoggingPrintStream(getLog()::info),
+ createLoggingPrintStream(getLog()::error));
transformer.setOptionDefaults(JakartaTransformer.class, getOptionDefaults());
return transformer;
}
@@ -177,6 +188,55 @@ private Map getOptionDefaults() {
return optionDefaults;
}
+ private PrintStream createLoggingPrintStream(Consumer logConsumer) {
+ try {
+ return new PrintStream(new LoggingOutputStream(logConsumer, MAX_LOG_BUFFER_SIZE), true, StandardCharsets.UTF_8.name());
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("UTF-8 encoding is not supported", e);
+ }
+ }
+
+ private static final class LoggingOutputStream extends OutputStream {
+ private final Consumer logConsumer;
+ private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ private final int maxBufferSize;
+
+ private LoggingOutputStream(Consumer logConsumer, int maxBufferSize) {
+ this.logConsumer = logConsumer;
+ this.maxBufferSize = maxBufferSize > 0 ? maxBufferSize : Integer.MAX_VALUE;
+ }
+
+ @Override
+ public void write(int b) {
+ if (b == '\n') {
+ flushBuffer();
+ } else if (b != '\r') {
+ buffer.write(b);
+ if (buffer.size() >= this.maxBufferSize) {
+ flushBuffer();
+ }
+ }
+ }
+
+ @Override
+ public void flush() {
+ flushBuffer();
+ }
+
+ @Override
+ public void close() throws IOException {
+ flushBuffer();
+ }
+
+ private void flushBuffer() {
+ if (buffer.size() == 0) {
+ return;
+ }
+ logConsumer.accept(new String(buffer.toByteArray(), StandardCharsets.UTF_8));
+ buffer.reset();
+ }
+ }
+
private boolean isEmpty(final String input) {
return input == null || input.trim()
.length() == 0;
diff --git a/modules/swagger-gradle-plugin/README.md b/modules/swagger-gradle-plugin/README.md
index ca1116beff..8371a6d0b8 100644
--- a/modules/swagger-gradle-plugin/README.md
+++ b/modules/swagger-gradle-plugin/README.md
@@ -14,7 +14,7 @@ In many cases it is sufficient to set it to the `classpath` value, like:
Alternatively provide as value a classpath with the following dependencies (replacing the provided [`javax` ones](https://github.com/swagger-api/swagger-core/blob/master/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/SwaggerPlugin.java#L16-L25)):
-* org.apache.commons:commons-lang3:3.12.0
+* org.apache.commons:commons-lang3:3.18.0
* io.swagger.core.v3:swagger-jaxrs2-jakarta:2.2.9
* jakarta.ws.rs:jakarta.ws.rs-api:3.0.0
* jakarta.servlet:jakarta.servlet-api:5.0.0
@@ -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.33"
+ id "io.swagger.core.v3.swagger-gradle-plugin" version "2.2.39"
}
```
### Gradle 1.x and 2.0
@@ -43,7 +43,7 @@ buildscript {
}
}
dependencies {
- classpath "io.swagger.core.v3:swagger-gradle-plugin:2.2.33"
+ classpath "io.swagger.core.v3:swagger-gradle-plugin:2.2.39"
}
}
@@ -123,14 +123,13 @@ info:
url: http://www.apache.org/licenses/LICENSE-2.0.html
```
-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
-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
-Since version 2.2.28, `openAPIVersion` parameter is available, allowing to specify the version of the OpenAPI specification to be used for the resolved spec.
-Since version 2.2.29, `groupsValidationStrategy` parameter is available, allowing to specify the strategy for resolving Validation annotations (`never`, `always`, `neverIfNoContext`).
-Since version 2.2.29, `validatorProcessorClass` parameter is available, allowing to specify a custom validator processor class, implementation of `io.swagger.v3.core.util.ValidatorProcessor`.
\ No newline at end of file
+- 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
+- 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
+- Since version 2.2.28, `openAPIVersion` parameter is available, allowing to specify the version of the OpenAPI specification to be used for the resolved spec.
+- Since version 2.2.29, `groupsValidationStrategy` parameter is available, allowing to specify the strategy for resolving Validation annotations (`never`, `always`, `neverIfNoContext`).
+- Since version 2.2.29, `validatorProcessorClass` parameter is available, allowing to specify a custom validator processor class, implementation of `io.swagger.v3.core.util.ValidatorProcessor`.
diff --git a/modules/swagger-gradle-plugin/build.gradle b/modules/swagger-gradle-plugin/build.gradle
index b9496e55c9..85c953718c 100644
--- a/modules/swagger-gradle-plugin/build.gradle
+++ b/modules/swagger-gradle-plugin/build.gradle
@@ -24,7 +24,7 @@ compileJava {
dependencies {
implementation gradleApi()
- implementation 'org.apache.commons:commons-lang3:3.14.0'
+ implementation 'org.apache.commons:commons-lang3:3.18.0'
testImplementation gradleTestKit()
testImplementation('com.github.tomakehurst:wiremock:2.27.2') {
diff --git a/modules/swagger-gradle-plugin/gradle.properties b/modules/swagger-gradle-plugin/gradle.properties
index 008428b849..3ffa56989d 100644
--- a/modules/swagger-gradle-plugin/gradle.properties
+++ b/modules/swagger-gradle-plugin/gradle.properties
@@ -1,2 +1,2 @@
-version=2.2.34-SNAPSHOT
+version=2.2.40-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 20458c4bba..781bf346b9 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,8 +16,8 @@ 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.34-SNAPSHOT"));
+ dependencies.add(project.getDependencies().create("org.apache.commons:commons-lang3:3.18.0"));
+ dependencies.add(project.getDependencies().create("io.swagger.core.v3:swagger-jaxrs2:2.2.40-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/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 732bb3eb51..b390afd051 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
@@ -77,7 +77,7 @@ public void testSwaggerResolveTask() throws IOException {
" mavenCentral()\n" +
"}\n" +
"dependencies { \n" +
- " implementation 'io.swagger.core.v3:swagger-jaxrs2:2.2.34-SNAPSHOT'\n" +
+ " implementation 'io.swagger.core.v3:swagger-jaxrs2:2.2.40-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" +
@@ -150,7 +150,7 @@ public void testSwaggerResolveWithOAS31OptionTask() throws IOException {
" mavenCentral()\n" +
"}\n" +
"dependencies { \n" +
- " implementation 'io.swagger.core.v3:swagger-jaxrs2:2.2.34-SNAPSHOT'\n" +
+ " implementation 'io.swagger.core.v3:swagger-jaxrs2:2.2.40-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 3291022e26..43634e770e 100644
--- a/modules/swagger-integration/pom.xml
+++ b/modules/swagger-integration/pom.xml
@@ -6,7 +6,7 @@
io.swagger.core.v3swagger-project
- 2.2.34-SNAPSHOT
+ 2.2.40-SNAPSHOT../..swagger-integration
diff --git a/modules/swagger-java17-support/pom.xml b/modules/swagger-java17-support/pom.xml
index 4c91a741c3..6046d18c21 100644
--- a/modules/swagger-java17-support/pom.xml
+++ b/modules/swagger-java17-support/pom.xml
@@ -5,7 +5,7 @@
io.swagger.core.v3swagger-project
- 2.2.34-SNAPSHOT
+ 2.2.40-SNAPSHOT../../pom.xmlswagger-java17-support
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
index 2735cb788a..427895be63 100644
--- 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
@@ -6,9 +6,12 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import io.swagger.v3.core.jackson.ModelResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public abstract class SwaggerTestBase {
static ObjectMapper mapper;
+ private static final Logger LOGGER = LoggerFactory.getLogger(SwaggerTestBase.class);
public static ObjectMapper mapper() {
if (mapper == null) {
@@ -26,9 +29,9 @@ protected ModelResolver modelResolver() {
protected void prettyPrint(Object o) {
try {
- System.out.println(mapper().writer(new DefaultPrettyPrinter()).writeValueAsString(o));
+ LOGGER.debug(mapper().writer(new DefaultPrettyPrinter()).writeValueAsString(o));
} catch (Exception e) {
- e.printStackTrace();
+ LOGGER.error("Failed to pretty print object", e);
}
}
}
diff --git a/modules/swagger-jaxrs2-servlet-initializer-v2/pom.xml b/modules/swagger-jaxrs2-servlet-initializer-v2/pom.xml
index ba18af003f..7d295b9278 100644
--- a/modules/swagger-jaxrs2-servlet-initializer-v2/pom.xml
+++ b/modules/swagger-jaxrs2-servlet-initializer-v2/pom.xml
@@ -5,7 +5,7 @@
swagger-projectio.swagger.core.v3
- 2.2.34-SNAPSHOT
+ 2.2.40-SNAPSHOT../../4.0.0
diff --git a/modules/swagger-jaxrs2-servlet-initializer/pom.xml b/modules/swagger-jaxrs2-servlet-initializer/pom.xml
index 596360a231..837915efd4 100644
--- a/modules/swagger-jaxrs2-servlet-initializer/pom.xml
+++ b/modules/swagger-jaxrs2-servlet-initializer/pom.xml
@@ -5,7 +5,7 @@
swagger-projectio.swagger.core.v3
- 2.2.34-SNAPSHOT
+ 2.2.40-SNAPSHOT../../4.0.0
diff --git a/modules/swagger-jaxrs2/pom.xml b/modules/swagger-jaxrs2/pom.xml
index f8cdcbeb64..eed807e5a3 100644
--- a/modules/swagger-jaxrs2/pom.xml
+++ b/modules/swagger-jaxrs2/pom.xml
@@ -5,7 +5,7 @@
swagger-projectio.swagger.core.v3
- 2.2.34-SNAPSHOT
+ 2.2.40-SNAPSHOT../../4.0.0
diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/OpenApiServlet.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/OpenApiServlet.java
index 8c7dbe38d4..a246f1dc99 100644
--- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/OpenApiServlet.java
+++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/OpenApiServlet.java
@@ -41,7 +41,7 @@ public void init(ServletConfig config) throws ServletException {
.ctxId(ctxId)
.buildContext(true);
} catch (OpenApiConfigurationException e) {
- e.printStackTrace();
+ LOGGER.error("Failed to initialize OpenAPI servlet context", e);
}
}
diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/PetResourceTest.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/PetResourceTest.java
index 7d003965dc..1cd25fcbdb 100644
--- a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/PetResourceTest.java
+++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/PetResourceTest.java
@@ -54,6 +54,8 @@
import io.swagger.v3.jaxrs2.petstore.tags.TagOperationResource;
import io.swagger.v3.oas.integration.SwaggerConfiguration;
import io.swagger.v3.oas.models.OpenAPI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.testng.annotations.Test;
import java.io.File;
@@ -73,6 +75,7 @@
* Adding a lot of tests of different pet resource examples
*/
public class PetResourceTest extends AbstractAnnotationTest {
+ private static final Logger LOGGER = LoggerFactory.getLogger(PetResourceTest.class);
private static final String PETSTORE_SOURCE = "petstore/";
private static final String TAGS_SOURCE = "petstore/tags/";
private static final String OPERATIONS_SOURCE = "petstore/operation/";
@@ -314,7 +317,7 @@ private void compare(final Class clazz, final String source, boolean openapi31)
compareAsYaml(clazz, getOpenAPIAsString(file));
}
} catch (IOException e) {
- e.printStackTrace();
+ LOGGER.error("Failed to compare class {} with YAML resource {}", clazz.getName(), file, e);
fail();
}
}
diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/annotations/AbstractAnnotationTest.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/annotations/AbstractAnnotationTest.java
index 8048810680..1ea6863dc3 100644
--- a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/annotations/AbstractAnnotationTest.java
+++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/annotations/AbstractAnnotationTest.java
@@ -8,6 +8,8 @@
import io.swagger.v3.oas.integration.SwaggerConfiguration;
import io.swagger.v3.oas.models.OpenAPI;
import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
@@ -16,6 +18,8 @@
import static org.testng.Assert.fail;
public abstract class AbstractAnnotationTest {
+ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAnnotationTest.class);
+
public String readIntoYaml(final Class> cls) {
Reader reader = new Reader(new OpenAPI());
OpenAPI openAPI = reader.read(cls);
@@ -37,7 +41,7 @@ public void compareToYamlFile(final Class> cls, String source){
try {
compareAsYaml(cls, getOpenAPIAsString(file));
} catch (IOException e) {
- e.printStackTrace();
+ LOGGER.error("Failed to compare class {} with YAML resource {}", cls.getName(), file, e);
fail();
}
}
diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/util/ServletUtilsTest.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/util/ServletUtilsTest.java
index 57af4fa7d0..b53f61c513 100644
--- a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/util/ServletUtilsTest.java
+++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/util/ServletUtilsTest.java
@@ -10,6 +10,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
import static org.testng.Assert.assertNotNull;
public class ServletUtilsTest {
@@ -25,7 +26,8 @@ public void convertWithRightOutputSize() throws Exception {
assertEquals(multivaluedMap.size(), 2);
assertEquals(multivaluedMap.get("key1").size(), 2);
assertEquals(multivaluedMap.get("key2").size(), 4);
- assertEquals(multivaluedMap.keySet().iterator().next(), "key1");
+ assertTrue(multivaluedMap.containsKey("key1"));
+ assertTrue(multivaluedMap.containsKey("key2"));
}
@Test(description = "convert query parameters to multivaluedmap with decoded values")
diff --git a/modules/swagger-maven-plugin/README.md b/modules/swagger-maven-plugin/README.md
index a368752459..6c9f45c029 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.v3swagger-maven-plugin
- 2.2.33
+ 2.2.39openapi${project.build.directory}/generatedtest
@@ -47,7 +47,7 @@ Both `javax` and `jakarta` examples are provided below
io.swagger.core.v3swagger-jaxrs2
- 2.2.33
+ 2.2.39
@@ -73,7 +73,7 @@ Both `javax` and `jakarta` examples are provided below
io.swagger.core.v3swagger-maven-plugin-jakarta
- 2.2.33
+ 2.2.39openapi${project.build.directory}/generatedtest
@@ -98,7 +98,7 @@ Both `javax` and `jakarta` examples are provided below
io.swagger.core.v3swagger-jaxrs2-jakarta
- 2.2.33
+ 2.2.39
@@ -126,7 +126,7 @@ Both `javax` and `jakarta` examples are provided below
io.swagger.core.v3swagger-maven-plugin
- 2.2.33
+ 2.2.39openapi${project.build.directory}/generatedtest
@@ -155,7 +155,7 @@ Both `javax` and `jakarta` examples are provided below
io.swagger.core.v3swagger-maven-plugin-jakarta
- 2.2.33
+ 2.2.39openapi${project.build.directory}/generatedtest
diff --git a/modules/swagger-maven-plugin/pom.xml b/modules/swagger-maven-plugin/pom.xml
index 4cb69b5b1d..288fb3149b 100644
--- a/modules/swagger-maven-plugin/pom.xml
+++ b/modules/swagger-maven-plugin/pom.xml
@@ -4,7 +4,7 @@
io.swagger.core.v3swagger-project
- 2.2.34-SNAPSHOT
+ 2.2.40-SNAPSHOT../..4.0.0
diff --git a/modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/IncludeProjectDependenciesComponentConfigurator.java b/modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/IncludeProjectDependenciesComponentConfigurator.java
index 8c37bbcd2c..40523ce96a 100644
--- a/modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/IncludeProjectDependenciesComponentConfigurator.java
+++ b/modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/IncludeProjectDependenciesComponentConfigurator.java
@@ -39,8 +39,9 @@ public void configureComponent(Object component, PlexusConfiguration configurati
private void addProjectDependenciesToClassRealm(ExpressionEvaluator expressionEvaluator, ClassRealm containerRealm) throws ComponentConfigurationException {
List compileClasspathElements;
try {
- //noinspection unchecked
- compileClasspathElements = (List) expressionEvaluator.evaluate("${project.compileClasspathElements}");
+ @SuppressWarnings("unchecked")
+ List evaluated = (List) expressionEvaluator.evaluate("${project.compileClasspathElements}");
+ compileClasspathElements = evaluated;
} catch (ExpressionEvaluationException e) {
throw new ComponentConfigurationException("There was a problem evaluating: ${project.compileClasspathElements}", e);
}
@@ -68,4 +69,4 @@ private URL[] buildURLs(List runtimeClasspathElements) throws ComponentC
return urls.toArray(new URL[urls.size()]);
}
-}
\ No newline at end of file
+}
diff --git a/modules/swagger-models/pom.xml b/modules/swagger-models/pom.xml
index bb00424253..4857479d5b 100644
--- a/modules/swagger-models/pom.xml
+++ b/modules/swagger-models/pom.xml
@@ -4,7 +4,7 @@
io.swagger.core.v3swagger-project
- 2.2.34-SNAPSHOT
+ 2.2.40-SNAPSHOT../..4.0.0
diff --git a/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/ComposedSchema.java b/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/ComposedSchema.java
index deea389061..a1a9e222d6 100644
--- a/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/ComposedSchema.java
+++ b/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/ComposedSchema.java
@@ -6,6 +6,55 @@
public class ComposedSchema extends Schema