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-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 e4752611fc..220af8d71d 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 @@ -2092,43 +2092,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/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/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-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 { + public static ComposedSchema from(Schema subtypeModel) { + ComposedSchema composedSchema = new ComposedSchema(); + copySchemaProperties(composedSchema, subtypeModel); + + + if (shouldSetExample(subtypeModel)) { + composedSchema.example(subtypeModel.getExample()); + } + composedSchema.setEnum(subtypeModel.getEnum()); + return composedSchema; + } + + private static void copySchemaProperties(ComposedSchema target, Schema source) { + target.title(source.getTitle()) + .name(source.getName()) + .deprecated(source.getDeprecated()) + .additionalProperties(source.getAdditionalProperties()) + .description(source.getDescription()) + .discriminator(source.getDiscriminator()) + .exclusiveMaximum(source.getExclusiveMaximum()) + .exclusiveMinimum(source.getExclusiveMinimum()) + .externalDocs(source.getExternalDocs()) + .format(source.getFormat()) + .maximum(source.getMaximum()) + .maxItems(source.getMaxItems()) + .maxLength(source.getMaxLength()) + .maxProperties(source.getMaxProperties()) + .minimum(source.getMinimum()) + .minItems(source.getMinItems()) + .minLength(source.getMinLength()) + .minProperties(source.getMinProperties()) + .multipleOf(source.getMultipleOf()) + .not(source.getNot()) + .nullable(source.getNullable()) + .pattern(source.getPattern()) + .properties(source.getProperties()) + .readOnly(source.getReadOnly()) + .required(source.getRequired()) + .type(source.getType()) + .uniqueItems(source.getUniqueItems()) + .writeOnly(source.getWriteOnly()) + .xml(source.getXml()) + .extensions(source.getExtensions()); + } + + + private static boolean shouldSetExample(Schema model) { + return model.getExample() != null || model.getExampleSetFlag(); + } @Override public String toString() {