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 99baa6abec..dfd9e3ddab 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 @@ -3215,8 +3215,8 @@ protected void resolveSchemaMembers(Schema schema, Annotated a, Annotation[] ann } if (openapi31 && schemaAnnotation != null) { - for (String type : schemaAnnotation.types()) { - schema.addType(type); + if (schemaAnnotation.types().length > 0) { + schema.setTypes(new LinkedHashSet<>(Arrays.asList(schemaAnnotation.types()))); } BigDecimal exclusiveMaximumValue = resolveExclusiveMaximumValue(a, annotations, schemaAnnotation); if (exclusiveMaximumValue != null) { 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 0d3fcc8a4c..c268488511 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 @@ -678,12 +678,7 @@ public static Optional getSchemaFromAnnotation( } if (schema.types().length > 0) { - if (schema.types().length == 1) { - schemaObject.setType(schema.types()[0]); - } - for (String type : schema.types()) { - schemaObject.addType(type); - } + schemaObject.setTypes(new LinkedHashSet<>(Arrays.asList(schema.types()))); } if (StringUtils.isNotBlank(schema.$id())) { schemaObject.set$id(schema.$id()); 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 4ec3b94ecc..52e8aade4b 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 @@ -239,7 +239,7 @@ public Schema createProperty() { } @Override public Schema createProperty31() { - return new JsonSchema(); + return explicitObjectType == null || explicitObjectType ? new JsonSchema().typesItem("object") : new JsonSchema(); } }; 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 8f86ba7f5d..4e687886fd 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 @@ -240,7 +240,8 @@ public void testOAS31Fields() { " creditCard:\n" + " $ref: \"#/components/schemas/CreditCard\"\n" + " properties:\n" + - " extraObject: {}\n" + + " extraObject:\n" + + " type: object\n" + "MultipleBaseBean:\n" + " type: object\n" + " description: MultipleBaseBean\n" + diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/Oas31ObjectFieldTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/Oas31ObjectFieldTest.java new file mode 100644 index 0000000000..6495787fba --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/Oas31ObjectFieldTest.java @@ -0,0 +1,91 @@ +package io.swagger.v3.core.serialization; + +import io.swagger.v3.core.converter.ModelConverters; +import io.swagger.v3.oas.models.media.Schema; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.List; +import java.util.Map; + +public class Oas31ObjectFieldTest { + @Test(description = "Repro #4682: In OAS 3.1, raw Object property should not be rendered as an empty schema") + public void rawObjectPropertyShouldBeObjectSchema() { + Map schemas = ModelConverters.getInstance(true).readAll(PojoUsingObjectField.class); + Schema pojo = schemas.get("PojoUsingObjectField"); + Assert.assertNotNull(pojo, "PojoUsingObjectField schema should exist"); + + Schema myField = (Schema) pojo.getProperties().get("myField"); + Assert.assertNotNull(myField, "myField schema should exist"); + Assert.assertTrue(isObjectSchema(myField), "Expected raw Object property to be an object schema in OAS 3.1, but was: type=" + myField.getType() + ", types=" + myField.getTypes() + ", class=" + myField.getClass()); + } + + @Test(description = "OAS 3.1: List items schema should be object") + public void listOfObjectItemsShouldBeObjectSchema() { + Map schemas = ModelConverters.getInstance(true).readAll(PojoUsingListOfObject.class); + Schema pojo = schemas.get("PojoUsingListOfObject"); + Assert.assertNotNull(pojo, "PojoUsingListOfObject schema should exist"); + Schema itemsProp = (Schema) pojo.getProperties().get("items"); + Assert.assertNotNull(itemsProp, "items property schema should exist"); + Schema itemSchema = itemsProp.getItems(); + Assert.assertNotNull(itemSchema, "List items schema should exist"); + Assert.assertTrue(isObjectSchema(itemSchema), "Expected List items to be an object schema in OAS 3.1, but was: type=" + itemSchema.getType() + ", types=" + itemSchema.getTypes() + ", class=" + itemSchema.getClass()); + } + + @Test(description = "OAS 3.1: Map additionalProperties schema should be object") + public void mapOfObjectAdditionalPropertiesShouldBeObjectSchema() { + Map schemas = ModelConverters.getInstance(true).readAll(PojoUsingMapStringToObject.class); + Schema pojo = schemas.get("PojoUsingMapStringToObject"); + Assert.assertNotNull(pojo, "PojoUsingMapStringToObject schema should exist"); + Schema additionalProp = (Schema) pojo.getProperties().get("additional"); + Assert.assertNotNull(additionalProp, "additional property schema should exist"); + Schema valueSchema = (Schema) additionalProp.getAdditionalProperties(); + Assert.assertNotNull(valueSchema, "Map additionalProperties (value schema) should exist"); + Assert.assertTrue(isObjectSchema(valueSchema), "Expected Map additionalProperties to be an object schema in OAS 3.1, but was: type=" + valueSchema.getType() + ", types=" + valueSchema.getTypes() + ", class=" + valueSchema.getClass()); + } + + /** + * OAS 3.1 may represent types via Schema#getTypes() (JSON Schema style) rather than Schema#getType(). + */ + private static boolean isObjectSchema(Schema s) { + if (s == null) return false; + if ("object".equals(s.getType())) return true; + return s.getTypes() != null && s.getTypes().contains("object"); + } + + private static class PojoUsingObjectField { + private Object myField; + + public Object getMyField() { + return myField; + } + + public void setMyField(Object myField) { + this.myField = myField; + } + } + + private static class PojoUsingListOfObject { + private List items; + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + } + + private static class PojoUsingMapStringToObject { + private Map additional; + + public Map getAdditional() { + return additional; + } + + public void setAdditional(Map additional) { + this.additional = additional; + } + } +} \ No newline at end of file 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 c7e9de8e3a..daa846a377 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 @@ -3505,8 +3505,8 @@ public void testOas31Petstore() { " exclusiveMaximum: 4\n" + " foobar:\n" + " type:\n" + - " - integer\n" + " - string\n" + + " - integer\n" + " format: int32\n" + " Category:\n" + " type: object\n" + @@ -3532,7 +3532,6 @@ public void testOas31Petstore() { " exclusiveMaximum: 2\n" + " foobar:\n" + " type:\n" + - " - integer\n" + " - string\n" + " - object\n" + " format: int32\n" + @@ -3550,7 +3549,6 @@ public void testOas31Petstore() { " exclusiveMaximum: 2\n" + " foobar:\n" + " type:\n" + - " - integer\n" + " - string\n" + " - object\n" + " format: int32\n" + @@ -3597,6 +3595,7 @@ public void testOas31Petstore() { " name:\n" + " type: string\n" + " annotated:\n" + + " type: object\n" + " $ref: \"#/components/schemas/Category\"\n" + " description: child description\n" + " properties:\n" + @@ -3643,7 +3642,6 @@ public void test31RefSiblings() { " exclusiveMaximum: 2\n" + " foobar:\n" + " type:\n" + - " - integer\n" + " - string\n" + " - object\n" + " format: int32\n" + @@ -3651,6 +3649,7 @@ public void test31RefSiblings() { " type: object\n" + " properties:\n" + " annotated:\n" + + " type: object\n" + " $ref: \"#/components/schemas/SimpleCategory\"\n" + " description: child description\n" + " properties:\n" + @@ -4113,6 +4112,7 @@ public void testMisc31() { " type: object\n" + " properties:\n" + " country:\n" + + " type: object\n" + " const: United States\n" + " CreditCard:\n" + " type: object\n" + @@ -4123,11 +4123,13 @@ public void testMisc31() { " type: object\n" + " properties:\n" + " postalCode:\n" + + " type: object\n" + " pattern: \"[0-9]{5}(-[0-9]{4})?\"\n" + " PostalCodePattern:\n" + " type: object\n" + " properties:\n" + " postalCode:\n" + + " type: object\n" + " pattern: \"[A-Z][0-9][A-Z] [0-9][A-Z][0-9]\"\n" + " PropertyNamesPattern:\n" + " pattern: \"^[A-Za-z_][A-Za-z0-9_]*$\"\n"; diff --git a/modules/swagger-jaxrs2/src/test/resources/petstore/WebHookResource.yaml b/modules/swagger-jaxrs2/src/test/resources/petstore/WebHookResource.yaml index 62e397cade..25d3d5a349 100644 --- a/modules/swagger-jaxrs2/src/test/resources/petstore/WebHookResource.yaml +++ b/modules/swagger-jaxrs2/src/test/resources/petstore/WebHookResource.yaml @@ -24,9 +24,11 @@ webhooks: $comment: random comment $id: http://yourdomain.com/schemas/myschema.json dependentSchemas: - pet: {} + pet: + type: object patternProperties: - user: {} + user: + type: object webhook1: post: description: "subscribes a client to updates relevant to the requestor's account,\ diff --git a/modules/swagger-jaxrs2/src/test/resources/petstore/callbacks/ComplexCallback31Resource.yaml b/modules/swagger-jaxrs2/src/test/resources/petstore/callbacks/ComplexCallback31Resource.yaml index 79189d9464..524ccb0b35 100644 --- a/modules/swagger-jaxrs2/src/test/resources/petstore/callbacks/ComplexCallback31Resource.yaml +++ b/modules/swagger-jaxrs2/src/test/resources/petstore/callbacks/ComplexCallback31Resource.yaml @@ -32,9 +32,11 @@ paths: $comment: random comment $id: http://yourdomain.com/schemas/myschema.json dependentSchemas: - pet: { } + pet: + type: object patternProperties: - user: { } + user: + type: object testCallback2: http://www.url2.com: get: diff --git a/modules/swagger-jaxrs2/src/test/resources/petstore/parameters/Parameters31Resource.yaml b/modules/swagger-jaxrs2/src/test/resources/petstore/parameters/Parameters31Resource.yaml index e42efe9760..fa8377a630 100644 --- a/modules/swagger-jaxrs2/src/test/resources/petstore/parameters/Parameters31Resource.yaml +++ b/modules/swagger-jaxrs2/src/test/resources/petstore/parameters/Parameters31Resource.yaml @@ -96,7 +96,8 @@ paths: user: $ref: "#/components/schemas/User" properties: - extraObject: {} + extraObject: + type: object components: schemas: Category: diff --git a/modules/swagger-jaxrs2/src/test/resources/petstore/requestbody/RequestBody31Resource.yaml b/modules/swagger-jaxrs2/src/test/resources/petstore/requestbody/RequestBody31Resource.yaml index 2c5b00cc76..1ffc71629d 100644 --- a/modules/swagger-jaxrs2/src/test/resources/petstore/requestbody/RequestBody31Resource.yaml +++ b/modules/swagger-jaxrs2/src/test/resources/petstore/requestbody/RequestBody31Resource.yaml @@ -40,16 +40,20 @@ paths: - string - number - object - if: {} - then: {} - else: {} + if: + type: object + then: + type: object + else: + type: object $anchor: parameter $anchor $schema: parameter $schema description: User description example: User Description exclusiveMaximum: 100 exclusiveMinimum: 1 - unevaluatedProperties: {} + unevaluatedProperties: + type: object required: true responses: default: