From 1d324fee4923d6b41aa40d3120b3b4acaa62f8c9 Mon Sep 17 00:00:00 2001 From: Balloon Date: Tue, 22 Jan 2019 20:07:36 -0500 Subject: [PATCH 1/3] -added Response Validator to validate response content -test cases will use ResponseValidator instead of HandlerTestBase, thus removed it. --- .../networknt/openapi/ResponseValidator.java | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 openapi-validator/src/main/java/com/networknt/openapi/ResponseValidator.java diff --git a/openapi-validator/src/main/java/com/networknt/openapi/ResponseValidator.java b/openapi-validator/src/main/java/com/networknt/openapi/ResponseValidator.java new file mode 100644 index 00000000..37a5bc2d --- /dev/null +++ b/openapi-validator/src/main/java/com/networknt/openapi/ResponseValidator.java @@ -0,0 +1,130 @@ +package com.networknt.openapi; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.jsonoverlay.Overlay; +import com.networknt.oas.model.*; +import com.networknt.oas.model.impl.SchemaImpl; +import com.networknt.schema.SchemaValidatorsConfig; +import com.networknt.status.Status; +import java.util.Optional; + +public class ResponseValidator { + private final SchemaValidator schemaValidator; + private final SchemaValidatorsConfig config; + static final String VALIDATOR_RESPONSE_CONTENT_UNEXPECTED = "ERR"; + static final String JSON_MEDIA_TYPE = "application/json"; + static final String GOOD_STATUS_CODE = "200"; + static final String DEFAULT_STATUS_CODE = "default"; + + /** + * Construct a new request validator with the given schema validator. + */ + public ResponseValidator(SchemaValidatorsConfig config) { + this.schemaValidator = new SchemaValidator(OpenApiHelper.openApi3); + this.config = config; + } + + public ResponseValidator() { + this.schemaValidator = new SchemaValidator(OpenApiHelper.openApi3); + this.config = new SchemaValidatorsConfig(); + } + + /** + * validate a given response content object with status code "200" and media content type "application/json" + * uri, httpMethod, JSON_MEDIA_TYPE("200"), DEFAULT_MEDIA_TYPE("application/json") is to locate the schema to validate + * @param responseContent response content needs to be validated + * @param uri uri of the request + * @param httpMethod eg. "put" or "get" + * @return Status + */ + public Status validateResponseContent(Object responseContent, String uri, String httpMethod) { + return validateResponseContent(responseContent, uri, httpMethod, GOOD_STATUS_CODE); + } + + /** + * validate a given response content object with media content type "application/json" + * uri, httpMethod, statusCode, DEFAULT_MEDIA_TYPE is to locate the schema to validate + * @param responseContent response content needs to be validated + * @param uri uri of the request + * @param httpMethod eg. "put" or "get" + * @param statusCode eg. 200, 400 + * @return Status + */ + public Status validateResponseContent(Object responseContent, String uri, String httpMethod, String statusCode) { + return validateResponseContent(responseContent, uri, httpMethod, statusCode, JSON_MEDIA_TYPE); + } + + /** + * validate a given response content object with schema coordinate (uri, httpMethod, statusCode, mediaTypeName) + * uri, httpMethod, statusCode, mediaTypeName is to locate the schema to validate + * @param responseContent response content needs to be validated + * @param uri uri of the request + * @param httpMethod eg. "put" or "get" + * @param statusCode eg. 200, 400 + * @param mediaTypeName eg. "application/json" + * @return Status + */ + public Status validateResponseContent(Object responseContent, String uri, String httpMethod, String statusCode, String mediaTypeName) { + OpenApiOperation operation = getOpenApiOperation(uri, httpMethod); + if(operation == null) { + return new Status(VALIDATOR_RESPONSE_CONTENT_UNEXPECTED, httpMethod, uri); + } + return validateResponseContent(responseContent, operation, statusCode, mediaTypeName); + } + + /** + * validate a given response content object + * @param responseContent response content needs to be validated + * @param openApiOperation OpenApi Operation which is located by uri and httpMethod + * @param statusCode eg. 200, 400 + * @param mediaTypeName eg. "application/json" + * @return Status + */ + public Status validateResponseContent(Object 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 = getContentSchema(openApiOperation, DEFAULT_STATUS_CODE, mediaTypeName); + } + if (responseContent != null && schema == null) { + return new Status(VALIDATOR_RESPONSE_CONTENT_UNEXPECTED, openApiOperation.getMethod(), openApiOperation.getPathString().original()); + } + config.setTypeLoose(false); + return schemaValidator.validate(responseContent, schema, config); + } + + /** + * locate operation based on uri and httpMethod + * @param uri uri of the request + * @param httpMethod http method of the request + * @return OpenApiOperation the wrapper of an api operation + */ + private OpenApiOperation getOpenApiOperation(String uri, String httpMethod) { + NormalisedPath requestPath = new ApiNormalisedPath(uri); + Optional maybeApiPath = OpenApiHelper.findMatchingApiPath(requestPath); + if (!maybeApiPath.isPresent()) { + return null; + } + + final NormalisedPath openApiPathString = maybeApiPath.get(); + final Path path = OpenApiHelper.openApi3.getPath(openApiPathString.original()); + + final Operation operation = path.getOperation(httpMethod); + return new OpenApiOperation(openApiPathString, path, httpMethod, operation); + } + + private JsonNode getContentSchema(OpenApiOperation operation, String statusCode, String mediaTypeStr) { + Schema schema; + Optional response = Optional.ofNullable(operation.getOperation().getResponse(String.valueOf(statusCode))); + if(response.isPresent()) { + Optional mediaType = Optional.ofNullable(response.get().getContentMediaType(mediaTypeStr)); + if(mediaType.isPresent()) { + schema = mediaType.get().getSchema(); + JsonNode schemaNode = schema == null ? null : Overlay.toJson((SchemaImpl)schema); + return schemaNode; + } + } + return null; + } +} From bd7ea5f33a77c15528d070630ad736b34ad9ff38 Mon Sep 17 00:00:00 2001 From: Balloon Date: Wed, 23 Jan 2019 13:39:49 -0500 Subject: [PATCH 2/3] -added an error message in status.yml for validating response content -now deserialize content string before do validation -fixed a issue when matching a given uri with schema, now remove query parameters from uri -changed some testing content --- .../networknt/openapi/ResponseValidator.java | 75 +++++++++++++++---- 1 file changed, 62 insertions(+), 13 deletions(-) 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 37a5bc2d..3d66adc0 100644 --- a/openapi-validator/src/main/java/com/networknt/openapi/ResponseValidator.java +++ b/openapi-validator/src/main/java/com/networknt/openapi/ResponseValidator.java @@ -1,20 +1,31 @@ package com.networknt.openapi; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.config.Config; import com.networknt.jsonoverlay.Overlay; import com.networknt.oas.model.*; import com.networknt.oas.model.impl.SchemaImpl; import com.networknt.schema.SchemaValidatorsConfig; import com.networknt.status.Status; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.List; import java.util.Optional; public class ResponseValidator { private final SchemaValidator schemaValidator; private final SchemaValidatorsConfig config; - static final String VALIDATOR_RESPONSE_CONTENT_UNEXPECTED = "ERR"; - static final String JSON_MEDIA_TYPE = "application/json"; - static final String GOOD_STATUS_CODE = "200"; - static final String DEFAULT_STATUS_CODE = "default"; + private static final String VALIDATOR_RESPONSE_CONTENT_UNEXPECTED = "ERR12108"; + 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"; + private static final Logger logger = LoggerFactory.getLogger(ResponseValidator.class); /** * Construct a new request validator with the given schema validator. @@ -33,7 +44,7 @@ public ResponseValidator() { * validate a given response content object with status code "200" and media content type "application/json" * uri, httpMethod, JSON_MEDIA_TYPE("200"), DEFAULT_MEDIA_TYPE("application/json") is to locate the schema to validate * @param responseContent response content needs to be validated - * @param uri uri of the request + * @param uri original uri of the request * @param httpMethod eg. "put" or "get" * @return Status */ @@ -45,7 +56,7 @@ public Status validateResponseContent(Object responseContent, String uri, String * validate a given response content object with media content type "application/json" * uri, httpMethod, statusCode, DEFAULT_MEDIA_TYPE is to locate the schema to validate * @param responseContent response content needs to be validated - * @param uri uri of the request + * @param uri original uri of the request * @param httpMethod eg. "put" or "get" * @param statusCode eg. 200, 400 * @return Status @@ -58,14 +69,20 @@ public Status validateResponseContent(Object responseContent, String uri, String * validate a given response content object with schema coordinate (uri, httpMethod, statusCode, mediaTypeName) * uri, httpMethod, statusCode, mediaTypeName is to locate the schema to validate * @param responseContent response content needs to be validated - * @param uri uri of the request + * @param uri original uri of the request * @param httpMethod eg. "put" or "get" * @param statusCode eg. 200, 400 * @param mediaTypeName eg. "application/json" * @return Status */ public Status validateResponseContent(Object responseContent, String uri, String httpMethod, String statusCode, String mediaTypeName) { - OpenApiOperation operation = getOpenApiOperation(uri, httpMethod); + OpenApiOperation operation = null; + try { + operation = getOpenApiOperation(uri, httpMethod); + } catch (URISyntaxException e) { + logger.error(e.getMessage()); + return new Status(VALIDATOR_RESPONSE_CONTENT_UNEXPECTED, httpMethod, uri); + } if(operation == null) { return new Status(VALIDATOR_RESPONSE_CONTENT_UNEXPECTED, httpMethod, uri); } @@ -81,27 +98,59 @@ public Status validateResponseContent(Object responseContent, String uri, String * @return Status */ 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); + } JsonNode schema = getContentSchema(openApiOperation, statusCode, mediaTypeName); //if cannot find schema based on status code, try to get from "default" if(schema == null) { schema = getContentSchema(openApiOperation, DEFAULT_STATUS_CODE, mediaTypeName); } - if (responseContent != null && schema == null) { + if ((responseContent != null && schema == null) || + (responseContent == null && schema != null)) { return new Status(VALIDATOR_RESPONSE_CONTENT_UNEXPECTED, openApiOperation.getMethod(), openApiOperation.getPathString().original()); } config.setTypeLoose(false); return schemaValidator.validate(responseContent, schema, config); } + /** + * try to convert a string with json style to a structured object. + * @param s + * @return Object + */ + private Object convertStrToObjTree(String s) { + Object contentObj = null; + if (s != null) { + s = s.trim(); + try { + if (s.startsWith("{")) { + contentObj = Config.getInstance().getMapper().readValue(s, new TypeReference>() { + }); + } else if (s.startsWith("[")) { + contentObj = Config.getInstance().getMapper().readValue(s, new TypeReference>() { + }); + } else { + logger.error("cannot deserialize json str: {}", s); + return null; + } + } catch (IOException e) { + logger.error(e.getMessage()); + } + } + return contentObj; + } + /** * locate operation based on uri and httpMethod - * @param uri uri of the request + * @param uri original uri of the request * @param httpMethod http method of the request * @return OpenApiOperation the wrapper of an api operation */ - private OpenApiOperation getOpenApiOperation(String uri, String httpMethod) { - NormalisedPath requestPath = new ApiNormalisedPath(uri); + private OpenApiOperation getOpenApiOperation(String uri, String httpMethod) throws URISyntaxException { + String uriWithoutQuery = new URI(uri).getPath(); + NormalisedPath requestPath = new ApiNormalisedPath(uriWithoutQuery); Optional maybeApiPath = OpenApiHelper.findMatchingApiPath(requestPath); if (!maybeApiPath.isPresent()) { return null; From 7e8179808306b506e674c60a8ce03ea5379a657e Mon Sep 17 00:00:00 2001 From: Balloon Date: Fri, 25 Jan 2019 14:12:32 -0500 Subject: [PATCH 3/3] -changed status code and moved it to swagger-validator errors position. --- .../src/main/java/com/networknt/openapi/ResponseValidator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3d66adc0..87e3450e 100644 --- a/openapi-validator/src/main/java/com/networknt/openapi/ResponseValidator.java +++ b/openapi-validator/src/main/java/com/networknt/openapi/ResponseValidator.java @@ -21,7 +21,7 @@ public class ResponseValidator { private final SchemaValidator schemaValidator; private final SchemaValidatorsConfig config; - private static final String VALIDATOR_RESPONSE_CONTENT_UNEXPECTED = "ERR12108"; + private static final String VALIDATOR_RESPONSE_CONTENT_UNEXPECTED = "ERR11018"; 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";