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 d2f232e4be..f549663bba 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 @@ -33,6 +33,25 @@ public class ModelDeserializer extends JsonDeserializer { + private static final String TYPE = "type"; + private static final String OBJECT_TYPE = "object"; + private static final String ARRAY_TYPE = "array"; + private static final String STRING_TYPE = "string"; + private static final String NUMBER_TYPE = "number"; + private static final String INTEGER_TYPE = "integer"; + private static final String BOOLEAN_TYPE = "boolean"; + private static final String ALL_OF = "allOf"; + private static final String ANY_OF = "anyOf"; + private static final String ONE_OF = "oneOf"; + private static final String FORMAT = "format"; + private static final String DATE_FORMAT = "date"; + private static final String DATE_TIME_FORMAT = "date-time"; + private static final String EMAIL_FORMAT = "email"; + private static final String PASSWORD_FORMAT = "password"; + private static final String UUID_FORMAT = "uuid"; + private static final String ADDITIONAL_PROPERTIES = "additionalProperties"; + private static final String REF = "$ref"; + static Boolean useArbitrarySchema = false; static { if (System.getenv(Schema.USE_ARBITRARY_SCHEMA_PROPERTY) != null) { @@ -48,7 +67,7 @@ public Schema deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { JsonNode node = jp.getCodec().readTree(jp); - Schema schema = null; + Schema schema; if (openapi31) { schema = deserializeJsonSchema(node); @@ -58,47 +77,19 @@ public Schema deserialize(JsonParser jp, DeserializationContext ctxt) return new Schema().booleanSchemaValue(node.booleanValue()); } - List composed = Arrays.asList("allOf", "anyOf", "oneOf"); + List composed = Arrays.asList(ALL_OF, ANY_OF, ONE_OF); for (String field: composed) { if (node.get(field) != null) { return Json.mapper().convertValue(node, ComposedSchema.class); } } - JsonNode type = node.get("type"); - String format = node.get("format") == null ? "" : node.get("format").textValue(); + JsonNode typeNode = node.get(TYPE); - if (type != null && "array".equals(((TextNode) type).textValue())) { - schema = Json.mapper().convertValue(node, ArraySchema.class); - } else if (type != null) { - if (type.textValue().equals("integer")) { - schema = Json.mapper().convertValue(node, IntegerSchema.class); - if (StringUtils.isBlank(format)) { - schema.setFormat(null); - } - } else if (type.textValue().equals("number")) { - schema = Json.mapper().convertValue(node, NumberSchema.class); - } else if (type.textValue().equals("boolean")) { - schema = Json.mapper().convertValue(node, BooleanSchema.class); - } else if (type.textValue().equals("string")) { - if ("date".equals(format)) { - schema = Json.mapper().convertValue(node, DateSchema.class); - } else if ("date-time".equals(format)) { - schema = Json.mapper().convertValue(node, DateTimeSchema.class); - } else if ("email".equals(format)) { - schema = Json.mapper().convertValue(node, EmailSchema.class); - } else if ("password".equals(format)) { - schema = Json.mapper().convertValue(node, PasswordSchema.class); - } else if ("uuid".equals(format)) { - schema = Json.mapper().convertValue(node, UUIDSchema.class); - } else { - schema = Json.mapper().convertValue(node, StringSchema.class); - } - } else if (type.textValue().equals("object")) { - schema = deserializeArbitraryOrObjectSchema(node, true); - } - } else if (node.get("$ref") != null) { - schema = new Schema().$ref(node.get("$ref").asText()); + if (typeNode != null) { + schema = deserializeSchemaWithType(node, typeNode); + } else if (node.get(REF) != null) { + schema = new Schema().$ref(getRefAsString(node)); } else { schema = deserializeArbitraryOrObjectSchema(node, false); } @@ -107,12 +98,12 @@ public Schema deserialize(JsonParser jp, DeserializationContext ctxt) } private Schema deserializeArbitraryOrObjectSchema(JsonNode node, boolean alwaysObject) { - JsonNode additionalProperties = node.get("additionalProperties"); - Schema schema = null; + JsonNode additionalProperties = node.get(ADDITIONAL_PROPERTIES); + Schema schema; if (additionalProperties != null) { if (additionalProperties.isBoolean()) { Boolean additionalPropsBoolean = Json.mapper().convertValue(additionalProperties, Boolean.class); - ((ObjectNode)node).remove("additionalProperties"); + ((ObjectNode)node).remove(ADDITIONAL_PROPERTIES); if (additionalPropsBoolean) { schema = Json.mapper().convertValue(node, MapSchema.class); } else { @@ -121,7 +112,7 @@ private Schema deserializeArbitraryOrObjectSchema(JsonNode node, boolean alwaysO schema.setAdditionalProperties(additionalPropsBoolean); } else { Schema innerSchema = Json.mapper().convertValue(additionalProperties, Schema.class); - ((ObjectNode)node).remove("additionalProperties"); + ((ObjectNode)node).remove(ADDITIONAL_PROPERTIES); MapSchema ms = Json.mapper().convertValue(node, MapSchema.class); ms.setAdditionalProperties(innerSchema); schema = ms; @@ -143,16 +134,16 @@ private Schema deserializeJsonSchema(JsonNode node) { if (node.isBoolean()) { return new Schema().booleanSchemaValue(node.booleanValue()); } - JsonNode additionalProperties = node.get("additionalProperties"); - JsonNode type = node.get("type"); - Schema schema = null; + JsonNode additionalProperties = node.get(ADDITIONAL_PROPERTIES); + JsonNode type = node.get(TYPE); + Schema schema; if (type != null || additionalProperties != null) { if (type != null) { - ((ObjectNode)node).remove("type"); + ((ObjectNode)node).remove(TYPE); } if (additionalProperties != null) { - ((ObjectNode)node).remove("additionalProperties"); + ((ObjectNode)node).remove(ADDITIONAL_PROPERTIES); } schema = Json31.mapper().convertValue(node, JsonSchema.class); if (type instanceof TextNode) { @@ -183,4 +174,48 @@ private Schema deserializeJsonSchema(JsonNode node) { } return schema; } + + private Schema deserializeSchemaWithType(JsonNode node, JsonNode typeNode) { + Schema schema = null; + String type = ((TextNode) typeNode).textValue(); + String format = node.get(FORMAT) == null ? "" : getNodeAsString(node, FORMAT); + + if (type.equals(ARRAY_TYPE)) { + schema = Json.mapper().convertValue(node, ArraySchema.class); + } else if (type.equals(INTEGER_TYPE)) { + schema = Json.mapper().convertValue(node, IntegerSchema.class); + if (StringUtils.isBlank(format)) { + schema.setFormat(null); + } + } else if (type.equals(NUMBER_TYPE)) { + schema = Json.mapper().convertValue(node, NumberSchema.class); + } else if (type.equals(BOOLEAN_TYPE)) { + schema = Json.mapper().convertValue(node, BooleanSchema.class); + } else if (type.equals(STRING_TYPE)) { + if (DATE_FORMAT.equals(format)) { + schema = Json.mapper().convertValue(node, DateSchema.class); + } else if (DATE_TIME_FORMAT.equals(format)) { + schema = Json.mapper().convertValue(node, DateTimeSchema.class); + } else if (EMAIL_FORMAT.equals(format)) { + schema = Json.mapper().convertValue(node, EmailSchema.class); + } else if (PASSWORD_FORMAT.equals(format)) { + schema = Json.mapper().convertValue(node, PasswordSchema.class); + } else if (UUID_FORMAT.equals(format)) { + schema = Json.mapper().convertValue(node, UUIDSchema.class); + } else { + schema = Json.mapper().convertValue(node, StringSchema.class); + } + } else if (type.equals(OBJECT_TYPE)) { + schema = deserializeArbitraryOrObjectSchema(node, true); + } + return schema; + } + + private String getNodeAsString(JsonNode jsonNode, String field) { + return jsonNode.get(field).textValue(); + } + + private String getRefAsString(JsonNode jsonNode) { + return jsonNode.get(REF).asText(); + } } diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/deserialization/JsonDeserializationTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/deserialization/JsonDeserializationTest.java index 3b38d0a97b..b9f5729b22 100644 --- a/modules/swagger-core/src/test/java/io/swagger/v3/core/deserialization/JsonDeserializationTest.java +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/deserialization/JsonDeserializationTest.java @@ -19,6 +19,7 @@ import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; import org.apache.commons.io.FileUtils; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.File; @@ -85,6 +86,45 @@ public void testObjectProperty() throws IOException { assertEquals("objectProperty", result.getTitle()); } + @DataProvider(name = "nonTextSchemaTypes") + public Object[][] nonTextSchemaTypes() { + return new Object[][]{ + {"{\"type\":1}"}, + {"{\"type\":true}"}, + {"{\"type\":{}}"}, + {"{\"type\":[]}"} + }; + } + + @Test(dataProvider = "nonTextSchemaTypes") + public void deserializeSchemaWithNonTextTypeFailsWithLegacyCastException(String json) throws IOException { + try { + m.readValue(json, Schema.class); + } catch (RuntimeException e) { + assertTrue(e instanceof ClassCastException); + return; + } + + assertTrue(false, "Expected ClassCastException"); + } + + @DataProvider(name = "nonTextSchemaRef") + public Object[][] nonTextSchemaRef() { + return new Object[][]{ + {"{\"$ref\":1}", "#/components/schemas/1"}, + {"{\"$ref\":true}", "#/components/schemas/true"}, + {"{\"$ref\":{}}", "#/components/schemas/"}, + {"{\"$ref\":[]}", "#/components/schemas/"} + }; + } + + @Test(dataProvider = "nonTextSchemaRef") + public void deserializeSchemaWithNonTextRef(String json, String expectedRef) throws IOException { + Schema schema = m.readValue(json, Schema.class); + + assertEquals(schema.get$ref(), expectedRef); + } + @Test(description = "it should deserialize nested ObjectProperty(s)") public void testNestedObjectProperty() throws IOException { final String json = "{\n" +