From c6373af97748e345556029eedf746a9a6b096549 Mon Sep 17 00:00:00 2001 From: Steve Hu Date: Tue, 25 Jun 2024 21:28:01 -0400 Subject: [PATCH] fixes #358 upgrade the json-schema-validator to 1.4.3 --- .../networknt/openapi/RequestValidator.java | 54 +++++++++++++------ .../networknt/openapi/ResponseValidator.java | 33 ++++++++---- .../networknt/openapi/SchemaValidator.java | 35 ++++++------ pom.xml | 2 +- 4 files changed, 77 insertions(+), 47 deletions(-) diff --git a/openapi-validator/src/main/java/com/networknt/openapi/RequestValidator.java b/openapi-validator/src/main/java/com/networknt/openapi/RequestValidator.java index 59c42c7d..8e8d17c4 100644 --- a/openapi-validator/src/main/java/com/networknt/openapi/RequestValidator.java +++ b/openapi-validator/src/main/java/com/networknt/openapi/RequestValidator.java @@ -19,14 +19,11 @@ import static java.util.Objects.requireNonNull; import java.net.URLDecoder; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import java.util.*; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.TextNode; +import com.networknt.config.Config; import com.networknt.httpstring.AttachmentConstants; import io.undertow.util.Headers; import org.slf4j.Logger; @@ -60,6 +57,7 @@ public class RequestValidator { static final String VALIDATOR_REQUEST_BODY_MISSING = "ERR11014"; static final String VALIDATOR_REQUEST_PARAMETER_HEADER_MISSING = "ERR11017"; static final String VALIDATOR_REQUEST_PARAMETER_QUERY_MISSING = "ERR11000"; + static final String CONTENT_TYPE_MISMATCH = "ERR10015"; private final SchemaValidator schemaValidator; @@ -110,12 +108,12 @@ private Status validateRequestBody(Object requestBody, final OpenApiOperation op if (requestBody == null) { if (specBody.getRequired() != null && specBody.getRequired()) { if(BodyHandler.config.isEnabled()) { - // BodyHandler is enable and there is no error returned, that means the request body is empty. This is an validation error. + // BodyHandler is enabled and there is no error returned, that means the request body is empty. This is a validation error. return new Status(VALIDATOR_REQUEST_BODY_MISSING, openApiOperation.getMethod(), openApiOperation.getPathString().original()); } else { - // most likely, the BodyHandler is missing from the request chain and we cannot find the body attachment in the exchange + // most likely, the BodyHandler is missing from the request chain, and we cannot find the body attachment in the exchange // the second scenario is that application/json is not in the request header and BodyHandler is skipped. - logger.warn("Body object doesn't exist in exchange attachment. Most likely the BodyHandler is not in the request chain before RequestValidator or reqeust misses application/json content type header"); + logger.warn("Body object doesn't exist in exchange attachment. Most likely the BodyHandler is not in the request chain before RequestValidator or request misses application/json content type header"); } } return null; @@ -124,7 +122,29 @@ private Status validateRequestBody(Object requestBody, final OpenApiOperation op config.setTypeLoose(false); config.setHandleNullableField(ValidatorHandler.config.isHandleNullableField()); - return schemaValidator.validate(requestBody, Overlay.toJson((SchemaImpl)specBody.getContentMediaType("application/json").getSchema()), config, "requestBody"); + // the body can be converted to JsonNode here. If not, an error is returned. + JsonNode requestNode; + // The body can be a string, map or list. Convert to JsonNode. + if(requestBody instanceof String) { + String requestBodyString = (String)requestBody; + requestBodyString = requestBodyString.trim(); + if(requestBodyString.startsWith("{") || requestBodyString.startsWith("[")) { + try { + requestNode = Config.getInstance().getMapper().readTree(requestBodyString); + } catch (Exception e) { + return new Status(CONTENT_TYPE_MISMATCH, "application/json"); + } + } else { + return new Status(CONTENT_TYPE_MISMATCH, "application/json"); + } + } else if (requestBody instanceof Map) { + requestNode = Config.getInstance().getMapper().valueToTree(requestBody); + } else if (requestBody instanceof List) { + requestNode = Config.getInstance().getMapper().valueToTree(requestBody); + } else { + return new Status(CONTENT_TYPE_MISMATCH, "application/json"); + } + return schemaValidator.validate(requestNode, Overlay.toJson((SchemaImpl)specBody.getContentMediaType("application/json").getSchema()), config); } private Status validateRequestParameters(final HttpServerExchange exchange, final NormalisedPath requestPath, final OpenApiOperation openApiOperation) { @@ -171,7 +191,7 @@ private Status validatePathParameters(final HttpServerExchange exchange, final N logger.info("Path parameter cannot be decoded, it will be used directly"); } - return schemaValidator.validate(paramValue, Overlay.toJson((SchemaImpl)(parameter.get().getSchema())), paramName); + return schemaValidator.validate(new TextNode(paramValue), Overlay.toJson((SchemaImpl)(parameter.get().getSchema()))); } } return status; @@ -213,7 +233,7 @@ private Status validateQueryParameter(final HttpServerExchange exchange, Optional optional = queryParameterValues .stream() - .map((v) -> schemaValidator.validate(v, Overlay.toJson((SchemaImpl)queryParameter.getSchema()), queryParameter.getName())) + .map((v) -> schemaValidator.validate(new TextNode(v), Overlay.toJson((SchemaImpl)queryParameter.getSchema()))) .filter(s -> s != null) .findFirst(); @@ -222,7 +242,8 @@ private Status validateQueryParameter(final HttpServerExchange exchange, // Since if the queryParameterValue's length larger than 2, it means the query parameter is an array. // thus array validation should be applied, for example, validate the length of the array. } else { - return schemaValidator.validate(queryParameterValues, Overlay.toJson((SchemaImpl)queryParameter.getSchema()), queryParameter.getName()); + final JsonNode content = Config.getInstance().getMapper().valueToTree(queryParameterValues); + return schemaValidator.validate(content, Overlay.toJson((SchemaImpl)queryParameter.getSchema())); } return null; } @@ -322,7 +343,7 @@ private Status validateHeader(final HttpServerExchange exchange, } else { Optional optional = headerValues .stream() - .map((v) -> schemaValidator.validate(v, Overlay.toJson((SchemaImpl)headerParameter.getSchema()), headerParameter.getName())) + .map((v) -> schemaValidator.validate(new TextNode(v), Overlay.toJson((SchemaImpl)headerParameter.getSchema()))) .filter(s -> s != null) .findFirst(); return optional.orElse(null); @@ -341,7 +362,8 @@ private ValidationResult validateDeserializedValues(final HttpServerExchange exc if (null==deserializedValue) { validationResult.addSkipped(p); }else { - Status s = schemaValidator.validate(deserializedValue, Overlay.toJson((SchemaImpl)(p.getSchema())), p.getName()); + JsonNode jsonNode = Config.getInstance().getMapper().valueToTree(deserializedValue); + Status s = schemaValidator.validate(jsonNode, Overlay.toJson((SchemaImpl)(p.getSchema()))); validationResult.addStatus(s); } }); diff --git a/openapi-validator/src/main/java/com/networknt/openapi/ResponseValidator.java b/openapi-validator/src/main/java/com/networknt/openapi/ResponseValidator.java index 2c9a5d1c..0acf22bc 100644 --- a/openapi-validator/src/main/java/com/networknt/openapi/ResponseValidator.java +++ b/openapi-validator/src/main/java/com/networknt/openapi/ResponseValidator.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.TextNode; import com.networknt.config.Config; import com.networknt.dump.StoreResponseStreamSinkConduit; import com.networknt.jsonoverlay.Overlay; @@ -46,6 +47,8 @@ public class ResponseValidator { private final SchemaValidatorsConfig config; private static final String VALIDATOR_RESPONSE_CONTENT_UNEXPECTED = "ERR11018"; private static final String REQUIRED_RESPONSE_HEADER_MISSING = "ERR11019"; + private static final String CONTENT_TYPE_MISMATCH = "ERR10015"; + private static final String JSON_MEDIA_TYPE = "application/json"; private static final String GOOD_STATUS_CODE = "200"; private static final String DEFAULT_STATUS_CODE = "default"; @@ -63,7 +66,7 @@ public ResponseValidator(SchemaValidator schemaValidator) { * @param exchange HttpServerExchange in handler * @return Status return null if no validation errors */ - public Status validateResponseContent(Object responseContent, HttpServerExchange exchange) { + public Status validateResponseContent(String responseContent, HttpServerExchange exchange) { return validateResponseContent(responseContent, exchange.getRequestURI(), exchange.getRequestMethod().toString().toLowerCase(), String.valueOf(exchange.getStatusCode())); } @@ -75,7 +78,7 @@ public Status validateResponseContent(Object responseContent, HttpServerExchange * @param httpMethod eg. "put" or "get" * @return Status return null if no validation errors */ - public Status validateResponseContent(Object responseContent, String uri, String httpMethod) { + public Status validateResponseContent(String responseContent, String uri, String httpMethod) { return validateResponseContent(responseContent, uri, httpMethod, GOOD_STATUS_CODE); } @@ -88,7 +91,7 @@ public Status validateResponseContent(Object responseContent, String uri, String * @param statusCode eg. 200, 400 * @return Status return null if no validation errors */ - public Status validateResponseContent(Object responseContent, String uri, String httpMethod, String statusCode) { + public Status validateResponseContent(String responseContent, String uri, String httpMethod, String statusCode) { return validateResponseContent(responseContent, uri, httpMethod, statusCode, JSON_MEDIA_TYPE); } @@ -102,7 +105,7 @@ public Status validateResponseContent(Object responseContent, String uri, String * @param mediaTypeName eg. "application/json" * @return Status return null if no validation errors */ - public Status validateResponseContent(Object responseContent, String uri, String httpMethod, String statusCode, String mediaTypeName) { + public Status validateResponseContent(String responseContent, String uri, String httpMethod, String statusCode, String mediaTypeName) { OpenApiOperation operation = null; try { operation = getOpenApiOperation(uri, httpMethod); @@ -124,11 +127,7 @@ public Status validateResponseContent(Object responseContent, String uri, String * @param mediaTypeName eg. "application/json" * @return Status return null if no validation errors */ - public Status validateResponseContent(Object responseContent, OpenApiOperation openApiOperation, String statusCode, String mediaTypeName) { - //try to convert json string to structured object - if(responseContent instanceof String) { - responseContent = convertStrToObjTree((String)responseContent); - } + public Status validateResponseContent(String responseContent, OpenApiOperation openApiOperation, String statusCode, String mediaTypeName) { JsonNode schema = getContentSchema(openApiOperation, statusCode, mediaTypeName); //if cannot find schema based on status code, try to get from "default" if(schema == null || schema.isMissingNode()) { @@ -146,7 +145,19 @@ public Status validateResponseContent(Object responseContent, OpenApiOperation o } config.setTypeLoose(false); config.setHandleNullableField(ValidatorHandler.config.isHandleNullableField()); - return schemaValidator.validate(responseContent, schema, config); + + JsonNode responseNode; + responseContent = responseContent.trim(); + if(responseContent.startsWith("{") || responseContent.startsWith("[")) { + try { + responseNode = Config.getInstance().getMapper().readTree(responseContent); + } catch (Exception e) { + return new Status(CONTENT_TYPE_MISMATCH, "application/json"); + } + } else { + return new Status(CONTENT_TYPE_MISMATCH, "application/json"); + } + return schemaValidator.validate(responseNode, schema, config); } /** @@ -258,7 +269,7 @@ private Status validateHeader(HttpServerExchange exchange, String headerName, He } else { Optional optional = headerValues .stream() - .map((v) -> schemaValidator.validate(v, Overlay.toJson((SchemaImpl)operationHeader.getSchema()), config)) + .map((v) -> schemaValidator.validate(new TextNode(v), Overlay.toJson((SchemaImpl)operationHeader.getSchema()), config)) .filter(s -> s != null) .findFirst(); if(optional.isPresent()) { diff --git a/openapi-validator/src/main/java/com/networknt/openapi/SchemaValidator.java b/openapi-validator/src/main/java/com/networknt/openapi/SchemaValidator.java index f3908151..884cda60 100644 --- a/openapi-validator/src/main/java/com/networknt/openapi/SchemaValidator.java +++ b/openapi-validator/src/main/java/com/networknt/openapi/SchemaValidator.java @@ -18,14 +18,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.networknt.config.Config; import com.networknt.jsonoverlay.Overlay; import com.networknt.oas.model.OpenApi3; import com.networknt.oas.model.impl.OpenApi3Impl; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.SchemaValidatorsConfig; -import com.networknt.schema.ValidationMessage; +import com.networknt.schema.*; import com.networknt.status.Status; import java.util.Set; @@ -80,8 +76,8 @@ public SchemaValidator(final OpenApi3 api) { * * @return A status containing error code and description */ - public Status validate(final Object value, final JsonNode schema, SchemaValidatorsConfig config) { - return doValidate(value, schema, config, "$"); + public Status validate(final JsonNode value, final JsonNode schema, SchemaValidatorsConfig config) { + return doValidate(value, schema, config, null); } /** @@ -90,23 +86,25 @@ public Status validate(final Object value, final JsonNode schema, SchemaValidato * @param value The value to validate * @param schema The property schema to validate the value against * @param config The config model for some validator - * @param at The name of the property being validated + * @param instanceLocation The JsonNodePath being validated * @return Status object */ - public Status validate(final Object value, final JsonNode schema, SchemaValidatorsConfig config, String at) { - return doValidate(value, schema, config, at); + public Status validate(final JsonNode value, final JsonNode schema, SchemaValidatorsConfig config, JsonNodePath instanceLocation) { + return doValidate(value, schema, config, instanceLocation); } - public Status validate(final Object value, final JsonNode schema) { - return doValidate(value, schema, defaultConfig, "$"); + public Status validate(final JsonNode value, final JsonNode schema) { + return doValidate(value, schema, defaultConfig, null); } - public Status validate(final Object value, final JsonNode schema, String at) { - return doValidate(value, schema, defaultConfig, at); + public Status validate(final JsonNode value, final JsonNode schema, JsonNodePath instanceLocation) { + return doValidate(value, schema, defaultConfig, instanceLocation); } - private Status doValidate(final Object value, final JsonNode schema, SchemaValidatorsConfig config, String at) { + private Status doValidate(final JsonNode value, final JsonNode schema, SchemaValidatorsConfig config, JsonNodePath instanceLocation) { requireNonNull(schema, "A schema is required"); + if (instanceLocation == null) + instanceLocation = new JsonNodePath(config.getPathType()); Status status = null; Set processingReport = null; @@ -114,14 +112,13 @@ private Status doValidate(final Object value, final JsonNode schema, SchemaValid if(jsonNode != null) { ((ObjectNode)schema).set(COMPONENTS_FIELD, jsonNode); } - JsonSchema jsonSchema = JsonSchemaFactory.getInstance().getSchema(schema, config); - final JsonNode content = Config.getInstance().getMapper().valueToTree(value); - processingReport = jsonSchema.validate(content, content, at); + JsonSchema jsonSchema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012).getSchema(schema, config); + processingReport = jsonSchema.validate(jsonSchema.createExecutionContext(), value, value, instanceLocation); } catch (Exception e) { e.printStackTrace(); } - if(processingReport != null && processingReport.size() > 0) { + if(processingReport != null && !processingReport.isEmpty()) { ValidationMessage vm = processingReport.iterator().next(); status = new Status(VALIDATOR_SCHEMA, vm.getMessage()); } diff --git a/pom.xml b/pom.xml index 5df39162..d9046f00 100644 --- a/pom.xml +++ b/pom.xml @@ -109,7 +109,7 @@ 1.5.18 3.0.2 2.4 - 1.0.65 + 1.4.3 1.0.3 3.4.1