lookup = new HashMap<>();
+
+ static {
+ for (ParameterType type: ParameterType.values()) {
+ lookup.put(type.name(), type);
+ }
+ }
+
+ public static ParameterType of(String typeStr) {
+ return lookup.get(StringUtils.trimToEmpty(typeStr).toUpperCase());
+ }
+
+ public static boolean is(String typeStr, ParameterType type) {
+ return type == of(typeStr);
+ }
+}
diff --git a/src/main/java/com/mservicetech/openapi/common/Status.java b/src/main/java/com/mservicetech/openapi/common/Status.java
new file mode 100644
index 0000000..76e0a85
--- /dev/null
+++ b/src/main/java/com/mservicetech/openapi/common/Status.java
@@ -0,0 +1,173 @@
+package com.mservicetech.openapi.common;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.util.IllegalFormatException;
+import java.util.Map;
+import org.yaml.snakeyaml.Yaml;
+
+import static java.lang.String.format;
+
+/**
+ * For every status response, there is only one message returned. This means the server
+ * will fail fast and won't return multiple message at all. Two benefits for this design:
+ *
+ * 1. low latency as server will return the first error without further processing
+ * 2. limited attack risks and make the error handling harder to analyzed
+ *
+ * @author Steve Hu
+ */
+public class Status {
+ private static final Logger logger = LoggerFactory.getLogger(Status.class);
+ static final Map config;
+
+ // default severity
+ public static final String defaultSeverity = "ERROR";
+
+ private int statusCode;
+ private String code;
+ private String severity;
+ private String message;
+ private String description;
+
+ static {
+ InputStream inputStream = Status.class.getClassLoader().getResourceAsStream("status.yml");
+ final Yaml yaml = new Yaml();
+ config = yaml.load(inputStream);
+ }
+
+ /**
+ * Default construction that is only used in reflection.
+ */
+ public Status() {
+ }
+
+ /**
+ * Construct a status object based on error code and a list of arguments. It is
+ * the most popular way to create status object from status.yml definition.
+ *
+ * @param code Error Code
+ * @param args A list of arguments that will be populated into the error description
+ */
+ public Status(final String code, final Object... args) {
+ this.code = code;
+ @SuppressWarnings("unchecked")
+ Map map = (Map) config.get(code);
+ if (map != null) {
+ this.statusCode = (Integer) map.get("statusCode");
+ this.message = (String) map.get("message");
+ this.description = (String) map.get("description");
+ try {
+ this.description = format(this.description, args);
+ } catch (IllegalFormatException e) {
+// logger.warn(format("Error formatting description of status %s", code), e);
+ }
+ if ((this.severity = (String) map.get("severity")) == null)
+ this.severity = defaultSeverity;
+
+ }
+ }
+
+ /**
+ * Construct a status object based on all the properties in the object. It is not
+ * very often to use this construct to create object.
+ *
+ * @param statusCode Status Code
+ * @param code Code
+ * @param message Message
+ * @param description Description
+ */
+ public Status(int statusCode, String code, String message, String description) {
+ this.statusCode = statusCode;
+ this.code = code;
+ this.severity = defaultSeverity;
+ this.message = message;
+ this.description = description;
+ }
+
+ /**
+ * Construct a status object based on all the properties in the object. It is not
+ * very often to use this construct to create object.
+ *
+ * @param httpStatus HttpStatus
+ * @param message Message
+ * @param description Description
+ */
+ public Status(HttpStatus httpStatus, String message, String description) {
+ this.statusCode = httpStatus.value();
+ this.code = httpStatus.getReasonPhrase();
+ this.severity = defaultSeverity;
+ this.message = message;
+ this.description = description;
+ }
+
+
+ /**
+ * Construct a status object based on all the properties in the object. It is not
+ * very often to use this construct to create object.
+ *
+ * @param statusCode Status Code
+ * @param code Code
+ * @param severity Status Severity
+ * @param message Message
+ * @param description Description
+ */
+ public Status(int statusCode, String code, String message, String description, String severity) {
+ this.statusCode = statusCode;
+ this.code = code;
+ this.severity = severity;
+ this.message = message;
+ this.description = description;
+ }
+
+ public int getStatusCode() {
+ return statusCode;
+ }
+
+ public void setStatusCode(int statusCode) {
+ this.statusCode = statusCode;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public void setCode(String code) {
+ this.code = code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public void setSeverity(String severity) {
+ this.severity = severity;
+ }
+
+ public String getSeverity() {
+ return severity;
+ }
+
+ @Override
+ public String toString() {
+ return "{\"statusCode\":" + getStatusCode()
+ + ",\"code\":\"" + getCode()
+ + "\",\"message\":\""
+ + getMessage() + "\",\"description\":\""
+ + getDescription() + "\",\"severity\":\"" + getSeverity() + "\"}";
+ }
+}
diff --git a/src/main/java/com/mservicetech/openapi/validation/ApiNormalisedPath.java b/src/main/java/com/mservicetech/openapi/validation/ApiNormalisedPath.java
deleted file mode 100644
index 38807b9..0000000
--- a/src/main/java/com/mservicetech/openapi/validation/ApiNormalisedPath.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (c) 2016 Network New Technologies Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.mservicetech.openapi.validation;
-
-import com.networknt.oas.model.OpenApi3;
-import com.networknt.openapi.NormalisedPath;
-import com.networknt.openapi.OpenApiHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.List;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.unmodifiableList;
-import static java.util.Objects.requireNonNull;
-
-/**
- * This utility normalize the RESTful API path so that they can be
- * handled identically.
- *
- * @author Steve Hu
- */
-public class ApiNormalisedPath implements NormalisedPath {
- Logger logger = LoggerFactory.getLogger(ApiNormalisedPath.class);
- private final List pathParts;
- private final String original;
- private final String normalised;
-
- public ApiNormalisedPath(final String path, OpenApi3 openApi3, String basePath) {
- if (logger.isDebugEnabled()) logger.debug("path =" + path);
- this.original = requireNonNull(path, "A path is required");
- this.normalised = normalise(path, openApi3, basePath);
- if (logger.isDebugEnabled()) logger.debug("normalised = " + this.normalised);
- this.pathParts = unmodifiableList(asList(normalised.split("/")));
- }
-
- @Override
- public List parts() {
- return pathParts;
- }
-
- @Override
- public String part(int index) {
- return pathParts.get(index);
- }
-
- @Override
- public boolean isParam(int index) {
- final String part = part(index);
- return part.startsWith("{") && part.endsWith("}");
- }
-
- @Override
- public String paramName(int index) {
- if (!isParam(index)) {
- return null;
- }
- final String part = part(index);
- return part.substring(1, part.length() - 1);
- }
-
- @Override
- public String original() {
- return original;
- }
-
- @Override
- public String normalised() {
- return normalised;
- }
-
- private String normalise(String requestPath, OpenApi3 openApi3, String basePath) {
- if(openApi3 != null && basePath.length() > 0) {
- requestPath = requestPath.replaceFirst(OpenApiHelper.basePath, "");
- }
- if (!requestPath.startsWith("/")) {
- return "/" + requestPath;
- }
- return requestPath;
- }
-}
diff --git a/src/main/java/com/mservicetech/openapi/validation/OpenApiHelper.java b/src/main/java/com/mservicetech/openapi/validation/OpenApiHelper.java
deleted file mode 100644
index bf53e82..0000000
--- a/src/main/java/com/mservicetech/openapi/validation/OpenApiHelper.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (c) 2016 Network New Technologies Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.mservicetech.openapi.validation;
-
-import com.mservicetech.openapi.common.OpenApiLoadException;
-import com.networknt.oas.OpenApiParser;
-import com.networknt.oas.model.OpenApi3;
-import com.networknt.oas.model.SecurityScheme;
-import com.networknt.oas.model.Server;
-import com.networknt.openapi.NormalisedPath;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-
-public class OpenApiHelper {
-
-
- static final Logger logger = LoggerFactory.getLogger(OpenApiHelper.class);
-
- public final OpenApi3 openApi3;
- public final List oauth2Names;
- public final String basePath;
-
-
-
- public OpenApiHelper(String spec) throws OpenApiLoadException {
- try {
- openApi3 = (OpenApi3) new OpenApiParser().parse(spec, new URL("https://oas.lightapi.net/"));
- oauth2Names = getOAuth2Name();
- basePath = getBasePath();
- } catch (Exception e) {
- throw new OpenApiLoadException(e.getMessage());
- }
-
- }
-
- public Optional findMatchingApiPath(final NormalisedPath requestPath) {
- if(this.openApi3 != null) {
- return this.openApi3.getPaths().keySet()
- .stream()
- .map(p -> (NormalisedPath) new ApiNormalisedPath(p, openApi3, basePath))
- .filter(p -> pathMatches(requestPath, p))
- .findFirst();
- } else {
- return Optional.empty();
- }
- }
-
- protected List getOAuth2Name() {
- List names = new ArrayList<>();
- Map defMap = openApi3.getSecuritySchemes();
- if(defMap != null) {
- for(Map.Entry entry : defMap.entrySet()) {
- if(entry.getValue().getType().equals("oauth2")) {
- names.add(entry.getKey());
- }
- }
- }
- return names;
- }
-
- protected String getBasePath() {
-
- String basePath = "";
- String url = null;
- if (openApi3.getServers().size() > 0) {
- Server server = openApi3.getServer(0);
- url = server.getUrl();
- }
- if (url != null) {
- // find "://" index
- int protocolIndex = url.indexOf("://");
- int pathIndex = url.indexOf('/', protocolIndex + 3);
- if (pathIndex > 0) {
- basePath = url.substring(pathIndex);
- }
- }
- return basePath;
- }
-
- private boolean pathMatches(final NormalisedPath requestPath, final NormalisedPath apiPath) {
- if (requestPath.parts().size() != apiPath.parts().size()) {
- return false;
- }
- for (int i = 0; i < requestPath.parts().size(); i++) {
- if (requestPath.part(i).equalsIgnoreCase(apiPath.part(i)) || apiPath.isParam(i)) {
- continue;
- }
- return false;
- }
- return true;
- }
-
- public OpenApi3 getOpenApi3() {
- return openApi3;
- }
-
-}
diff --git a/src/main/java/com/mservicetech/openapi/validation/OpenApiValidator.java b/src/main/java/com/mservicetech/openapi/validation/OpenApiValidator.java
index 763bedc..96efb2f 100644
--- a/src/main/java/com/mservicetech/openapi/validation/OpenApiValidator.java
+++ b/src/main/java/com/mservicetech/openapi/validation/OpenApiValidator.java
@@ -1,8 +1,10 @@
package com.mservicetech.openapi.validation;
import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mservicetech.openapi.common.ParameterType;
import com.mservicetech.openapi.common.RequestEntity;
-import com.networknt.config.Config;
+import com.mservicetech.openapi.common.Status;
import com.networknt.jsonoverlay.Overlay;
import com.networknt.oas.model.Operation;
import com.networknt.oas.model.Parameter;
@@ -10,11 +12,11 @@
import com.networknt.oas.model.RequestBody;
import com.networknt.oas.model.impl.RequestBodyImpl;
import com.networknt.oas.model.impl.SchemaImpl;
+import com.networknt.openapi.ApiNormalisedPath;
import com.networknt.openapi.NormalisedPath;
+import com.networknt.openapi.OpenApiHelper;
import com.networknt.openapi.OpenApiOperation;
-import com.networknt.openapi.parameter.ParameterType;
import com.networknt.schema.SchemaValidatorsConfig;
-import com.networknt.status.Status;
import com.networknt.utility.StringUtils;
import org.slf4j.Logger;
@@ -41,6 +43,7 @@ public class OpenApiValidator {
public String spec;
public OpenApiHelper openApiHelper;
public SchemaValidator schemaValidator;
+ private ObjectMapper objectMapper = new ObjectMapper();
/**
* Construct a new request validator with the given schema validator.
@@ -54,8 +57,8 @@ public OpenApiValidator() {
throw new IOException("cannot load openapi spec file");
}
spec = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)).lines().collect(Collectors.joining("\n"));
- openApiHelper = new OpenApiHelper(spec);
- schemaValidator = new SchemaValidator(openApiHelper.getOpenApi3());
+ openApiHelper = OpenApiHelper.init(spec);
+ schemaValidator = new SchemaValidator(openApiHelper.openApi3);
} catch (Exception e) {
logger.error("initial failed:" + e);
}
@@ -69,8 +72,8 @@ public OpenApiValidator() {
public OpenApiValidator(String openapiPath) {
InputStream in = this.getClass().getClassLoader().getResourceAsStream(openapiPath);
spec = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)).lines().collect(Collectors.joining("\n"));
- openApiHelper = new OpenApiHelper(spec);
- schemaValidator = new SchemaValidator(openApiHelper.getOpenApi3());
+ openApiHelper = OpenApiHelper.init(spec);
+ schemaValidator = new SchemaValidator(openApiHelper.openApi3);
}
/**
@@ -80,8 +83,8 @@ public OpenApiValidator(String openapiPath) {
*/
public OpenApiValidator(InputStream openapi) {
spec = new BufferedReader(new InputStreamReader(openapi, StandardCharsets.UTF_8)).lines().collect(Collectors.joining("\n"));
- openApiHelper = new OpenApiHelper(spec);
- schemaValidator = new SchemaValidator(openApiHelper.getOpenApi3());
+ openApiHelper = OpenApiHelper.init(spec);
+ schemaValidator = new SchemaValidator(openApiHelper.openApi3);
}
/**
@@ -93,7 +96,7 @@ public OpenApiValidator(InputStream openapi) {
*/
public Status validateRequestPath (String requestURI , String httpMethod, RequestEntity requestEntity ) {
requireNonNull(openApiHelper, "openApiHelper object cannot be null");
- final NormalisedPath requestPath = new ApiNormalisedPath(requestURI, openApiHelper.getOpenApi3(), openApiHelper.getBasePath());
+ final NormalisedPath requestPath = new ApiNormalisedPath(requestURI);
final Optional maybeApiPath = openApiHelper.findMatchingApiPath(requestPath);
if (!maybeApiPath.isPresent()) {
Status status = new Status( STATUS_INVALID_REQUEST_PATH, requestPath.normalised());
@@ -101,7 +104,7 @@ public Status validateRequestPath (String requestURI , String httpMethod, Reques
}
final NormalisedPath openApiPathString = maybeApiPath.get();
- final Path path = openApiHelper.getOpenApi3().getPath(openApiPathString.original());
+ final Path path = openApiHelper.openApi3.getPath(openApiPathString.original());
final Operation operation = path.getOperation(httpMethod.toLowerCase());
OpenApiOperation openApiOperation = new OpenApiOperation(openApiPathString, path, httpMethod, operation);
@@ -331,10 +334,10 @@ private Object attachJsonBody( String bodyString) throws Exception {
if (bodyString != null) {
bodyString = bodyString.trim();
if (bodyString.startsWith("{")) {
- body = Config.getInstance().getMapper().readValue(bodyString, new TypeReference