All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.swagger.parser.util.SwaggerDeserializer Maven / Gradle / Ivy

There is a newer version: 2.0.0-rc1
Show newest version
package io.swagger.parser.util;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.*;
import io.swagger.models.*;
import io.swagger.models.auth.*;
import io.swagger.models.parameters.*;
import io.swagger.models.properties.ArrayProperty;
import io.swagger.models.properties.BooleanValueProperty;
import io.swagger.models.properties.Property;
import io.swagger.models.properties.PropertyBuilder;
import io.swagger.util.Json;

import java.math.BigDecimal;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static io.swagger.models.properties.PropertyBuilder.PropertyId.*;

public class SwaggerDeserializer {

    protected static Set ROOT_KEYS = new LinkedHashSet(Arrays.asList("swagger", "info", "host", "basePath", "schemes", "consumes", "produces", "paths", "definitions", "parameters", "responses", "securityDefinitions", "security", "tags", "externalDocs"));
    protected static Set EXTERNAL_DOCS_KEYS = new LinkedHashSet(Arrays.asList("description", "url"));
    protected static Set SCHEMA_KEYS = new LinkedHashSet(Arrays.asList("discriminator", "example", "$ref", "format", "title", "description", "default", "multipleOf", "maximum", "exclusiveMaximum", "minimum", "exclusiveMinimum", "maxLength", "minLength", "pattern", "maxItems", "minItems", "uniqueItems", "maxProperties", "minProperties", "required", "enum", "type", "items", "allOf", "properties", "additionalProperties", "xml", "readOnly", "allowEmptyValue"));
    protected static Set INFO_KEYS = new LinkedHashSet(Arrays.asList("title", "description", "termsOfService", "contact", "license", "version"));
    protected static Set TAG_KEYS = new LinkedHashSet(Arrays.asList("description", "name", "externalDocs"));
    protected static Set RESPONSE_KEYS = new LinkedHashSet(Arrays.asList("description", "schema", "headers", "examples"));
    protected static Set CONTACT_KEYS = new LinkedHashSet(Arrays.asList("name", "url", "email"));
    protected static Set LICENSE_KEYS = new LinkedHashSet(Arrays.asList("name", "url"));
    protected static Set REF_MODEL_KEYS = new LinkedHashSet(Arrays.asList("$ref"));
    protected static Set PATH_KEYS = new LinkedHashSet(Arrays.asList("$ref", "get", "put", "post", "delete", "head", "patch", "options", "parameters"));
    protected static Set OPERATION_KEYS = new LinkedHashSet(Arrays.asList("scheme", "tags", "summary", "description", "externalDocs", "operationId", "consumes", "produces", "parameters", "responses", "schemes", "deprecated", "security"));
    protected static Set PARAMETER_KEYS = new LinkedHashSet(Arrays.asList("name", "in", "description", "required", "type", "format", "allowEmptyValue", "items", "collectionFormat", "default", "maximum", "exclusiveMaximum", "minimum", "exclusiveMinimum", "maxLength", "minLength", "pattern", "maxItems", "minItems", "uniqueItems", "enum", "multipleOf", "readOnly", "allowEmptyValue"));
    protected static Set BODY_PARAMETER_KEYS = new LinkedHashSet(Arrays.asList("name", "in", "description", "required", "schema"));
    protected static Set SECURITY_SCHEME_KEYS = new LinkedHashSet(Arrays.asList("type", "name", "in", "description", "flow", "authorizationUrl", "tokenUrl", "scopes"));

    private final Set operationIDs = new HashSet<>();

    public SwaggerDeserializationResult deserialize(JsonNode rootNode) {
        SwaggerDeserializationResult result = new SwaggerDeserializationResult();
        ParseResult rootParse = new ParseResult();

        Swagger swagger = parseRoot(rootNode, rootParse);
        result.setSwagger(swagger);
        result.setMessages(rootParse.getMessages());
        return result;
    }

    public Swagger parseRoot(JsonNode node, ParseResult result) {
        String location = "";
        Swagger swagger = new Swagger();
        if (node.getNodeType().equals(JsonNodeType.OBJECT)) {
            ObjectNode on = (ObjectNode) node;
            Iterator it = null;

            // required
            String value = getString("swagger", on, true, location, result);
            swagger.setSwagger(value);

            ObjectNode obj = getObject("info", on, true, "", result);
            if (obj != null) {
                Info info = info(obj, "info", result);
                swagger.info(info);
            }

            // optional
            value = getString("host", on, false, location, result);
            swagger.setHost(value);

            value = getString("basePath", on, false, location, result);
            swagger.setBasePath(value);

            ArrayNode array = getArray("schemes", on, false, location, result);
            if (array != null) {
                it = array.iterator();
                while (it.hasNext()) {
                    JsonNode n = it.next();
                    String s = getString(n, location + ".schemes", result);
                    if (s != null) {
                        Scheme scheme = Scheme.forValue(s);
                        if (scheme != null) {
                            swagger.scheme(scheme);
                        }
                    }
                }
            }

            array = getArray("consumes", on, false, location, result);
            if (array != null) {
                it = array.iterator();
                while (it.hasNext()) {
                    JsonNode n = it.next();
                    String s = getString(n, location + ".consumes", result);
                    if (s != null) {
                        swagger.consumes(s);
                    }
                }
            }

            array = getArray("produces", on, false, location, result);
            if (array != null) {
                it = array.iterator();
                while (it.hasNext()) {
                    JsonNode n = it.next();
                    String s = getString(n, location + ".produces", result);
                    if (s != null) {
                        swagger.produces(s);
                    }
                }
            }

            obj = getObject("paths", on, true, location, result);
            Map paths = paths(obj, "paths", result);
            swagger.paths(paths);

            obj = getObject("definitions", on, false, location, result);
            Map definitions = definitions(obj, "definitions", result);
            swagger.setDefinitions(definitions);

            obj = getObject("parameters", on, false, location, result);
            // TODO: parse

            if (obj != null) {
                Map parameters = new LinkedHashMap<>();
                Set keys = getKeys(obj);
                for (String key : keys) {
                    JsonNode paramNode = obj.get(key);
                    if (paramNode instanceof ObjectNode) {
                        Parameter parameter = this.parameter((ObjectNode) paramNode, location, result);
                        if ("path".equalsIgnoreCase(parameter.getIn()) && !parameter.getRequired()) {
                            result.warning(location + ".'" + parameter.getName() + "'", " For path parameter '" + parameter.getName() + "' the required value should be true");
                        }
                        parameters.put(key, parameter);
                    }
                }
                swagger.setParameters(parameters);
            }

            obj = getObject("responses", on, false, location, result);
            Responses responses = responses(obj, "responses", result);
            swagger.responses(responses);

            obj = getObject("securityDefinitions", on, false, location, result);
            Map securityDefinitions = securityDefinitions(obj, "securityDefinitions", result);
            swagger.setSecurityDefinitions(securityDefinitions);

            array = getArray("security", on, false, location, result);
            List security = securityRequirements(array, "security", result);
            swagger.setSecurity(security);

            array = getArray("tags", on, false, location, result);
            List tags = tags(array, "tags", result);
            swagger.tags(tags);

            obj = getObject("externalDocs", on, false, location, result);
            ExternalDocs docs = externalDocs(obj, "externalDocs", result);
            swagger.externalDocs(docs);

            // extra keys
            Set keys = getKeys(on);
            for (String key : keys) {
                if (key.startsWith("x-")) {
                    swagger.vendorExtension(key, extension(on.get(key)));
                } else if (!ROOT_KEYS.contains(key)) {
                    result.extra(location, key, node.get(key));
                }
            }
        } else {
            result.invalidType("", "", "object", node);
            result.invalid();
            return null;
        }
        return swagger;
    }


    public Map paths(ObjectNode obj, String location, ParseResult result) {
        Map output = new LinkedHashMap<>();
        if(obj == null) {
            return null;
        }

        Set pathKeys = getKeys(obj);
        for (String pathName : pathKeys) {
            JsonNode pathValue = obj.get(pathName);
            if (pathName.startsWith("x-")) {
                result.unsupported(location, pathName, pathValue);
            } else {
                if (!pathValue.getNodeType().equals(JsonNodeType.OBJECT)) {
                    result.invalidType(location, pathName, "object", pathValue);
                } else {
                    ObjectNode path = (ObjectNode) pathValue;
                    Path pathObj = path(path, location + ".'" + pathName + "'", result);
                    List eachPart = new ArrayList<>();
                    Matcher m = Pattern.compile("\\{(.+?)\\}").matcher(pathName);
                    while (m.find()) {
                        eachPart.add(m.group());
                    }
                    List operationsInAPath = getAllOperationsInAPath(pathObj);
                    for (Operation operation : operationsInAPath) {
                        for (Parameter parameter : operation.getParameters()) {
                            if ("path".equalsIgnoreCase(parameter.getIn()) && !parameter.getRequired()) {
                                result.warning(location + ".'" + parameter.getName() + "'", " For path parameter '" + parameter.getName() + "' the required value should be true");
                            }
                        }
                    }
                    for (String part : eachPart) {
                        String pathParam = part.substring(1, part.length() - 1);
                        boolean definedInPathLevel = isPathParamDefined(pathParam, pathObj.getParameters());
                        if (definedInPathLevel) {
                            continue;
                        }
                        for (Operation operation : operationsInAPath) {
                            if (!isPathParamDefined(pathParam, operation.getParameters())) {
                                result.warning(location + ".'" + pathName + "'", " Declared path parameter " + pathParam + " needs to be defined as a path parameter in path or operation level");
                                break;
                            }
                        }
                    }
                    output.put(pathName, pathObj);
                }
            }
        }
        return output;
    }

    private boolean isPathParamDefined(String pathParam, List parameters) {
        if (parameters == null || parameters.isEmpty()) {
            return false;
        } else {
            for (Parameter parameter : parameters) {
                if (parameter instanceof RefParameter || (pathParam.equals(parameter.getName()) && "path".equals(parameter.getIn()))) {
                    if (!parameter.getRequired()) {

                    }
                    return true;
                }
            }
        }
        return false;
    }

    private void addToOperationsList(List operationsList, Operation operation) {
        if (operation == null) {
            return;
        }
        operationsList.add(operation);
    }

    private List getAllOperationsInAPath(Path pathObj) {
        List operations = new ArrayList<>();
        addToOperationsList(operations, pathObj.getGet());
        addToOperationsList(operations, pathObj.getPut());
        addToOperationsList(operations, pathObj.getPost());
        addToOperationsList(operations, pathObj.getPatch());
        addToOperationsList(operations, pathObj.getDelete());
        addToOperationsList(operations, pathObj.getOptions());
        addToOperationsList(operations, pathObj.getHead());
        return operations;
    }

    public Path path(ObjectNode obj, String location, ParseResult result) {
        if (obj.get("$ref") != null) {
            JsonNode ref = obj.get("$ref");
            if (ref.getNodeType().equals(JsonNodeType.STRING)) {

                return pathRef((TextNode) ref, location, result);
            } else if (ref.getNodeType().equals(JsonNodeType.OBJECT)) {
                ObjectNode on = (ObjectNode) ref;

                // extra keys
                Set keys = getKeys(on);
                for (String key : keys) {
                    result.extra(location, key, on.get(key));
                }
            }
            return null;
        }
        Path path = new Path();

        ArrayNode parameters = getArray("parameters", obj, false, location, result);
        path.setParameters(parameters(parameters, location, result));

        ObjectNode on = getObject("get", obj, false, location, result);
        if (on != null) {
            Operation op = operation(on, location + "(get)", result);
            if (op != null) {
                path.setGet(op);
            }
        }
        on = getObject("put", obj, false, location, result);
        if (on != null) {
            Operation op = operation(on, location + "(put)", result);
            if (op != null) {
                path.setPut(op);
            }
        }
        on = getObject("post", obj, false, location, result);
        if (on != null) {
            Operation op = operation(on, location + "(post)", result);
            if (op != null) {
                path.setPost(op);
            }
        }
        on = getObject("head", obj, false, location, result);
        if (on != null) {
            Operation op = operation(on, location + "(head)", result);
            if (op != null) {
                path.setHead(op);
            }
        }
        on = getObject("delete", obj, false, location, result);
        if (on != null) {
            Operation op = operation(on, location + "(delete)", result);
            if (op != null) {
                path.setDelete(op);
            }
        }
        on = getObject("patch", obj, false, location, result);
        if (on != null) {
            Operation op = operation(on, location + "(patch)", result);
            if (op != null) {
                path.setPatch(op);
            }
        }
        on = getObject("options", obj, false, location, result);
        if (on != null) {
            Operation op = operation(on, location + "(options)", result);
            if (op != null) {
                path.setOptions(op);
            }
        }

        // extra keys
        Set keys = getKeys(obj);
        for (String key : keys) {
            if (key.startsWith("x-")) {
                path.setVendorExtension(key, extension(obj.get(key)));
            } else if (!PATH_KEYS.contains(key)) {
                result.extra(location, key, obj.get(key));
            }
        }
        return path;
    }

    public Operation operation(ObjectNode obj, String location, ParseResult result) {
        if (obj == null) {
            return null;
        }
        Operation output = new Operation();
        ArrayNode array = getArray("tags", obj, false, location, result);
        List tags = tagStrings(array, location, result);
        if (tags != null) {
            output.tags(tags);
        }
        String value = getString("summary", obj, false, location, result);
        output.summary(value);

        value = getString("description", obj, false, location, result);
        output.description(value);

        ObjectNode externalDocs = getObject("externalDocs", obj, false, location, result);
        ExternalDocs docs = externalDocs(externalDocs, location, result);
        output.setExternalDocs(docs);

        value = getString("operationId", obj, false, location, result, operationIDs);
        output.operationId(value);

        array = getArray("consumes", obj, false, location, result);
        if (array != null) {
            if (array.size() == 0) {
                output.consumes(Collections.emptyList());
            } else {
                Iterator it = array.iterator();
                while (it.hasNext()) {
                    JsonNode n = it.next();
                    String s = getString(n, location + ".consumes", result);
                    if (s != null) {
                        output.consumes(s);
                    }
                }
            }
        }
        array = getArray("produces", obj, false, location, result);
        if (array != null) {
            if (array.size() == 0) {
                output.produces(Collections.emptyList());
            } else {
                Iterator it = array.iterator();
                while (it.hasNext()) {
                    JsonNode n = it.next();
                    String s = getString(n, location + ".produces", result);
                    if (s != null) {
                        output.produces(s);
                    }
                }
            }
        }
        ArrayNode parameters = getArray("parameters", obj, false, location, result);
        output.setParameters(parameters(parameters, location, result));

        ObjectNode responses = getObject("responses", obj, true, location+".responses", result);
        Responses responsesObject = responses(responses, location+".responses", result);
        if(responsesObject != null && responsesObject.size() == 0) {

            result.missing(location, "responses");
        }
        output.setResponsesObject(responsesObject);

        array = getArray("schemes", obj, false, location, result);
        if (array != null) {
            Iterator it = array.iterator();
            while (it.hasNext()) {
                JsonNode n = it.next();

                String s = getString(n, location + ".schemes", result);
                if (s != null) {
                    Scheme scheme = Scheme.forValue(s);
                    if (scheme != null) {
                        output.scheme(scheme);
                    }
                }
            }
        }
        Boolean deprecated = getBoolean("deprecated", obj, false, location, result);
        if (deprecated != null) {
            output.setDeprecated(deprecated);
        }
        array = getArray("security", obj, false, location, result);
        List security = securityRequirements(array, location, result);
        if (security != null) {
            List>> ss = new ArrayList<>();
            for(SecurityRequirement s : security) {
                if(s.getRequirements() != null) {
                    if (s.getRequirements().size() > 0) {
                        ss.add(s.getRequirements());
                    } else {
                        ss.add(Collections.>emptyMap());
                    }
                }
            }
            output.setSecurity(ss);
        }

        // extra keys
        Set keys = getKeys(obj);
        for (String key : keys) {
            if (key.startsWith("x-")) {
                output.setVendorExtension(key, extension(obj.get(key)));
            } else if (!OPERATION_KEYS.contains(key)) {
                result.extra(location, key, obj.get(key));
            }
        }

        return output;
    }

    public Boolean getBoolean(String key, ObjectNode node, boolean required, String location, ParseResult result) {
        Boolean value = null;
        JsonNode v = node.get(key);
        if (node == null || v == null) {
            if (required) {
                result.missing(location, key);
                result.invalid();
            }
        } else {
            if (v.getNodeType().equals(JsonNodeType.BOOLEAN)) {
                value = v.asBoolean();
            } else if (v.getNodeType().equals(JsonNodeType.STRING)) {
                String stringValue = v.textValue();
                return Boolean.parseBoolean(stringValue);
            }
        }
        return value;
    }

    public List parameters(ArrayNode obj, String location, ParseResult result) {
        List output = new ArrayList();
        if (obj == null) {
            return output;
        }
        for (JsonNode item : obj) {
            if (item.getNodeType().equals(JsonNodeType.OBJECT)) {
                Parameter param = parameter((ObjectNode) item, location, result);
                if (param != null) {
                    output.add(param);
                }
            }
        }
        return output;
    }


    public Parameter parameter(ObjectNode obj, String location, ParseResult result) {
        if (obj == null) {
            return null;
        }

        Parameter output = null;
        JsonNode ref = obj.get("$ref");
        if (ref != null) {
            if (ref.getNodeType().equals(JsonNodeType.STRING)) {
                return refParameter((TextNode) ref, location, result);
            } else {
                result.invalidType(location, "$ref", "string", obj);
                return null;
            }
        }

        String l = null;
        JsonNode ln = obj.get("name");
        if (ln != null) {
            l = ln.asText();
        } else {
            l = "['unknown']";
        }
        location += ".[" + l + "]";

        String value = getString("in", obj, true, location, result);
        if (value != null) {
            String type = getString("type", obj, false, location, result);
            String format = getString("format", obj, false, location, result);
            AbstractSerializableParameter sp = null;

            if ("query".equals(value)) {
                sp = new QueryParameter();
            } else if ("header".equals(value)) {
                sp = new HeaderParameter();
            } else if ("path".equals(value)) {
                sp = new PathParameter();
            } else if ("formData".equals(value)) {
                sp = new FormParameter();
            }

            if (sp != null) {
                // type is mandatory when sp != null
                String paramType = getString("type", obj, true, location, result);
                Map map = new LinkedHashMap();

                map.put(TYPE, type);
                map.put(FORMAT, format);
                String defaultValue = parameterDefault(obj, paramType, location, result);
                map.put(DEFAULT, defaultValue);
                sp.setDefault(defaultValue);

                BigDecimal bd = getBigDecimal("maximum", obj, false, location, result);
                if (bd != null) {
                    map.put(MAXIMUM, bd);
                    sp.setMaximum(bd);
                }

                Boolean bl = getBoolean("exclusiveMaximum", obj, false, location, result);
                if (bl != null) {
                    map.put(EXCLUSIVE_MAXIMUM, bl);
                    sp.setExclusiveMaximum(bl);
                }

                bd = getBigDecimal("minimum", obj, false, location, result);
                if (bd != null) {
                    map.put(MINIMUM, bd);
                    sp.setMinimum(bd);
                }

                bl = getBoolean("exclusiveMinimum", obj, false, location, result);
                if (bl != null) {
                    map.put(EXCLUSIVE_MINIMUM, bl);
                    sp.setExclusiveMinimum(bl);
                }

                Integer maxLength = getInteger("maxLength", obj, false, location, result);
                map.put(MAX_LENGTH, maxLength);
                sp.setMaxLength(maxLength);

                Integer minLength = getInteger("minLength", obj, false, location, result);
                map.put(MIN_LENGTH, minLength);
                sp.setMinLength(minLength);

                String pat = getString("pattern", obj, false, location, result);
                map.put(PATTERN, pat);
                sp.setPattern(pat);

                Integer iv = getInteger("maxItems", obj, false, location, result);
                map.put(MAX_ITEMS, iv);
                sp.setMaxItems(iv);

                iv = getInteger("minItems", obj, false, location, result);
                map.put(MIN_ITEMS, iv);
                sp.setMinItems(iv);

                iv = getInteger("minLength", obj, false, location, result);
                map.put(MIN_LENGTH, iv);
                sp.setMinLength(iv);

                iv = getInteger("maxLength", obj, false, location, result);
                map.put(MAX_LENGTH, iv);
                sp.setMaxLength(iv);

                bd = getBigDecimal("multipleOf", obj, false, location, result);
                if (bd != null) {
                    map.put(MULTIPLE_OF, bd);
                    sp.setMultipleOf(bd.doubleValue());
                }

                Boolean uniqueItems = getBoolean("uniqueItems", obj, false, location, result);
                map.put(UNIQUE_ITEMS, uniqueItems);
                sp.setUniqueItems(uniqueItems);

                ArrayNode an = getArray("enum", obj, false, location, result);
                if (an != null) {
                    List _enum = new ArrayList();
                    for (JsonNode n : an) {
                        if (n.isValueNode()) {
                            _enum.add(n.asText());
                        } else {
                            result.invalidType(location, "enum", "value", n);
                        }
                    }
                    sp.setEnum(_enum);
                    map.put(ENUM, _enum);
                }

                bl = getBoolean("readOnly", obj, false, location, result);
                if (bl != null) {
                    map.put(READ_ONLY, bl);
                    sp.setReadOnly(bl);
                }

                bl = getBoolean("allowEmptyValue", obj, false, location, result);
                if (bl != null) {
                    map.put(ALLOW_EMPTY_VALUE, bl);
                    sp.setAllowEmptyValue(bl);
                }

                Property prop = PropertyBuilder.build(type, format, map);

                if (prop != null) {
                    sp.setProperty(prop);
                    ObjectNode items = getObject("items", obj, false, location, result);
                    if (items != null) {
                        Property inner = schema(null, items, location, result);
                        sp.setItems(inner);
                    }
                }

                Set keys = getKeys(obj);
                for (String key : keys) {
                    if (key.startsWith("x-")) {
                        sp.setVendorExtension(key, extension(obj.get(key)));
                    } else if (!PARAMETER_KEYS.contains(key)) {
                        result.extra(location, key, obj.get(key));
                    }
                }

                String collectionFormat = getString("collectionFormat", obj, false, location, result);
                sp.setCollectionFormat(collectionFormat);

                output = sp;
            } else if ("body".equals(value)) {
                BodyParameter bp = new BodyParameter();

                JsonNode node = obj.get("schema");
                if (node != null && node instanceof ObjectNode) {
                    bp.setSchema(this.definition((ObjectNode) node, location, result));
                }

                // examples
                ObjectNode examplesNode = getObject("examples", obj, false, location, result);
                if (examplesNode != null) {
                    Map examples = Json.mapper().convertValue(examplesNode, Json.mapper().getTypeFactory().constructMapType(Map.class, String.class, Object.class));
                    bp.setExamples(examples);
                }

                // pattern
                String pat = getString("pattern", obj, false, location, result);
                if (pat != null) {
                    bp.setPattern(pat);
                }

                // readOnly
                Boolean bl = getBoolean("readOnly", obj, false, location, result);
                if (bl != null) {
                    bp.setReadOnly(bl);
                }

                // vendor extensions
                Set keys = getKeys(obj);
                for (String key : keys) {
                    if (key.startsWith("x-")) {
                        bp.setVendorExtension(key, extension(obj.get(key)));
                    } else if (!BODY_PARAMETER_KEYS.contains(key)) {
                        result.extra(location, key, obj.get(key));
                    }
                }
                output = bp;

//                output = Json.mapper().convertValue(obj, Parameter.class);
            }
            if (output != null) {
                value = getString("name", obj, true, location, result);
                output.setName(value);

                value = getString("description", obj, false, location, result);
                output.setDescription(value);

                Boolean required = getBoolean("required", obj, false, location, result);
                if (required != null) {
                    output.setRequired(required);
                }
            }
        }

        return output;
    }

    private String parameterDefault(ObjectNode node, String type, String location, ParseResult result) {
        String key = "default";
        if (type != null && type.equals("array")) {
            ArrayNode array = getArray(key, node, false, location, result);
            return array != null ? array.toString() : null;
        }
        return getString(key, node, false, location, result);
    }

    private Property schema(Map schemaItems, JsonNode obj, String location, ParseResult result) {
        return Json.mapper().convertValue(obj, Property.class);
    }

    public RefParameter refParameter(TextNode obj, String location, ParseResult result) {
        return new RefParameter(obj.asText());
    }

    public RefResponse refResponse(TextNode obj, String location, ParseResult result) {
        return new RefResponse(obj.asText());
    }

    public Path pathRef(TextNode ref, String location, ParseResult result) {
        RefPath output = new RefPath();

        output.set$ref(ref.textValue());
        return output;
    }

    public Map definitions(ObjectNode node, String location, ParseResult result) {
        if (node == null)
            return null;
        Set schemas = getKeys(node);
        Map output = new LinkedHashMap();

        for (String schemaName : schemas) {
            JsonNode schema = node.get(schemaName);
            if (schema.getNodeType().equals(JsonNodeType.OBJECT)) {
                Model model = definition((ObjectNode) schema, location + "." + schemaName, result);
                if (model != null) {
                    output.put(schemaName, model);
                }
            } else {
                result.invalidType(location, schemaName, "object", schema);
            }
        }
        return output;
    }

    public Model definition(ObjectNode node, String location, ParseResult result) {
        if (result == null) {
            // TODO, this shouldn't happen, but the `ResolverCache#loadRef` method is passing null
            result = new ParseResult();
        }
        if (node == null) {
            result.missing(location, "empty schema");
            return null;
        }
        if (node.isBoolean()) {
            return new BooleanValueModel(node.asBoolean());
        }
        if (node.get("$ref") != null) {
            return refModel(node, location, result);
        }
        if (node.get("allOf") != null) {
            return allOfModel(node, location, result);
        }
        Model model = null;
        String value = null;

        String type = getString("type", node, false, location, result);
        JsonNode itemsKey = node.get("items");
        Model m = new ModelImpl();
        if ("array".equals(type) || itemsKey != null) {
            ArrayModel am = new ArrayModel();
            ObjectNode propertyNode = getObject("properties", node, false, location, result);
            Map properties = properties(propertyNode, location, result);
            am.setProperties(properties);

            ObjectNode itemsNode = getObject("items", node, false, location, result);
            Property items = property(itemsNode, location, result);
            if (items != null) {
                am.items(items);
            }

            Integer maxItems = getInteger("maxItems", node, false, location, result);
            am.setMaxItems(maxItems);

            Integer minItems = getInteger("minItems", node, false, location, result);
            am.setMinItems(minItems);

            Boolean uniqueItems = getBoolean("uniqueItems", node, false, location, result);
            if (uniqueItems != null) {
                am.setUniqueItems(uniqueItems);
            }

            // add xml specific information if available
            JsonNode xml = node.get("xml");
            if (xml != null) {
                am.setXml(Json.mapper().convertValue(xml, Xml.class));
            }

            // extra keys
            Set keys = getKeys(node);
            for (String key : keys) {
                if (key.startsWith("x-")) {
                    am.setVendorExtension(key, extension(node.get(key)));
                }
            }

            model = am;
        } else {
            ModelImpl impl = new ModelImpl();
            impl.setType(type);

            JsonNode ap = node.get("additionalProperties");
            if (ap != null && ap.getNodeType().equals(JsonNodeType.OBJECT)) {
                impl.setAdditionalProperties(Json.mapper().convertValue(ap, Property.class));
            } else if (ap != null && ap.isBoolean()) {
                impl.setAdditionalProperties(new BooleanValueProperty(ap.asBoolean()));
            }

            value = getString("default", node, false, location, result);
            impl.setDefaultValue(value);

            value = getString("format", node, false, location, result);
            impl.setFormat(value);

            value = getString("discriminator", node, false, location, result);
            impl.setDiscriminator(value);

            Boolean bp = getBoolean("uniqueItems", node, false, location, result);
            if (bp != null) {
                impl.setUniqueItems(bp);
            }


            BigDecimal bd = getBigDecimal("minimum", node, false, location, result);
            impl.setMinimum(bd);

            bd = getBigDecimal("maximum", node, false, location, result);
            impl.setMaximum(bd);

            bp = getBoolean("exclusiveMaximum", node, false, location, result);
            if (bp != null) {
                impl.setExclusiveMaximum(bp);
            }

            bp = getBoolean("exclusiveMinimum", node, false, location, result);
            if (bp != null) {
                impl.setExclusiveMinimum(bp);
            }

            value = getString("pattern", node, false, location, result);
            impl.setPattern(value);

            BigDecimal maximum = getBigDecimal("maximum", node, false, location, result);
            if (maximum != null) {
                impl.maximum(maximum);
            }

            BigDecimal minimum = getBigDecimal("minimum", node, false, location, result);
            if (minimum != null) {
                impl.minimum(minimum);
            }

            Integer minLength = getInteger("minLength", node, false, location, result);
            if (minLength != null) {
                impl.setMinLength(minLength);
            }

            Integer maxLength = getInteger("maxLength", node, false, location, result);
            if (maxLength != null) {
                impl.setMaxLength(maxLength);
            }

            BigDecimal multipleOf = getBigDecimal("multipleOf", node, false, location, result);
            if (multipleOf != null) {
                impl.setMultipleOf(multipleOf);
            }


            ap = node.get("enum");
            if (ap != null) {
                ArrayNode arrayNode = getArray("enum", node, false, location, result);
                if (arrayNode != null) {
                    for (JsonNode n : arrayNode) {
                        if (n.isValueNode()) {
                            impl._enum(n.asText());
                        } else {
                            result.invalidType(location, "enum", "value", n);
                        }
                    }
                }
            }

            JsonNode xml = node.get("xml");
            if (xml != null) {
                impl.setXml(Json.mapper().convertValue(xml, Xml.class));
            }

            ObjectNode externalDocs = getObject("externalDocs", node, false, location, result);
            ExternalDocs docs = externalDocs(externalDocs, location, result);
            impl.setExternalDocs(docs);

            addProperties(location, node, result, impl);

            // need to set properties first
            ArrayNode required = getArray("required", node, false, location, result);
            if (required != null) {
                List requiredProperties = new ArrayList();
                for (JsonNode n : required) {
                    if (n.getNodeType().equals(JsonNodeType.STRING)) {
                        requiredProperties.add(((TextNode) n).textValue());
                    } else {
                        result.invalidType(location, "required", "string", n);
                    }
                }
                if (requiredProperties.size() > 0) {
                    impl.setRequired(requiredProperties);
                }
            }

            // extra keys
            Set keys = getKeys(node);
            for (String key : keys) {
                if (key.startsWith("x-")) {
                    impl.setVendorExtension(key, extension(node.get(key)));
                } else if (!SCHEMA_KEYS.contains(key)) {
                    result.extra(location, key, node.get(key));
                }
            }
            model = impl;
        }
        JsonNode exampleNode = node.get("example");
        if (exampleNode != null) {
            Object example = Json.mapper().convertValue(exampleNode, Object.class);
            model.setExample(example);
        }

        if (model != null) {
            value = getString("description", node, false, location, result);
            model.setDescription(value);

            value = getString("title", node, false, location, result);
            model.setTitle(value);
        }

        return model;
    }

    public Object extension(JsonNode jsonNode) {
        return Json.mapper().convertValue(jsonNode, Object.class);
    }

    public Model allOfModel(ObjectNode node, String location, ParseResult result) {
        JsonNode sub = node.get("$ref");
        JsonNode allOf = node.get("allOf");

        if (sub != null) {
            if (sub.getNodeType().equals(JsonNodeType.OBJECT)) {
                return refModel((ObjectNode) sub, location, result);
            } else {
                result.invalidType(location, "$ref", "object", sub);
                return null;
            }
        } else if (allOf != null) {
            ComposedModel model = null;
            if (allOf.getNodeType().equals(JsonNodeType.ARRAY)) {
                model = new ComposedModel();

                int pos = 0;
                for (JsonNode part : allOf) {
                    if (part.getNodeType().equals(JsonNodeType.OBJECT)) {
                        Model segment = definition((ObjectNode) part, location, result);
                        if (segment != null) {
                            model.getAllOf().add(segment);
                        }
                    } else {
                        result.invalidType(location, "allOf[" + pos + "]", "object", part);
                    }
                    pos++;
                }

                Model child = null;
                List interfaces = new ArrayList();
                for (Model m : model.getAllOf()) {
                    if (m instanceof RefModel) {
                        interfaces.add((RefModel) m);
                    } else if (m instanceof ModelImpl) {
                        // NOTE: since ComposedModel.child allows only one inline child schema, the last one 'wins'.
                        child = m;
                    }
                }
                model.setInterfaces(interfaces);

                if (child != null) {
                    model.setChild(child);
                }

            } else {
                result.invalidType(location, "allOf", "array", allOf);
            }

            // extra keys
            Set keys = getKeys(node);
            for (String key : keys) {
                if (key.startsWith("x-")) {
                    model.setVendorExtension(key, extension(node.get(key)));
                } else if (!SCHEMA_KEYS.contains(key)) {
                    result.extra(location, key, node.get(key));
                } else {
                    String value = getString("title", node, false, location, result);
                    model.setTitle(value);

                    value = getString("description", node, false, location, result);
                    model.setDescription(value);
                }
            }

            addProperties(location, node, result, model);

            // need to set properties first
            ArrayNode required = getArray("required", node, false, location, result);
            if (required != null) {
                List requiredProperties = new ArrayList();
                for (JsonNode n : required) {
                    if (n.getNodeType().equals(JsonNodeType.STRING)) {
                        requiredProperties.add(((TextNode) n).textValue());
                    } else {
                        result.invalidType(location, "required", "string", n);
                    }
                }
                if (requiredProperties.size() > 0) {
                    model.setRequired(requiredProperties);
                }
            }

            return model;
        }
        return null;
    }

    private void addProperties(String location, ObjectNode node, ParseResult result, AbstractModel model) {
        ObjectNode properties = getObject("properties", node, false, location, result);
        if (properties != null) {
            Set propertyNames = getKeys(properties);
            for (String propertyName : propertyNames) {
                JsonNode propertyNode = properties.get(propertyName);
                if (propertyNode.getNodeType().equals(JsonNodeType.OBJECT)) {
                    ObjectNode on = (ObjectNode) propertyNode;
                    Property property = property(on, location, result);
                    if (property != null) {
                        if ("array".equals(property.getType()) && !(property instanceof ArrayProperty && ((ArrayProperty) property).getItems() != null)) {
                            result.missing(location, "items");
                        }
                    }
                    model.addProperty(propertyName, property);
                } else {
                    result.invalidType(location, "properties", "object", propertyNode);
                }
            }
        }
    }

    public Map properties(ObjectNode node, String location, ParseResult result) {
        if (node == null) {
            return null;
        }
        Map output = new LinkedHashMap();

        Set keys = getKeys(node);
        for (String propertyName : keys) {
            JsonNode propertyNode = node.get(propertyName);
            if (propertyNode.getNodeType().equals(JsonNodeType.OBJECT)) {
                Property property = property((ObjectNode) propertyNode, location, result);
                output.put(propertyName, property);
            } else {
                result.invalidType(location, propertyName, "object", propertyNode);
            }
        }
        return output;
    }

    public Property property(ObjectNode node, String location, ParseResult result) {
        if (node != null) {
            if (node.isBoolean()) {
                return new BooleanValueProperty(node.asBoolean());
            }
            if (node.get("type") == null) {
                // may have an enum where type can be inferred
                JsonNode enumNode = node.get("enum");
                if (enumNode != null && enumNode.isArray()) {
                    String type = inferTypeFromArray((ArrayNode) enumNode);
                    node.put("type", type);
                }
            }
        }

        return Json.mapper().convertValue(node, Property.class);
    }

    public String inferTypeFromArray(ArrayNode an) {
        if (an.size() == 0) {
            return "string";
        }
        String type = null;
        for (int i = 0; i < an.size(); i++) {
            JsonNode element = an.get(0);
            if (element.isBoolean()) {
                if (type == null) {
                    type = "boolean";
                } else if (!"boolean".equals(type)) {
                    type = "string";
                }
            } else if (element.isNumber()) {
                if (type == null) {
                    type = "number";
                } else if (!"number".equals(type)) {
                    type = "string";
                }
            } else {
                type = "string";
            }
        }

        return type;
    }

    public RefModel refModel(ObjectNode node, String location, ParseResult result) {
        RefModel output = new RefModel();

        if (node.getNodeType().equals(JsonNodeType.OBJECT)) {
            String refValue = ((TextNode) node.get("$ref")).textValue();
            output.set$ref(refValue);
        } else {
            result.invalidType(location, "$ref", "object", node);
            return null;
        }

        // extra keys
        Set keys = getKeys(node);
        for (String key : keys) {
            if (!REF_MODEL_KEYS.contains(key)) {
                result.extra(location, key, node.get(key));
            }
        }

        return output;
    }


    public Responses responses(ObjectNode node, String location, ParseResult result) {
        if(node == null)
            return null;

        Responses output = new Responses();

        Set keys = getKeys(node);

        for(String key : keys) {
            JsonNode responseValue = node.get(key);
            if (key.startsWith("x-")) {
                output.addVendorExtension(key,extension(responseValue));
            }
            else {
                ObjectNode obj = getObject(key, node, false, location + ".responses", result);
                Response response = response(obj, location + "." + key, result);
                output.put(key, response);
            }
        }

        return output;
    }

    public Response response(ObjectNode node, String location, ParseResult result) {
        if (node == null)
            return null;

        Response output = new Response();
        JsonNode ref = node.get("$ref");
        if (ref != null) {
            if (ref.getNodeType().equals(JsonNodeType.STRING)) {

                return refResponse((TextNode) ref, location, result);
            } else {
                result.invalidType(location, "$ref", "string", node);
                return null;
            }
        }

        String value = getString("description", node, true, location, result);
        output.description(value);

        ObjectNode schema = getObject("schema", node, false, location, result);
        if (schema != null) {
            JsonNode schemaRef = schema.get("$ref");
            if (schemaRef != null) {
                if (schemaRef.getNodeType().equals(JsonNodeType.STRING)) {
                    Model schemaProp = new RefModel(schemaRef.textValue());
                    output.responseSchema(schemaProp);
                } else {
                    result.invalidType(location, "$ref", "string", node);
                }
            } else {
                output.responseSchema(definition(schema, location + ".schema", result));
            }

        }
        ObjectNode headersNode = getObject("headers", node, false, location, result);
        if (headersNode != null) {
            // TODO
            Map headers = Json.mapper().convertValue(headersNode,
                    Json.mapper().getTypeFactory().constructMapType(Map.class, String.class, Property.class));
            output.headers(headers);
        }

        ObjectNode examplesNode = getObject("examples", node, false, location, result);
        if (examplesNode != null) {
            Map examples = Json.mapper().convertValue(examplesNode, Json.mapper().getTypeFactory().constructMapType(Map.class, String.class, Object.class));
            output.setExamples(examples);
        }

        // extra keys
        Set keys = getKeys(node);
        for (String key : keys) {
            if (key.startsWith("x-")) {
                output.setVendorExtension(key, extension(node.get(key)));
            } else if (!RESPONSE_KEYS.contains(key)) {
                result.extra(location, key, node.get(key));
            }
        }
        return output;
    }

    public Info info(ObjectNode node, String location, ParseResult result) {
        if (node == null)
            return null;

        Info info = new Info();
        String value = getString("title", node, true, location, result);
        info.title(value);

        value = getString("description", node, false, location, result);
        info.description(value);

        value = getString("termsOfService", node, false, location, result);
        info.termsOfService(value);

        ObjectNode obj = getObject("contact", node, false, "contact", result);
        Contact contact = contact(obj, location, result);
        info.contact(contact);

        obj = getObject("license", node, false, location, result);
        License license = license(obj, location, result);
        info.license(license);

        value = getString("version", node, false, location, result);
        info.version(value);

        // extra keys
        Set keys = getKeys(node);
        for (String key : keys) {
            if (key.startsWith("x-")) {
                info.setVendorExtension(key, extension(node.get(key)));
            } else if (!INFO_KEYS.contains(key)) {
                result.extra(location, key, node.get(key));
            }
        }

        return info;
    }

    public License license(ObjectNode node, String location, ParseResult result) {
        if (node == null)
            return null;

        License license = new License();

        String value = getString("name", node, true, location, result);
        license.name(value);

        value = getString("url", node, false, location, result);
        license.url(value);

        // extra keys
        Set keys = getKeys(node);
        for (String key : keys) {
            if (key.startsWith("x-")) {
                license.setVendorExtension(key, extension(node.get(key)));
            } else if (!LICENSE_KEYS.contains(key)) {
                result.extra(location + ".license", key, node.get(key));
            }
        }

        return license;
    }

    public Contact contact(ObjectNode node, String location, ParseResult result) {
        if (node == null)
            return null;

        Contact contact = new Contact();

        String value = getString("name", node, false, location + ".name", result);
        contact.name(value);

        value = getString("url", node, false, location + ".url", result);
        contact.url(value);

        value = getString("email", node, false, location + ".email", result);
        contact.email(value);

        // extra keys
        Set keys = getKeys(node);
        for (String key : keys) {
            if (key.startsWith("x-")) {
                contact.setVendorExtension(key, extension(node.get(key)));
            } else if (!CONTACT_KEYS.contains(key)) {
                result.extra(location + ".contact", key, node.get(key));
            }
        }

        return contact;
    }

    public Map securityDefinitions(ObjectNode node, String location, ParseResult result) {
        if (node == null)
            return null;

        Map output = new LinkedHashMap<>();
        Set keys = getKeys(node);

        for (String key : keys) {
            ObjectNode obj = getObject(key, node, false, location, result);
            SecuritySchemeDefinition def = securityDefinition(obj, location + "." + key, result);

            if (def != null) {
                output.put(key, def);
            }
        }

        return output;
    }

    public SecuritySchemeDefinition securityDefinition(ObjectNode node, String location, ParseResult result) {
        if (node == null)
            return null;

        SecuritySchemeDefinition output = null;

        String type = getString("type", node, true, location, result);
        if (type != null) {
            if (type.equals("basic")) {
                // TODO: parse manually for better feedback
                output = Json.mapper().convertValue(node, BasicAuthDefinition.class);
            } else if (type.equals("apiKey")) {
                String position = getString("in", node, true, location, result);
                String name = getString("name", node, true, location, result);

                if (name != null && ("header".equals(position) || "query".equals(position))) {
                    In in = In.forValue(position);
                    if (in != null) {
                        output = new ApiKeyAuthDefinition()
                                .name(name)
                                .in(in);
                        String description = getString("description", node, false, location, result);
                        output.setDescription(description);
                    }
                }
                JsonNode desc = node.get("description");
                if (desc != null) {
                    output.setDescription(desc.textValue());
                }
            } else if (type.equals("oauth2")) {
                // TODO: parse manually for better feedback
                output = Json.mapper().convertValue(node, OAuth2Definition.class);
                JsonNode desc = node.get("description");
                if (desc != null) {
                    output.setDescription(desc.textValue());
                }
            } else {
                result.invalidType(location + ".type", "type", "basic|apiKey|oauth2", node);
            }

            // extra keys
            Set keys = getKeys(node);
            for (String key : keys) {
                if (key.startsWith("x-")) {
                    output.setVendorExtension(key, extension(node.get(key)));
                } else if (!SECURITY_SCHEME_KEYS.contains(key)) {
                    result.extra(location, key, node.get(key));
                }
            }
        }


        return output;
    }

    public List securityRequirements(ArrayNode node, String location, ParseResult result) {
        if (node == null)
            return null;

        List output = new ArrayList();

        for (JsonNode item : node) {
            SecurityRequirement security = new SecurityRequirement();
            if (item.getNodeType().equals(JsonNodeType.OBJECT)) {
                ObjectNode on = (ObjectNode) item;
                Set keys = getKeys(on);

                for (String key : keys) {
                    List scopes = new ArrayList<>();
                    ArrayNode obj = getArray(key, on, false, location + ".security", result);
                    if (obj != null) {
                        for (JsonNode n : obj) {
                            if (n.getNodeType().equals(JsonNodeType.STRING)) {
                                scopes.add(n.asText());
                            } else {
                                result.invalidType(location, key, "string", n);
                            }
                        }
                    }
                    security.requirement(key, scopes);
                }
            }
            output.add(security);
        }

        return output;
    }


    public List tagStrings(ArrayNode nodes, String location, ParseResult result) {
        if (nodes == null)
            return null;

        List output = new ArrayList();

        for (JsonNode node : nodes) {
            if (node.getNodeType().equals(JsonNodeType.STRING)) {
                output.add(node.textValue());
            }
        }
        return output;
    }

    public List tags(ArrayNode nodes, String location, ParseResult result) {
        if (nodes == null)
            return null;

        List output = new ArrayList();

        for (JsonNode node : nodes) {
            if (node.getNodeType().equals(JsonNodeType.OBJECT)) {
                Tag tag = tag((ObjectNode) node, location + ".tags", result);
                if (tag != null) {
                    output.add(tag);
                }
            }
        }

        return output;
    }

    public Tag tag(ObjectNode node, String location, ParseResult result) {
        Tag tag = null;

        if (node != null) {
            tag = new Tag();
            Set keys = getKeys(node);

            String value = getString("name", node, true, location, result);
            tag.name(value);

            value = getString("description", node, false, location, result);
            tag.description(value);

            ObjectNode externalDocs = getObject("externalDocs", node, false, location, result);
            ExternalDocs docs = externalDocs(externalDocs, location + "externalDocs", result);
            tag.externalDocs(docs);

            // extra keys
            for (String key : keys) {
                if (key.startsWith("x-")) {
                    tag.setVendorExtension(key, extension(node.get(key)));
                } else if (!TAG_KEYS.contains(key)) {
                    result.extra(location + ".externalDocs", key, node.get(key));
                }
            }
        }

        return tag;
    }

    public ExternalDocs externalDocs(ObjectNode node, String location, ParseResult result) {
        ExternalDocs output = null;

        if (node != null) {
            output = new ExternalDocs();
            Set keys = getKeys(node);

            String value = getString("description", node, false, location, result);
            output.description(value);

            value = getString("url", node, true, location, result);
            output.url(value);

            // extra keys
            for (String key : keys) {
                if (key.startsWith("x-")) {
                    output.setVendorExtension(key, extension(node.get(key)));
                } else if (!EXTERNAL_DOCS_KEYS.contains(key)) {
                    result.extra(location + ".externalDocs", key, node.get(key));
                }
            }
        }

        return output;
    }

    public String getString(JsonNode node, String location, ParseResult result) {
        String output = null;
        if (!node.getNodeType().equals(JsonNodeType.STRING)) {
            result.invalidType(location, "", "string", node);
        } else {
            output = ((TextNode) node).asText();
        }
        return output;
    }

    public ArrayNode getArray(String key, ObjectNode node, boolean required, String location, ParseResult result) {
        JsonNode value = node.get(key);
        ArrayNode an = null;
        if (value == null) {
            if (required) {
                result.missing(location, key);
                result.invalid();
            }
        } else if (!value.getNodeType().equals(JsonNodeType.ARRAY)) {
            result.invalidType(location, key, "array", value);
        } else {
            an = (ArrayNode) value;
        }
        return an;
    }

    public ObjectNode getObject(String key, ObjectNode node, boolean required, String location, ParseResult result) {
        JsonNode value = node.get(key);
        ObjectNode on = null;
        if (value == null) {
            if (required) {
                result.missing(location, key);
                result.invalid();
            }
        } else if (!value.getNodeType().equals(JsonNodeType.OBJECT)) {
            result.invalidType(location, key, "object", value);
            if (required) {
                result.invalid();
            }
        } else {
            on = (ObjectNode) value;
        }
        return on;
    }

    public BigDecimal getBigDecimal(String key, ObjectNode node, boolean required, String location, ParseResult result) {
        BigDecimal value = null;
        JsonNode v = node.get(key);
        if (node == null || v == null) {
            if (required) {
                result.missing(location, key);
                result.invalid();
            }
        } else if (v.getNodeType().equals(JsonNodeType.NUMBER)) {
            value = new BigDecimal(v.asText());
        } else if (!v.isValueNode()) {
            result.invalidType(location, key, "double", node);
        }
        return value;
    }

    public Number getNumber(String key, ObjectNode node, boolean required, String location, ParseResult result) {
        Number value = null;
        JsonNode v = node.get(key);
        if (v == null) {
            if (required) {
                result.missing(location, key);
                result.invalid();
            }
        } else if (v.getNodeType().equals(JsonNodeType.NUMBER)) {
            value = v.numberValue();
        } else if (!v.isValueNode()) {
            result.invalidType(location, key, "number", node);
        }
        return value;
    }

    public Integer getInteger(String key, ObjectNode node, boolean required, String location, ParseResult result) {
        Integer value = null;
        JsonNode v = node.get(key);
        if (node == null || v == null) {
            if (required) {
                result.missing(location, key);
                result.invalid();
            }
        } else if (v.getNodeType().equals(JsonNodeType.NUMBER)) {
            value = v.intValue();
        } else if (!v.isValueNode()) {
            result.invalidType(location, key, "integer", node);
        }
        return value;
    }

    public String getString(String key, ObjectNode node, boolean required, String location, ParseResult result) {
        return getString(key, node, required, location, result, null);
    }

    public String getString(String key, ObjectNode node, boolean required, String location, ParseResult result, Set uniqueValues) {
        String value = null;
        JsonNode v = node.get(key);
        if (node == null || v == null) {
            if (required) {
                result.missing(location, key);
                result.invalid();
            }
        } else if (!v.isValueNode()) {
            result.invalidType(location, key, "string", node);
        } else {
            value = v.asText();
            if (uniqueValues != null && !uniqueValues.add(value)) {
                result.unique(location, "operationId");
                result.invalid();
            }
        }
        return value;
    }

    public Set getKeys(ObjectNode node) {
        Set keys = new LinkedHashSet<>();
        if (node == null) {
            return keys;
        }

        Iterator it = node.fieldNames();
        while (it.hasNext()) {
            keys.add(it.next());
        }

        return keys;
    }

    protected static class ParseResult {
        private boolean valid = true;
        private Map extra = new LinkedHashMap();
        private Map unsupported = new LinkedHashMap();
        private Map invalidType = new LinkedHashMap();
        private List warnings = new ArrayList<>();
        private List missing = new ArrayList();
        private List unique = new ArrayList<>();

        public ParseResult() {
        }

        public void unsupported(String location, String key, JsonNode value) {
            unsupported.put(new Location(location, key), value);
        }

        public void extra(String location, String key, JsonNode value) {
            extra.put(new Location(location, key), value);
        }

        public void unique(String location, String key) {
            unique.add(new Location(location, key));
        }

        public void missing(String location, String key) {
            missing.add(new Location(location, key));
        }

        public void warning(String location, String key) {
            warnings.add(new Location(location, key));
        }

        public void invalidType(String location, String key, String expectedType, JsonNode value) {
            invalidType.put(new Location(location, key), expectedType);
        }

        public void invalid() {
            this.valid = false;
        }

        public Map getUnsupported() {
            return unsupported;
        }

        public void setUnsupported(Map unsupported) {
            this.unsupported = unsupported;
        }

        public boolean isValid() {
            return valid;
        }

        public void setValid(boolean valid) {
            this.valid = valid;
        }

        public Map getExtra() {
            return extra;
        }

        public void setExtra(Map extra) {
            this.extra = extra;
        }

        public Map getInvalidType() {
            return invalidType;
        }

        public void setInvalidType(Map invalidType) {
            this.invalidType = invalidType;
        }

        public List getMissing() {
            return missing;
        }

        public void setMissing(List missing) {
            this.missing = missing;
        }

        public List getMessages() {
            List messages = new ArrayList();
            for (Location l : extra.keySet()) {
                String location = l.location.equals("") ? "" : l.location + ".";
                String message = "attribute " + location + l.key + " is unexpected";
                messages.add(message);
            }
            for (Location l : invalidType.keySet()) {
                String location = l.location.equals("") ? "" : l.location + ".";
                String message = "attribute " + location + l.key + " is not of type `" + invalidType.get(l) + "`";
                messages.add(message);
            }
            for (Location l : missing) {
                String location = l.location.equals("") ? "" : l.location + ".";
                String message = "attribute " + location + l.key + " is missing";
                messages.add(message);
            }
            for (Location l : warnings) {
                String location = l.location.equals("") ? "" : l.location + ".";
                String message = "attribute " + location + l.key;
                messages.add(message);
            }
            for (Location l : unique) {
                String location = l.location.equals("") ? "" : l.location + ".";
                String message = "attribute " + location + l.key + " is repeated";
                messages.add(message);
            }
            for (Location l : unsupported.keySet()) {
                String location = l.location.equals("") ? "" : l.location + ".";
                String message = "attribute " + location + l.key + " is unsupported";
                messages.add(message);
            }
            return messages;
        }
    }

    protected static class Location {
        private String location;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Location)) return false;

            Location location1 = (Location) o;

            if (location != null ? !location.equals(location1.location) : location1.location != null) return false;
            return !(key != null ? !key.equals(location1.key) : location1.key != null);

        }

        @Override
        public int hashCode() {
            int result = location != null ? location.hashCode() : 0;
            result = 31 * result + (key != null ? key.hashCode() : 0);
            return result;
        }

        private String key;

        public Location(String location, String key) {
            this.location = location;
            this.key = key;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy