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 1aab00a41b..5f543a2430 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 @@ -59,7 +59,6 @@ import io.swagger.v3.oas.models.media.IntegerSchema; import io.swagger.v3.oas.models.media.JsonSchema; import io.swagger.v3.oas.models.media.MapSchema; -import io.swagger.v3.oas.models.media.NumberSchema; import io.swagger.v3.oas.models.media.ObjectSchema; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.StringSchema; @@ -153,6 +152,7 @@ public ObjectMapper objectMapper() { @Override public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context, Iterator next) { + boolean implicitObject = false; boolean isPrimitive = false; Schema model = null; List requiredProps = new ArrayList<>(); @@ -269,7 +269,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context resolveArraySchema(annotatedType, schema, resolvedArrayAnnotation); Schema innerSchema = null; - Schema primitive = PrimitiveType.createProperty(cls); + Schema primitive = PrimitiveType.createProperty(cls, openapi31); if (primitive != null) { innerSchema = primitive; } else { @@ -310,7 +310,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context primitiveType = PrimitiveType.fromName(resolvedSchemaAnnotation.type()); } if (primitiveType != null) { - Schema primitive = primitiveType.createProperty(); + Schema primitive = openapi31 ? primitiveType.createProperty31() : primitiveType.createProperty(); model = primitive; isPrimitive = true; @@ -318,7 +318,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context } if (model == null && type.isEnumType()) { - model = new StringSchema(); + model = openapi31 ? new JsonSchema().typesItem("string") : new StringSchema(); _addEnumProps(type.getRawClass(), model); isPrimitive = true; } @@ -326,7 +326,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context if (resolvedSchemaAnnotation != null && StringUtils.isEmpty(resolvedSchemaAnnotation.type())) { PrimitiveType primitiveType = PrimitiveType.fromTypeAndFormat(type, resolvedSchemaAnnotation.format()); if (primitiveType != null) { - model = primitiveType.createProperty(); + model = openapi31 ? primitiveType.createProperty31() : primitiveType.createProperty(); isPrimitive = true; } } @@ -334,7 +334,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context if (model == null) { PrimitiveType primitiveType = PrimitiveType.fromType(type); if (primitiveType != null) { - model = primitiveType.createProperty(); + model = openapi31 ? primitiveType.createProperty31() : primitiveType.createProperty(); isPrimitive = true; } } @@ -561,10 +561,16 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context } } else if (isComposedSchema) { model = new ComposedSchema().name(name); - if (openapi31 && resolvedArrayAnnotation == null) { - model.addType("object"); + if ( + (openapi31 && Boolean.TRUE.equals(PrimitiveType.explicitObjectType)) || + (!openapi31 && (!Boolean.FALSE.equals(PrimitiveType.explicitObjectType)))) { + if (openapi31 && resolvedArrayAnnotation == null) { + model.addType("object"); + } else { + model.type("object"); + } } else { - model.type("object"); + implicitObject = true; } } else { AnnotatedType aType = ReferenceTypeUtils.unwrapReference(annotatedType); @@ -573,10 +579,16 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context return model; } else { model = new Schema().name(name); - if (openapi31 && resolvedArrayAnnotation == null) { - model.addType("object"); + if ( + (openapi31 && Boolean.TRUE.equals(PrimitiveType.explicitObjectType)) || + (!openapi31 && (!Boolean.FALSE.equals(PrimitiveType.explicitObjectType)))) { + if (openapi31 && resolvedArrayAnnotation == null) { + model.addType("object"); + } else { + model.type("object"); + } } else { - model.type("object"); + implicitObject = true; } } } @@ -1080,11 +1092,18 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context // define the model here to support self/cyclic referencing of models context.defineModel(name, model, annotatedType, null); } - + // check if it has "object" related keywords + if (isInferredObjectSchema(model) && model.get$ref() == null) { + if (openapi31 && model.getTypes() == null) { + model.addType("object"); + } else if (!openapi31 && model.getType() == null){ + model.type("object"); + } + } Schema.SchemaResolution resolvedSchemaResolution = AnnotationsUtils.resolveSchemaResolution(this.schemaResolution, resolvedSchemaAnnotation); if (model != null && annotatedType.isResolveAsRef() && - (isComposedSchema || isObjectSchema(model)) && + (isComposedSchema || isObjectSchema(model) || implicitObject) && StringUtils.isNotBlank(model.getName())) { if (context.getDefinedModels().containsKey(model.getName())) { if (!Schema.SchemaResolution.INLINE.equals(resolvedSchemaResolution)) { @@ -1290,9 +1309,13 @@ protected void _addEnumProps(Class propClass, Schema property) { } else { n = _intr().findEnumValue(en); } - if (property instanceof StringSchema) { - StringSchema sp = (StringSchema) property; - sp.addEnumItem(n); + if (isStringSchema(property)) { + if (openapi31) { + property.addEnumItemObject(n); + } else { + StringSchema sp = (StringSchema) property; + sp.addEnumItem(n); + } } } } @@ -1431,7 +1454,7 @@ protected Schema processAsId(String propertyName, AnnotatedType type, final AnnotatedMember propMember = def.getPrimaryMember(); final JavaType propType = propMember.getType(); if (PrimitiveType.fromType(propType) != null) { - return PrimitiveType.createProperty(propType); + return PrimitiveType.createProperty(propType, openapi31); } else { List list = new ArrayList<>(); for (Annotation a : propMember.annotations()) { @@ -2593,7 +2616,7 @@ protected void resolveDiscriminatorProperty(JavaType type, ModelConverterContext modelToUpdate = context.getDefinedModels().get(model.get$ref().substring(SCHEMA_COMPONENT_PREFIX)); } if (modelToUpdate.getProperties() == null || !modelToUpdate.getProperties().keySet().contains(typeInfoProp)) { - Schema discriminatorSchema = new StringSchema().name(typeInfoProp); + Schema discriminatorSchema = openapi31 ? new JsonSchema().typesItem("string").name(typeInfoProp) : new StringSchema().name(typeInfoProp); modelToUpdate.addProperties(typeInfoProp, discriminatorSchema); if (modelToUpdate.getRequired() == null || !modelToUpdate.getRequired().contains(typeInfoProp)) { modelToUpdate.addRequiredItem(typeInfoProp); @@ -3429,7 +3452,20 @@ public void setConfiguration(Configuration configuration) { } protected boolean isObjectSchema(Schema schema) { - return (schema.getTypes() != null && schema.getTypes().contains("object")) || "object".equals(schema.getType()) || (schema.getType() == null && schema.getProperties() != null && !schema.getProperties().isEmpty()); + return (schema.getTypes() != null && schema.getTypes().contains("object")) || "object".equals(schema.getType()) || (schema.getType() == null && ((schema.getProperties() != null && !schema.getProperties().isEmpty()) || (schema.getPatternProperties() != null && !schema.getPatternProperties().isEmpty()))); + } + + protected boolean isInferredObjectSchema(Schema schema) { + return ((schema.getProperties() != null && !schema.getProperties().isEmpty()) + || (schema.getPatternProperties() != null && !schema.getPatternProperties().isEmpty()) + || (schema.getAdditionalProperties() != null) + || (schema.getUnevaluatedProperties() != null) + || (schema.getRequired() != null && !schema.getRequired().isEmpty()) + || (schema.getPropertyNames() != null) + || (schema.getDependentRequired() != null && !schema.getDependentRequired().isEmpty()) + || (schema.getDependentSchemas() != null && !schema.getDependentSchemas().isEmpty()) + || (schema.getMinProperties() != null && schema.getMinProperties() > 0) + || (schema.getMaxProperties() != null && schema.getMaxProperties() > 0)); } protected boolean isArraySchema(Schema schema) { 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 7dbc5ab396..9ce4fb1439 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 @@ -506,7 +506,7 @@ public static Optional getArraySchema(io.swagger.v3.oas.annotations.medi arraySchemaObject = new ArraySchema(); } else { if (existingSchema == null) { - arraySchemaObject = new ArraySchema(); + arraySchemaObject = new JsonSchema().typesItem("array"); } else { arraySchemaObject = existingSchema; } @@ -889,7 +889,7 @@ public static Schema resolveSchemaFromType(Class schemaImplementation, Compon Schema schemaObject; PrimitiveType primitiveType = PrimitiveType.fromType(schemaImplementation); if (primitiveType != null) { - schemaObject = primitiveType.createProperty(); + schemaObject = openapi31 ? primitiveType.createProperty31() : primitiveType.createProperty(); } else { schemaObject = new Schema(); ResolvedSchema resolvedSchema = null; @@ -1583,7 +1583,7 @@ public static Optional getContent(io.swagger.v3.oas.annotations.media.C } if (annotationContent.schemaProperties().length > 0) { if (mediaType.getSchema() == null) { - mediaType.schema(new Schema().type("object")); + mediaType.schema(openapi31 ? new JsonSchema().typesItem("object") : new Schema().type("object")); } Schema oSchema = mediaType.getSchema(); for (SchemaProperty sp: annotationContent.schemaProperties()) { diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ModelDeserializer.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ModelDeserializer.java index 42bfe857b2..d2f232e4be 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ModelDeserializer.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ModelDeserializer.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; +import io.swagger.v3.oas.models.media.ArbitrarySchema; import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.media.BooleanSchema; import io.swagger.v3.oas.models.media.ComposedSchema; @@ -32,6 +33,15 @@ public class ModelDeserializer extends JsonDeserializer { + static Boolean useArbitrarySchema = false; + static { + if (System.getenv(Schema.USE_ARBITRARY_SCHEMA_PROPERTY) != null) { + useArbitrarySchema = Boolean.parseBoolean(System.getenv(Schema.USE_ARBITRARY_SCHEMA_PROPERTY)); + } else if (System.getProperty(Schema.USE_ARBITRARY_SCHEMA_PROPERTY) != null) { + useArbitrarySchema = Boolean.parseBoolean(System.getProperty(Schema.USE_ARBITRARY_SCHEMA_PROPERTY)); + } + } + protected boolean openapi31 = false; @Override public Schema deserialize(JsonParser jp, DeserializationContext ctxt) @@ -85,18 +95,18 @@ public Schema deserialize(JsonParser jp, DeserializationContext ctxt) schema = Json.mapper().convertValue(node, StringSchema.class); } } else if (type.textValue().equals("object")) { - schema = deserializeObjectSchema(node); + schema = deserializeArbitraryOrObjectSchema(node, true); } } else if (node.get("$ref") != null) { schema = new Schema().$ref(node.get("$ref").asText()); - } else { // assume object - schema = deserializeObjectSchema(node); + } else { + schema = deserializeArbitraryOrObjectSchema(node, false); } return schema; } - private Schema deserializeObjectSchema(JsonNode node) { + private Schema deserializeArbitraryOrObjectSchema(JsonNode node, boolean alwaysObject) { JsonNode additionalProperties = node.get("additionalProperties"); Schema schema = null; if (additionalProperties != null) { @@ -117,7 +127,11 @@ private Schema deserializeObjectSchema(JsonNode node) { schema = ms; } } else { - schema = Json.mapper().convertValue(node, ObjectSchema.class); + if (!Boolean.TRUE.equals(useArbitrarySchema) || alwaysObject) { + schema = Json.mapper().convertValue(node, ObjectSchema.class); + } else { + schema = Json.mapper().convertValue(node, ArbitrarySchema.class); + } } if (schema != null) { schema.jsonSchema(Json31.jsonSchemaAsMap(node)); diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java index 9fc23217c0..507bdc9180 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java @@ -253,8 +253,8 @@ public static Parameter applyAnnotations( if (parameter.getSchema() == null) { parameter.setSchema(new ArraySchema()); } - if (parameter.getSchema() instanceof ArraySchema) { - ArraySchema as = (ArraySchema) parameter.getSchema(); + if (isArraySchema(parameter.getSchema())) { + Schema as = parameter.getSchema(); Integer min = (Integer) annotation.annotationType().getMethod("min").invoke(annotation); if (min != null) { as.setMinItems(min); @@ -281,10 +281,9 @@ public static Parameter applyAnnotations( } } if (paramSchema != null) { - if (paramSchema instanceof ArraySchema) { - ArraySchema as = (ArraySchema) paramSchema; + if (isArraySchema(paramSchema)) { if (defaultValue != null) { - as.getItems().setDefault(defaultValue); + paramSchema.getItems().setDefault(defaultValue); } } else { if (defaultValue != null) { @@ -295,6 +294,10 @@ public static Parameter applyAnnotations( return parameter; } + public static boolean isArraySchema(Schema schema) { + return "array".equals(schema.getType()) || (schema.getTypes() != null && schema.getTypes().contains("array")); + } + public static void setParameterExplode(Parameter parameter, io.swagger.v3.oas.annotations.Parameter p) { if (isExplodable(p, parameter)) { if (Explode.TRUE.equals(p.explode())) { diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/PrimitiveType.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/PrimitiveType.java index df32dc2958..4ec3b94ecc 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/PrimitiveType.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/PrimitiveType.java @@ -8,6 +8,7 @@ import io.swagger.v3.oas.models.media.DateTimeSchema; import io.swagger.v3.oas.models.media.FileSchema; import io.swagger.v3.oas.models.media.IntegerSchema; +import io.swagger.v3.oas.models.media.JsonSchema; import io.swagger.v3.oas.models.media.NumberSchema; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.StringSchema; @@ -29,17 +30,26 @@ * of classes into Swagger primitive types. */ public enum PrimitiveType { + STRING(String.class, "string") { @Override public Schema createProperty() { return new StringSchema(); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("string"); + } }, BOOLEAN(Boolean.class, "boolean") { @Override public Schema createProperty() { return new BooleanSchema(); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("boolean"); + } }, BYTE(Byte.class, "byte") { @Override @@ -51,6 +61,10 @@ public Schema createProperty() { } return new ByteArraySchema(); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("string").format("byte"); + } }, BINARY(Byte.class, "binary") { @Override @@ -62,104 +76,174 @@ public Schema createProperty() { } return new BinarySchema(); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("string").format("binary"); + } }, URI(java.net.URI.class, "uri") { @Override public Schema createProperty() { return new StringSchema().format("uri"); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("string").format("uri"); + } }, URL(java.net.URL.class, "url") { @Override public Schema createProperty() { return new StringSchema().format("url"); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("string").format("url"); + } }, EMAIL(String.class, "email") { @Override public Schema createProperty() { return new StringSchema().format("email"); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("string").format("email"); + } }, UUID(java.util.UUID.class, "uuid") { @Override public UUIDSchema createProperty() { return new UUIDSchema(); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("string").format("uuid"); + } }, INT(Integer.class, "integer") { @Override public IntegerSchema createProperty() { return new IntegerSchema(); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("integer").format("int32"); + } }, LONG(Long.class, "long") { @Override public Schema createProperty() { return new IntegerSchema().format("int64"); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("integer").format("int64"); + } }, FLOAT(Float.class, "float") { @Override public Schema createProperty() { return new NumberSchema().format("float"); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("number").format("float"); + } }, DOUBLE(Double.class, "double") { @Override public Schema createProperty() { return new NumberSchema().format("double"); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("number").format("double"); + } }, INTEGER(java.math.BigInteger.class) { @Override public Schema createProperty() { return new IntegerSchema().format(null); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("integer"); + } }, DECIMAL(java.math.BigDecimal.class, "number") { @Override public Schema createProperty() { return new NumberSchema(); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("number"); + } }, NUMBER(Number.class, "number") { @Override public Schema createProperty() { return new NumberSchema(); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("number"); + } }, DATE(DateStub.class, "date") { @Override public DateSchema createProperty() { return new DateSchema(); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("string").format("date"); + } }, DATE_TIME(java.util.Date.class, "date-time") { @Override public DateTimeSchema createProperty() { return new DateTimeSchema(); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("string").format("date-time"); + } }, PARTIAL_TIME(java.time.LocalTime.class, "partial-time") { @Override public Schema createProperty() { return new StringSchema().format("partial-time"); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("string").format("partial-time"); + } }, FILE(java.io.File.class, "file") { @Override public FileSchema createProperty() { return new FileSchema(); } + @Override + public Schema createProperty31() { + return new JsonSchema().typesItem("string").format("binary"); + } }, OBJECT(Object.class) { + @Override public Schema createProperty() { - return new Schema().type("object"); + return explicitObjectType == null || explicitObjectType ? new Schema().type("object"): new Schema(); + } + @Override + public Schema createProperty31() { + return new JsonSchema(); } }; + public static Boolean explicitObjectType; private static final Map, PrimitiveType> KEY_CLASSES; private static final Map, Collection> MULTI_KEY_CLASSES; private static final Map, PrimitiveType> BASE_CLASSES; @@ -289,6 +373,12 @@ public Schema createProperty() { addKeys(names, INT, "int"); addKeys(names, OBJECT, "object"); NAMES = Collections.unmodifiableMap(names); + + if (System.getenv(Schema.EXPLICIT_OBJECT_SCHEMA_PROPERTY) != null) { + explicitObjectType = Boolean.parseBoolean(System.getenv(Schema.EXPLICIT_OBJECT_SCHEMA_PROPERTY)); + } else if (System.getProperty(Schema.EXPLICIT_OBJECT_SCHEMA_PROPERTY) != null) { + explicitObjectType = Boolean.parseBoolean(System.getProperty(Schema.EXPLICIT_OBJECT_SCHEMA_PROPERTY)); + } } private PrimitiveType(Class keyClass) { @@ -434,13 +524,21 @@ public static PrimitiveType fromTypeAndFormat(String type, String format) { } public static Schema createProperty(Type type) { + return createProperty(type, false); + } + + public static Schema createProperty(Type type, boolean openapi31) { final PrimitiveType item = fromType(type); - return item == null ? null : item.createProperty(); + return item == null ? null : openapi31 ? item.createProperty31() : item.createProperty(); } public static Schema createProperty(String name) { + return createProperty(name, false); + } + + public static Schema createProperty(String name, boolean openapi31) { final PrimitiveType item = fromName(name); - return item == null ? null : item.createProperty(); + return item == null ? null : openapi31 ? item.createProperty31() : item.createProperty(); } public static String getCommonName(Type type) { @@ -457,6 +555,7 @@ public String getCommonName() { } public abstract Schema createProperty(); + public abstract Schema createProperty31(); private static void addKeys(Map map, PrimitiveType type, K... keys) { for (K key : keys) { 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 0a813cf7a7..542cb87007 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 @@ -65,11 +65,12 @@ public void testOAS31Fields() { " - UNITED_STATES_OF_AMERICA\n" + " - CANADA\n" + " propertyNames:\n" + - " $ref: \"#/components/schemas/PropertyNamesPattern\"\n" + + " pattern: \"^[A-Za-z_][A-Za-z0-9_]*$\"\n" + "AnnotatedCountry:\n" + " type: object\n" + " properties:\n" + " country:\n" + + " type: string\n" + " const: United States\n" + "Client:\n" + " type: object\n" + @@ -167,17 +168,19 @@ public void testOAS31Fields() { " type: object\n" + " properties:\n" + " postalCode:\n" + + " type: string\n" + " pattern: \"[0-9]{5}(-[0-9]{4})?\"\n" + "PostalCodePattern:\n" + " type: object\n" + " properties:\n" + " postalCode:\n" + + " type: string\n" + " pattern: \"[A-Z][0-9][A-Z] [0-9][A-Z][0-9]\"\n" + "PropertyNamesPattern:\n" + - " type: object\n" + " pattern: \"^[A-Za-z_][A-Za-z0-9_]*$\"\n"); } + @Test public void testDependentSchemasAnnotation() { final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(true); diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/Address.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/Address.java index 862b4d4e50..808daf08c8 100644 --- a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/Address.java +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/Address.java @@ -70,7 +70,8 @@ class AnnotatedCountry { private Object country; @Schema( - _const = "United States" + _const = "United States", + type = "string" ) public Object getCountry() { diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/PostalCodeNumberPattern.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/PostalCodeNumberPattern.java index 9d20719472..a3d4ba6848 100644 --- a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/PostalCodeNumberPattern.java +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/PostalCodeNumberPattern.java @@ -8,7 +8,8 @@ public class PostalCodeNumberPattern { private Object postalCode; @Schema( - pattern = "[0-9]{5}(-[0-9]{4})?" + pattern = "[0-9]{5}(-[0-9]{4})?", + type = "string" ) public Object getPostalCode() { return postalCode; diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/PostalCodePattern.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/PostalCodePattern.java index e345c09d96..e5d0a0ead3 100644 --- a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/PostalCodePattern.java +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/v31/model/PostalCodePattern.java @@ -7,7 +7,8 @@ public class PostalCodePattern { private Object postalCode; @Schema( - pattern = "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" + pattern = "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]", + type = "string" ) public Object getPostalCode() { return postalCode; diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java index 42c862093f..4ff9a53eb0 100644 --- a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java @@ -89,6 +89,7 @@ import io.swagger.v3.jaxrs2.resources.Ticket4804ProcessorResource; import io.swagger.v3.jaxrs2.resources.Ticket4804Resource; import io.swagger.v3.jaxrs2.resources.Ticket4859Resource; +import io.swagger.v3.jaxrs2.resources.Ticket4879Resource; import io.swagger.v3.jaxrs2.resources.UploadResource; import io.swagger.v3.jaxrs2.resources.UrlEncodedResourceWithEncodings; import io.swagger.v3.jaxrs2.resources.UserAnnotationResource; @@ -3623,8 +3624,7 @@ public void test31RefSiblings() { " properties:\n" + " foo:\n" + " $ref: \"#/components/schemas/Foo\"\n" + - " SimpleCategory:\n" + - " type: object\n" ; + " SimpleCategory: {}\n"; SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); } @@ -3698,7 +3698,6 @@ public void testSiblingsOnResource() { "components:\n" + " schemas:\n" + " PetSimple:\n" + - " type: object\n" + " description: Pet\n"; SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); } @@ -3747,7 +3746,6 @@ public void testSiblingsOnResourceResponse() { "components:\n" + " schemas:\n" + " PetSimple:\n" + - " type: object\n" + " description: Pet\n"; SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); } @@ -3793,7 +3791,6 @@ public void testSiblingsOnResourceRequestBody() { "components:\n" + " schemas:\n" + " PetSimple:\n" + - " type: object\n" + " description: Pet\n"; SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); } @@ -3870,7 +3867,6 @@ public void testSiblingsOnResourceRequestBodyMultiple() { "components:\n" + " schemas:\n" + " PetSimple:\n" + - " type: object\n" + " description: Pet\n"; SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); } @@ -4080,7 +4076,7 @@ public void testMisc31() { " - United States of America\n" + " - Canada\n" + " propertyNames:\n" + - " $ref: \"#/components/schemas/PropertyNamesPattern\"\n" + + " pattern: \"^[A-Za-z_][A-Za-z0-9_]*$\"\n" + " AnnotatedCountry:\n" + " type: object\n" + " properties:\n" + @@ -4102,7 +4098,6 @@ public void testMisc31() { " postalCode:\n" + " pattern: \"[A-Z][0-9][A-Z] [0-9][A-Z][0-9]\"\n" + " PropertyNamesPattern:\n" + - " type: object\n" + " pattern: \"^[A-Za-z_][A-Za-z0-9_]*$\"\n"; SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); } @@ -5163,4 +5158,75 @@ public void testTicket4859() { SerializationMatchers.assertEqualsToYaml(openAPI, yaml); ModelConverters.reset(); } + + @Test(description = "test default value type") + public void testTicket4879() { + ModelConverters.reset(); + SwaggerConfiguration config = new SwaggerConfiguration().openAPI31(true); + Reader reader = new Reader(config); + + OpenAPI openAPI = reader.read(Ticket4879Resource.class); + String yaml = "openapi: 3.1.0\n" + + "paths:\n" + + " /test/test:\n" + + " put:\n" + + " operationId: test\n" + + " requestBody:\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " $ref: \"#/components/schemas/DefaultClass\"\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*': {}\n" + + " /test/testDefaultValueAnnotation:\n" + + " get:\n" + + " operationId: testDefault\n" + + " parameters:\n" + + " - name: myBool\n" + + " in: query\n" + + " schema:\n" + + " type: boolean\n" + + " default: true\n" + + " - name: myInt\n" + + " in: query\n" + + " schema:\n" + + " type: integer\n" + + " format: int32\n" + + " default: 1\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*': {}\n" + + " /test/testsize:\n" + + " get:\n" + + " operationId: testSize\n" + + " requestBody:\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " type: array\n" + + " items:\n" + + " type: string\n" + + " maxItems: 100\n" + + " minItems: 1\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*': {}\n" + + "components:\n" + + " schemas:\n" + + " DefaultClass:\n" + + " type: object\n" + + " properties:\n" + + " name:\n" + + " type: boolean\n" + + " default: true\n"; + SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); + ModelConverters.reset(); + } } diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/Ticket4879Resource.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/Ticket4879Resource.java new file mode 100644 index 0000000000..dd11a2d8d4 --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/Ticket4879Resource.java @@ -0,0 +1,35 @@ +package io.swagger.v3.jaxrs2.resources; + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.validation.constraints.Size; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import java.util.List; + +@Path("/test") +public class Ticket4879Resource { + + @PUT + @Path("/test") + public void test(DefaultClass defaultClass) {} + + @GET + @Path("/testDefaultValueAnnotation") + public void testDefault( + @DefaultValue(value = "true") @QueryParam(value = "myBool") Boolean myBool, + @DefaultValue(value = "1") @QueryParam(value = "myInt") Integer myInt) { + } + + @GET + @Path("/testsize") + public void testSize(@Size(min = 1, max = 100) List myList) {} + + public static class DefaultClass { + @Schema(defaultValue = "true") + public Boolean name; + } +} diff --git a/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/ArbitrarySchema.java b/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/ArbitrarySchema.java new file mode 100644 index 0000000000..eb315fda28 --- /dev/null +++ b/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/ArbitrarySchema.java @@ -0,0 +1,59 @@ +package io.swagger.v3.oas.models.media; + +import java.util.Objects; + +/** + * ObjectSchema + */ + +public class ArbitrarySchema extends Schema { + + public ArbitrarySchema() { + } + + @Override + public ArbitrarySchema type(String type) { + super.setType(type); + return this; + } + + @Override + public ArbitrarySchema example(Object example) { + if (example != null) { + super.setExample(example.toString()); + } else { + super.setExample(example); + } + return this; + } + + @Override + protected Object cast(Object value) { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ObjectSchema {\n"); + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/JsonSchema.java b/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/JsonSchema.java index f3ecf19870..d6957e1c54 100644 --- a/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/JsonSchema.java +++ b/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/JsonSchema.java @@ -2,16 +2,72 @@ import io.swagger.v3.oas.models.SpecVersion; +import java.text.NumberFormat; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + /** * JsonSchema */ public class JsonSchema extends Schema { + private static final Set NUMBER_TYPES = new HashSet<>(Arrays.asList("integer", "number")); + public JsonSchema (){ specVersion(SpecVersion.V31); } + private String resolveType() { + if (this.getTypes() != null) { + if (this.getTypes().size() == 1) { + String type = this.getTypes().iterator().next(); + if (NUMBER_TYPES.contains(type)) { + return "number"; + } + return type; + } + if (this.getTypes().contains("object")) { + return "object"; + } else if (this.getTypes().contains("string")) { + return "string"; + } else if (this.getTypes().contains("array")) { + return "array"; + } else if (this.getTypes().contains("integer") || this.getTypes().contains("number")) { + return "number"; + } else if (this.getTypes().contains("boolean")) { + return "boolean"; + } + return this.getTypes().iterator().next(); + } + return "null"; + } + + protected Object cast(Object value) { + if (value == null) { + return null; + } + if (value instanceof String) { + if (resolveType().equals("number")) { + try { + Number casted = NumberFormat.getInstance().parse(value.toString()); + if (Integer.MIN_VALUE <= casted.longValue() && casted.longValue() <= Integer.MAX_VALUE) { + return Integer.parseInt(value.toString()); + } else { + return Long.parseLong(value.toString()); + } + } catch (Exception e) { + return value; + } + } else if (resolveType().equals("boolean")) { + return Boolean.parseBoolean(value.toString()); + } + + } + return value; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/Schema.java b/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/Schema.java index e1acb9d55a..117f893225 100644 --- a/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/Schema.java +++ b/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/Schema.java @@ -47,6 +47,9 @@ public String toString() { public static final String SCHEMA_RESOLUTION_PROPERTY = "schema-resolution"; public static final String APPLY_SCHEMA_RESOLUTION_PROPERTY = "apply-schema-resolution"; + public static final String EXPLICIT_OBJECT_SCHEMA_PROPERTY = "explicit-object-schema"; + public static final String USE_ARBITRARY_SCHEMA_PROPERTY = "use-arbitrary-schema"; + public enum SchemaResolution { @JsonProperty("default") DEFAULT("default"), @@ -537,6 +540,16 @@ public boolean addType(String type) { return types.add(type); } + /** + * + * @since 2.2.30 (OpenAPI 3.1.0) + */ + @OpenAPI31 + public Schema typesItem(String type) { + addType(type); + return this; + } + /** * * @since 2.2.0 (OpenAPI 3.1.0) @@ -821,7 +834,7 @@ public Schema items(Schema items) { /** - * returns the name property from a from a Schema instance. Ignored in serialization. + * returns the name property from a Schema instance. Ignored in serialization. * * @return String name **/