Skip to content

Commit

Permalink
fixes #34 move the openapi-helper from light-rest-4j to this repo for…
Browse files Browse the repository at this point in the history
… easy sharing
  • Loading branch information
stevehu committed Dec 29, 2021
1 parent 5a57bfa commit 418562a
Show file tree
Hide file tree
Showing 12 changed files with 2,912 additions and 0 deletions.
111 changes: 111 additions & 0 deletions src/main/java/com/networknt/openapi/ApiNormalisedPath.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* 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.networknt.openapi;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

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<String> pathParts;
private final String original;
private final String normalised;

@Deprecated
public ApiNormalisedPath(final String path) {
if (logger.isDebugEnabled()) logger.debug("path =" + path);
this.original = requireNonNull(path, "A path is required");
this.normalised = normalise(path, null);
if (logger.isDebugEnabled()) logger.debug("normalised = " + this.normalised);
this.pathParts = List.of(normalised.split("/"));
}

/**
* Construct normalized path
* @param path original path
* @param basePath the path you want to overwrite
* - put null will use the base path parsed from {@link OpenApiHelper}
* - put "" empty String will not trigger any overwrites
* - put regex will replace the first basePath with empty String
*/
public ApiNormalisedPath(final String path, final String basePath) {
if (logger.isDebugEnabled()) logger.debug("path = {}, base path is set to: {}", path, basePath);
this.original = requireNonNull(path, "A path is required");
this.normalised = normalise(path, basePath);
if (logger.isDebugEnabled()) logger.debug("normalised = " + this.normalised);
this.pathParts = List.of(normalised.split("/"));
}

@Override
public List<String> 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;
}

// case basePath == null, use OpenApiHelper.basePath
// case basePath != null, remove the basePath
private String normalise(String requestPath, String basePath) {
if (basePath == null) {
basePath = OpenApiHelper.openApi3 == null ? "" : OpenApiHelper.basePath;
}
requestPath = requestPath.replaceFirst(basePath, "");

if (!requestPath.startsWith("/")) {
return "/" + requestPath;
}
return requestPath;
}
}
66 changes: 66 additions & 0 deletions src/main/java/com/networknt/openapi/NormalisedPath.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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.networknt.openapi;

import java.util.List;

/**
* A normalised representation of an API path.
* <p>
* Normalised paths are devoid of path prefixes and contain a normalised starting/ending
* slash to make comparisons easier.
*
* @author Steve Hu
*/
public interface NormalisedPath {

/**
* @return The path parts from the normalised path
*/
List<String> parts();

/**
* @return The path part at the given index
* @param index position of part
* @throws IndexOutOfBoundsException if the provided index is not a valid index
*/
String part(int index);

/**
* @return Whether the path part at the given index is a path param (e.g. "/my/{param}/")
* @param index position of part
* @throws IndexOutOfBoundsException if the provided index is not a valid index
*/
boolean isParam(int index);

/**
* @return The parameter name of the path part at the given index, or <code>null</code>
* @param index position of part
* @throws IndexOutOfBoundsException if the provided index is not a valid index
*/
String paramName(int index);

/**
* @return The original, un-normalised path string
*/
String original();

/**
* @return The normalised path string, with prefixes removed and a standard treatment for leading/trailing slashed.
*/
String normalised();
}
175 changes: 175 additions & 0 deletions src/main/java/com/networknt/openapi/OpenApiHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* 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.networknt.openapi;

import com.networknt.oas.OpenApiParser;
import com.networknt.oas.model.OpenApi3;
import com.networknt.oas.model.SecurityScheme;
import com.networknt.oas.model.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;

/**
* This class load and cache openapi.json in a static block so that it can be
* shared by security for scope validation and validator for schema validation
*
* This handler supports openapi.yml, openapi.yaml and openapi.json and above
* is the loading sequence.
*
* @author Steve Hu
*/
public class OpenApiHelper {
static final Logger logger = LoggerFactory.getLogger(OpenApiHelper.class);

public static OpenApi3 openApi3;
public static List<String> oauth2Names;
public static String basePath;
private static OpenApiHelper INSTANCE = null;

private OpenApiHelper(String spec) {
try {
openApi3 = (OpenApi3) new OpenApiParser().parse(spec, new URL("https://oas.lightapi.net/"));
} catch (MalformedURLException e) {
logger.error("MalformedURLException", e);
}
if(openApi3 == null) {
logger.error("Unable to load openapi.json");
throw new RuntimeException("Unable to load openapi.json");
} else {
oauth2Names = getOAuth2Name();
basePath = getBasePath();
}

}

public static OpenApiHelper getInstance() {
if(INSTANCE == null) {
return null;
}
return INSTANCE;
}

public synchronized static OpenApiHelper init(String spec) {
if(INSTANCE != null) {
return INSTANCE;
}
INSTANCE = new OpenApiHelper(spec);
return INSTANCE;
}

/**
* merge inject map to openapi map
* @param openapi {@link Map} openapi
* @param inject {@link Map} openapi
*/
public static void merge(Map<String, Object> openapi, Map<String, Object> inject) {
if (inject == null) {
return;
}
for (Map.Entry<String, Object> entry : inject.entrySet()) {
openapi.merge(entry.getKey(), entry.getValue(), new Merger());
}
}

// merge in case of map, add in case of list
static class Merger implements BiFunction {
@Override
public Object apply(Object o, Object i) {
if (o instanceof Map && i instanceof Map) {
for (Map.Entry<String, Object> entry : ((Map<String, Object>) i).entrySet()) {
((Map<String, Object>) o).merge(entry.getKey(), entry.getValue(), new Merger());
}
} else if (o instanceof List && i instanceof List) {
((List<Object>) o).addAll((List)i);
} else {
// if has the same key, return the injected
return i;
}
return o;
}
}

public Optional<NormalisedPath> findMatchingApiPath(final NormalisedPath requestPath) {
if(OpenApiHelper.openApi3 != null) {
return OpenApiHelper.openApi3.getPaths().keySet()
.stream()
.map(p -> (NormalisedPath) new ApiNormalisedPath(p))
.filter(p -> pathMatches(requestPath, p))
.findFirst();
} else {
return Optional.empty();
}
}

private List<String> getOAuth2Name() {
List<String> names = new ArrayList<>();
Map<String, SecurityScheme> defMap = openApi3.getSecuritySchemes();
if(defMap != null) {
for(Map.Entry<String, SecurityScheme> entry : defMap.entrySet()) {
if(entry.getValue().getType().equals("oauth2")) {
names.add(entry.getKey());
}
}
}
return names;
}

private 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("://");
if (protocolIndex != -1) {
int pathIndex = url.indexOf('/', protocolIndex + 3);
if (pathIndex > 0) {
basePath = url.substring(pathIndex);
}
} else {
// support openapi Relative URLs
basePath = url.startsWith("/") ? url : "";
}
}
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;
}
}
Loading

0 comments on commit 418562a

Please sign in to comment.