Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixes #358 upgrade the json-schema-validator to 1.4.3 #376

Merged
merged 1 commit into from
Jun 26, 2024
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 @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -213,7 +233,7 @@ private Status validateQueryParameter(final HttpServerExchange exchange,

Optional<Status> 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();

Expand All @@ -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;
}
Expand Down Expand Up @@ -322,7 +343,7 @@ private Status validateHeader(final HttpServerExchange exchange,
} else {
Optional<Status> 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);
Expand All @@ -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);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand All @@ -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()));
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
Expand All @@ -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()) {
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -258,7 +269,7 @@ private Status validateHeader(HttpServerExchange exchange, String headerName, He
} else {
Optional<Status> 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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -90,38 +86,39 @@ 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<ValidationMessage> processingReport = null;
try {
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());
}
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
<version.swagger-core>1.5.18</version.swagger-core>
<version.jsr305>3.0.2</version.jsr305>
<versions.maven-version>2.4</versions.maven-version>
<version.json-schema-validator>1.0.65</version.json-schema-validator>
<version.json-schema-validator>1.4.3</version.json-schema-validator>
<version.yaml-rule>1.0.3</version.yaml-rule>
<version.maven-javadoc>3.4.1</version.maven-javadoc>
</properties>
Expand Down