Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,25 @@

public class ModelDeserializer extends JsonDeserializer<Schema> {

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) {
Expand All @@ -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);
Expand All @@ -58,47 +77,19 @@ public Schema deserialize(JsonParser jp, DeserializationContext ctxt)
return new Schema().booleanSchemaValue(node.booleanValue());
}

List<String> composed = Arrays.asList("allOf", "anyOf", "oneOf");
List<String> 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);
}
Expand All @@ -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 {
Expand All @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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" +
Expand Down
Loading