diff --git a/core/src/main/java/com/predic8/membrane/core/exchange/Exchange.java b/core/src/main/java/com/predic8/membrane/core/exchange/Exchange.java index 99b42857f..561281386 100644 --- a/core/src/main/java/com/predic8/membrane/core/exchange/Exchange.java +++ b/core/src/main/java/com/predic8/membrane/core/exchange/Exchange.java @@ -77,7 +77,7 @@ public Exchange(AbstractHttpHandler handler) { /** * For HttpResendRunnable * - * @param original + * @param original Exchange */ public Exchange(Exchange original, AbstractHttpHandler handler) { super(original); @@ -234,7 +234,7 @@ public long getId() { } @Override - public AbstractExchange createSnapshot(Runnable bodyUpdatedCallback, BodyCollectingMessageObserver.Strategy strategy, long limit) throws Exception { + public AbstractExchange createSnapshot(Runnable bodyUpdatedCallback, BodyCollectingMessageObserver.Strategy strategy, long limit) { Exchange exc = updateCopy(this, new Exchange(null), bodyUpdatedCallback, strategy, limit); exc.setId(this.getId()); return exc; @@ -259,4 +259,11 @@ public Exception[] getNodeExceptions() { public void setNodeExceptions(Exception[] nodeExceptions) { this.nodeExceptions = nodeExceptions; } + + public String getInboundProtocol() { + if (getRule().getSslInboundContext() == null) + return "http"; + else + return "https"; + } } diff --git a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxy.java b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxy.java index 6a839fe61..9982e864b 100644 --- a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxy.java +++ b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxy.java @@ -28,8 +28,6 @@ import java.net.*; import java.util.*; -import static com.predic8.membrane.core.openapi.serviceproxy.APIProxy.Spec.YesNoOpenAPIOption.*; - /** * @description The APIProxy extends the serviceProxy with API related functions like OpenAPI support. * @@ -59,100 +57,10 @@ protected AbstractProxy getNewInstance() { return new APIProxy(); } - /** - * @description Reads an OpenAPI description and deploys an API with the information of it. - */ - @MCElement(name = "openapi", topLevel = false) - public static class Spec { - - String location; - String dir; - YesNoOpenAPIOption validateRequests = ASINOPENAPI; - YesNoOpenAPIOption validateResponses = ASINOPENAPI; - YesNoOpenAPIOption validationDetails = ASINOPENAPI; - - public Spec() { - } - - public String getLocation() { - return location; - } - - /** - * @description Filename or URL pointing to an OpenAPI document. Relative filenames use the %MEMBRANE_HOME%/conf folder as base directory. - * @example openapi/fruitstore-v1.yaml, https://api.predic8.de/shop/swagger - */ - @MCAttribute() - public void setLocation(String location) { - this.location = location; - } - - public String getDir() { - return dir; - } - - /** - * @description Directory containing OpenAPI definitions to deploy. - * @example openapi - */ - @MCAttribute() - public void setDir(String dir) { - this.dir = dir; - } - - public YesNoOpenAPIOption getValidateRequests() { - return validateRequests; - } - - /** - * @description Turn validation of requests on or off. - * @example yes - * @default no - */ - @MCAttribute - public void setValidateRequests(YesNoOpenAPIOption validateRequests) { - this.validateRequests = validateRequests; - } - - @SuppressWarnings("unused") - public YesNoOpenAPIOption getValidateResponses() { - return validateResponses; - } - - /** - * @description Turn validation of responses on or off. - * @example yes - * @default no - */ - @MCAttribute() - public void setValidateResponses(YesNoOpenAPIOption validateResponses) { - this.validateResponses = validateResponses; - } - - /** - * @description Show details of the validation to the caller. - * @example yes - * @default no - */ - @MCAttribute() - public void setValidationDetails(YesNoOpenAPIOption validationDetails) { - this.validationDetails = validationDetails; - } - - public YesNoOpenAPIOption getValidationDetails() { - return validationDetails; - } - - public enum YesNoOpenAPIOption { - YES, - NO, - ASINOPENAPI - } - } - protected List specs = new ArrayList<>(); + protected List specs = new ArrayList<>(); - public List getSpecs() { + public List getSpecs() { return specs; } @@ -160,7 +68,7 @@ public List getSpecs() { * @description Deploys an API from an OpenAPI document. */ @MCChildElement(order = 25) - public void setSpecs(List specs) { + public void setSpecs(List specs) { this.specs = specs; } diff --git a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptor.java b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptor.java index ea85de929..d70c76448 100644 --- a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptor.java @@ -43,10 +43,10 @@ public class OpenAPIInterceptor extends AbstractInterceptor { - protected final APIProxy proxy; + protected final APIProxy apiProxy; - public OpenAPIInterceptor(APIProxy proxy) { - this.proxy = proxy; + public OpenAPIInterceptor(APIProxy apiProxy) { + this.apiProxy = apiProxy; } @Override @@ -61,7 +61,7 @@ public Outcome handleRequest(Exchange exc) throws Exception { return RETURN; } - OpenAPIRecord rec = proxy.getBasePaths().get(basePath); + OpenAPIRecord rec = apiProxy.getBasePaths().get(basePath); // If OpenAPIProxy has a Element use this for routing otherwise // take the urls from the info.servers field in the OpenAPI document. @@ -71,7 +71,7 @@ public Outcome handleRequest(Exchange exc) throws Exception { ValidationErrors errors = validateRequest(rec.api, exc); if (!errors.isEmpty()) { - proxy.statisticCollector.collect(errors); + apiProxy.statisticCollector.collect(errors); return returnErrors(exc, errors, REQUEST, validationDetails(rec.api)); } @@ -81,7 +81,7 @@ public Outcome handleRequest(Exchange exc) throws Exception { } private boolean hasProxyATargetElement() { - return proxy.getTarget() != null && (proxy.getTarget().getHost() != null || proxy.getTarget().getUrl() != null); + return apiProxy.getTarget() != null && (apiProxy.getTarget().getHost() != null || apiProxy.getTarget().getUrl() != null); } @Override @@ -92,14 +92,14 @@ public Outcome handleResponse(Exchange exc) throws Exception { if (errors != null && errors.hasErrors()) { exc.getResponse().setStatusCode(500); // A validation error in the response is a server error! - proxy.statisticCollector.collect(errors); + apiProxy.statisticCollector.collect(errors); return returnErrors(exc, errors, RESPONSE, validationDetails(rec.api)); } return CONTINUE; } protected String getMatchingBasePath(Exchange exc) { - for (String basePath : proxy.getBasePaths().keySet()) { + for (String basePath : apiProxy.getBasePaths().keySet()) { if (exc.getRequest().getUri().startsWith(basePath)) { return basePath; } @@ -196,7 +196,7 @@ public String getLongDescription() { sb.append(""); sb.append(""); - for (Map.Entry entry : proxy.getBasePaths().entrySet()) { + for (Map.Entry entry : apiProxy.getBasePaths().entrySet()) { sb.append(""); sb.append("
APIBase PathValidation
"); sb.append(entry.getValue().api.getInfo().getTitle()); diff --git a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptor.java b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptor.java index 0f1a6d41b..802129a8b 100644 --- a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptor.java @@ -36,7 +36,6 @@ import static com.predic8.membrane.core.http.MimeType.*; import static com.predic8.membrane.core.interceptor.Outcome.*; import static com.predic8.membrane.core.openapi.util.OpenAPIUtil.*; -import static com.predic8.membrane.core.openapi.util.UriUtil.*; import static com.predic8.membrane.core.openapi.util.Utils.*; public class OpenAPIPublisherInterceptor extends AbstractInterceptor { @@ -83,7 +82,7 @@ public Outcome handleRequest(Exchange exc) throws Exception { return handleOverviewOpenAPIDoc(exc); } - private Outcome handleOverviewOpenAPIDoc(Exchange exc) throws JsonProcessingException, URISyntaxException { + private Outcome handleOverviewOpenAPIDoc(Exchange exc) throws IOException, URISyntaxException { Matcher m = patternMeta.matcher(exc.getRequest().getUri()); if (!m.matches()) { // No id specified if (acceptsHtmlExplicit(exc)) { @@ -119,95 +118,31 @@ private Outcome returnNoFound(Exchange exc, String id) { return RETURN; } - private Outcome returnOpenApiAsYaml(Exchange exc, OpenAPIRecord rec) throws JsonProcessingException, URISyntaxException { - rewriteOpenAPIaccordingToRequest(exc, rec); + private Outcome returnOpenApiAsYaml(Exchange exc, OpenAPIRecord rec) throws IOException, URISyntaxException { + rec.rewriteOpenAPI(exc, getRouter().getUriFactory()); exc.setResponse(Response.ok().contentType(APPLICATION_X_YAML).body(omYaml.writeValueAsBytes(rec.node)).build()); return RETURN; } - protected void rewriteOpenAPIaccordingToRequest(Exchange exc, OpenAPIRecord rec) throws URISyntaxException { - rewriteOpenAPIVersion3(exc, rec); - rewriteSwaggerVersion2(exc, rec); - } - - private void rewriteSwaggerVersion2(Exchange exc, OpenAPIRecord rec) { - // Rewrite OpenAPI 2.X - JsonNode host = rec.node.get("host"); - if (host == null) - return; - - String rewrittenHost = rewriteHost(exc); - ((ObjectNode) rec.node).put("host", rewrittenHost); - - // Add protocol http or https - ArrayNode schemes = ((ObjectNode) rec.node).putArray("schemes"); - schemes.add(getProtocol(exc)); - - log.debug("Rewriting {} to {}", host, rewrittenHost); - } - - private void rewriteOpenAPIVersion3(Exchange exc, OpenAPIRecord rec) throws URISyntaxException { - JsonNode servers = rec.node.get("servers"); - if (servers == null) - return; - - for (JsonNode server : servers) { - String serverUrl = server.get("url").asText(); - String rewrittenUrl = rewriteUrl(exc, serverUrl); - ((ObjectNode) server).put("url", rewrittenUrl); - log.debug("Rewriting {} to {}", serverUrl, rewrittenUrl); - } - } - - /** - * Rewrites URL from OpenAPI 3.X - * @param exc Exchange - * @param url URL to rewrite - * @return Rewritten URL - * @throws URISyntaxException syntax error ín URL - */ - protected String rewriteUrl(Exchange exc, String url) throws URISyntaxException { - return rewrite(router.getUriFactory(), url, - getProtocol(exc), - exc.getOriginalHostHeaderHost(), - exc.getOriginalHostHeaderPort()); - } - - /** - * Rewrites Host from Swagger 2.X - * @param exc Exchange - * @return Rewritten host with port - */ - protected String rewriteHost(Exchange exc) { - return exc.getOriginalHostHeaderHost() + ":" + exc.getOriginalHostHeaderPort(); - } - - private String getProtocol(Exchange exc) { - if (exc.getRule().getSslInboundContext() == null) - return "http"; - else - return "https"; - } - private Outcome handleSwaggerUi(Exchange exc) { Matcher m = patternUI.matcher(exc.getRequest().getUri()); // No id specified if (!m.matches()) { - Map details = new HashMap<>(); - details.put("message","Please specify an id of an OpenAPI document. Path should match this pattern: /api-doc/ui/<>"); - exc.setResponse(createProblemDetails(404,"/openapi/wrong-id","No OpenAPI document id",details)); + Map details = new HashMap<>(); + details.put("message", "Please specify an id of an OpenAPI document. Path should match this pattern: /api-doc/ui/<>"); + exc.setResponse(createProblemDetails(404, "/openapi/wrong-id", "No OpenAPI document id", details)); return RETURN; } // /api-doc/ui/(.*) String id = m.group(1); - log.info("OpenAPI with id {} requested",id); + log.info("OpenAPI with id {} requested", id); OpenAPIRecord record = apis.get(id); if (record == null) { - return returnNoFound(exc,id); + return returnNoFound(exc, id); } exc.setResponse(Response.ok().contentType(HTML_UTF_8).body(renderSwaggerUITemplate(id, record.api)).build()); diff --git a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIRecord.java b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIRecord.java index ec1d28ac0..b0a6452e2 100644 --- a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIRecord.java +++ b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIRecord.java @@ -17,7 +17,13 @@ package com.predic8.membrane.core.openapi.serviceproxy; import com.fasterxml.jackson.databind.*; +import com.predic8.membrane.core.exchange.*; +import com.predic8.membrane.core.util.*; import io.swagger.v3.oas.models.*; +import org.slf4j.*; + +import java.io.*; +import java.net.*; import static com.predic8.membrane.core.openapi.util.OpenAPIUtil.*; @@ -33,15 +39,23 @@ public class OpenAPIRecord { */ JsonNode node; + OpenAPISpec spec; + /** * Version of the OpenAPI standard e.g. 2.0, 3.0.1 */ String version; - public OpenAPIRecord(OpenAPI api, JsonNode node) { + /** + * Used for tests + */ + public OpenAPIRecord() {} + + public OpenAPIRecord(OpenAPI api, JsonNode node, OpenAPISpec spec) { this.api = api; this.node = node; this.version = getOpenAPIVersion(node); + this.spec = spec; } public boolean isVersion2() { @@ -51,4 +65,9 @@ public boolean isVersion2() { public boolean isVersion3() { return version.startsWith("3"); } + + public JsonNode rewriteOpenAPI(Exchange exc, URIFactory uriFactory) throws URISyntaxException, IOException { + return spec.rewrite.rewrite(this,exc,uriFactory); + } + } diff --git a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIRecordFactory.java b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIRecordFactory.java index e57e9d51a..66e4f415e 100644 --- a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIRecordFactory.java +++ b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIRecordFactory.java @@ -31,7 +31,8 @@ import java.util.*; import static com.predic8.membrane.core.openapi.serviceproxy.APIProxy.*; -import static com.predic8.membrane.core.openapi.serviceproxy.APIProxy.Spec.YesNoOpenAPIOption.*; +import static com.predic8.membrane.core.openapi.serviceproxy.OpenAPISpec.YesNoOpenAPIOption.ASINOPENAPI; +import static com.predic8.membrane.core.openapi.serviceproxy.OpenAPISpec.YesNoOpenAPIOption.YES; import static com.predic8.membrane.core.openapi.util.OpenAPIUtil.*; import static com.predic8.membrane.core.util.FileUtil.*; import static java.lang.String.*; @@ -48,9 +49,9 @@ public OpenAPIRecordFactory(Router router) { this.router = router; } - public Map create(Collection specs) throws IOException { + public Map create(Collection specs) throws IOException { Map apiRecords = new LinkedHashMap<>(); - for (Spec spec : specs) { + for (OpenAPISpec spec : specs) { // Maybe a spec has both location and dir. addOpenApisFromLocation(apiRecords, spec); addOpenApisFromDirectory(apiRecords, spec); @@ -58,7 +59,7 @@ public Map create(Collection specs) throws IOExcept return apiRecords; } - private void addOpenApisFromDirectory(Map apiRecords, Spec spec) throws IOException { + private void addOpenApisFromDirectory(Map apiRecords, OpenAPISpec spec) throws IOException { if (spec.dir == null) return; @@ -75,7 +76,7 @@ private void addOpenApisFromDirectory(Map apiRecords, Spe } } - private void addOpenApisFromLocation(Map apiRecords, Spec spec) { + private void addOpenApisFromLocation(Map apiRecords, OpenAPISpec spec) { if (spec.location == null) return; @@ -112,19 +113,19 @@ private String getUniqueId(Map apiRecords, OpenAPIRecord return id; } - private OpenAPIRecord create(Spec spec) throws IOException { - OpenAPIRecord record = new OpenAPIRecord(getOpenAPI(router, spec), getSpec(router, spec)); + private OpenAPIRecord create(OpenAPISpec spec) throws IOException { + OpenAPIRecord record = new OpenAPIRecord(getOpenAPI(router, spec), getSpec(router, spec),spec); setExtentsionOnAPI(spec, record.api); return record; } - private OpenAPIRecord create(Spec spec, File file) throws IOException { - OpenAPIRecord record = new OpenAPIRecord(parseFileAsOpenAPI(file), getSpec(file)); + private OpenAPIRecord create(OpenAPISpec spec, File file) throws IOException { + OpenAPIRecord record = new OpenAPIRecord(parseFileAsOpenAPI(file), getSpec(file),spec); setExtentsionOnAPI(spec, record.api); return record; } - private OpenAPI getOpenAPI(Router router, Spec spec) throws ResourceRetrievalException { + private OpenAPI getOpenAPI(Router router, OpenAPISpec spec) throws ResourceRetrievalException { OpenAPI openAPI = new OpenAPIParser().readContents(readInputStream(getInputStreamForLocation(router, spec.location)), null, null).getOpenAPI(); @@ -143,7 +144,7 @@ private InputStream getInputStreamForLocation(Router router, String location) th return router.getResolverMap().resolve(ResolverMap.combine(router.getBaseLocation(), location)); } - private JsonNode getSpec(Router router, Spec spec) throws IOException { + private JsonNode getSpec(Router router, OpenAPISpec spec) throws IOException { return omYaml.readTree(getInputStreamForLocation(router, spec.location)); } @@ -151,7 +152,7 @@ private JsonNode getSpec(File file) throws IOException { return omYaml.readTree(file); } - private void setExtentsionOnAPI(Spec spec, OpenAPI api) { + private void setExtentsionOnAPI(OpenAPISpec spec, OpenAPI api) { if (api.getExtensions() == null) { api.setExtensions(new HashMap<>()); } @@ -170,7 +171,7 @@ private Map getXValidationExtension(OpenAPI api) { return extension; } - private Map updateExtension(Map extension, Spec spec) { + private Map updateExtension(Map extension, OpenAPISpec spec) { if (spec.validationDetails != ASINOPENAPI) extension.put(VALIDATION_DETAILS, toYesNo(spec.validationDetails)); @@ -187,7 +188,7 @@ private Map updateExtension(Map extension, Spec return extension; } - private boolean toYesNo(Spec.YesNoOpenAPIOption option) { + private boolean toYesNo(OpenAPISpec.YesNoOpenAPIOption option) { return option == YES; } diff --git a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPISpec.java b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPISpec.java new file mode 100644 index 000000000..970f20541 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPISpec.java @@ -0,0 +1,122 @@ +/* + * Copyright 2022 predic8 GmbH, www.predic8.com + * + * 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.predic8.membrane.core.openapi.serviceproxy; + +import com.predic8.membrane.annot.*; + +import static com.predic8.membrane.core.openapi.serviceproxy.OpenAPISpec.YesNoOpenAPIOption.ASINOPENAPI; + +/** + * @description Reads an OpenAPI description and deploys an API with the information of it. + */ +@MCElement(name = "openapi", topLevel = false) +public class OpenAPISpec { + + public String location; + public String dir; + public YesNoOpenAPIOption validateRequests = ASINOPENAPI; + YesNoOpenAPIOption validateResponses = ASINOPENAPI; + YesNoOpenAPIOption validationDetails = ASINOPENAPI; + Rewrite rewrite = new Rewrite(); + + public OpenAPISpec() { + } + + @MCChildElement(order = 50) + public void setRewrite(Rewrite rewrite) { + this.rewrite = rewrite; + } + + public Rewrite getRewrite() { + return rewrite; + } + + public String getLocation() { + return location; + } + + /** + * @description Filename or URL pointing to an OpenAPI document. Relative filenames use the %MEMBRANE_HOME%/conf folder as base directory. + * @example openapi/fruitstore-v1.yaml, https://api.predic8.de/shop/swagger + */ + @MCAttribute() + public void setLocation(String location) { + this.location = location; + } + + public String getDir() { + return dir; + } + + /** + * @description Directory containing OpenAPI definitions to deploy. + * @example openapi + */ + @MCAttribute() + public void setDir(String dir) { + this.dir = dir; + } + + public YesNoOpenAPIOption getValidateRequests() { + return validateRequests; + } + + /** + * @description Turn validation of requests on or off. + * @example yes + * @default no + */ + @MCAttribute + public void setValidateRequests(YesNoOpenAPIOption validateRequests) { + this.validateRequests = validateRequests; + } + + @SuppressWarnings("unused") + public YesNoOpenAPIOption getValidateResponses() { + return validateResponses; + } + + /** + * @description Turn validation of responses on or off. + * @example yes + * @default no + */ + @MCAttribute() + public void setValidateResponses(YesNoOpenAPIOption validateResponses) { + this.validateResponses = validateResponses; + } + + /** + * @description Show details of the validation to the caller. + * @example yes + * @default no + */ + @MCAttribute() + public void setValidationDetails(YesNoOpenAPIOption validationDetails) { + this.validationDetails = validationDetails; + } + + public YesNoOpenAPIOption getValidationDetails() { + return validationDetails; + } + + public enum YesNoOpenAPIOption { + YES, + NO, + ASINOPENAPI + } +} \ No newline at end of file diff --git a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/Rewrite.java b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/Rewrite.java new file mode 100644 index 000000000..c9b33741a --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/Rewrite.java @@ -0,0 +1,175 @@ +/* + * Copyright 2023 predic8 GmbH, www.predic8.com + * + * 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.predic8.membrane.core.openapi.serviceproxy; + +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.node.*; +import com.predic8.membrane.annot.*; +import com.predic8.membrane.core.exchange.*; +import com.predic8.membrane.core.openapi.util.*; +import com.predic8.membrane.core.util.*; +import org.slf4j.*; + +import java.io.*; +import java.net.*; + +import static java.util.Objects.*; + +/** + * @description + */ +@MCElement(name = "rewrite", topLevel = false) +public class Rewrite { + + private static final Logger log = LoggerFactory.getLogger(Rewrite.class.getName()); + + Integer port; + String protocol; + String host; + + public JsonNode rewrite(OpenAPIRecord rec, Exchange exc, URIFactory uriFactory) throws URISyntaxException, IOException { + if (rec.isVersion3()) + return rewriteOpenAPI3(exc, uriFactory, rec.node); + + return rewriteSwagger2(exc, rec.node); + } + + private JsonNode rewriteOpenAPI3(Exchange exc, URIFactory uriFactory, JsonNode node) throws URISyntaxException { + // Expensive cloning before null check is fine cause most of OpenAPIs will have servers. + // Caching should not be needed cause the OpenAPI is not so often retrieved. Maybe practice + // will prove that wrong. + JsonNode rewritten = node.deepCopy(); + for (JsonNode server : rewritten.get("servers")) { + rewriteServerEntry(exc, uriFactory, server); + } + return rewritten; + } + + private void rewriteServerEntry(Exchange exc, URIFactory uriFactory, JsonNode server) throws URISyntaxException { + String url = server.get("url").asText(); + String rewritten = rewriteUrl(exc, url, uriFactory); + ((ObjectNode) server).put("url", rewritten); + log.debug("Rewriting {} to {}", url, rewritten); + } + + /** + * Rewrites URL from OpenAPI 3.X + * + * @param exc Exchange + * @param url URL to rewrite + * @param uriFactory URIFactory + * @return Rewritten URL + * @throws URISyntaxException syntax error ín URL + */ + protected String rewriteUrl(Exchange exc, String url, URIFactory uriFactory) throws URISyntaxException { + return UriUtil.rewrite(uriFactory, url, rewriteProtocol(exc.getInboundProtocol()), rewriteHost(exc.getOriginalHostHeaderHost()), rewritePort(exc.getOriginalHostHeaderPort())); + } + + private JsonNode rewriteSwagger2(Exchange exc, JsonNode node) { + JsonNode rewritten = node.deepCopy(); + rewriteHostAndPortSwagger2(exc, rewritten); + rewriteSchemeSwagger2(exc, rewritten); + return rewritten; + } + + private void rewriteSchemeSwagger2(Exchange exc, JsonNode node) { + // Add protocol http or https + ArrayNode schemes = ((ObjectNode) node).putArray("schemes"); + schemes.add(exc.getInboundProtocol()); + } + + private void rewriteHostAndPortSwagger2(Exchange exc, JsonNode rewrittenJson) { + JsonNode host = rewrittenJson.get("host"); + if (host == null) + return; + + String rewrittenHost = getRewrittenHostAndPortSwagger2(exc); + ((ObjectNode) rewrittenJson).put("host", rewrittenHost); + log.debug("Rewriting {} to {}", host, rewrittenHost); + } + + /** + * Rewrites Host from Swagger 2.X + * + * @param exc Exchange + * @return Rewritten host with port + */ + protected String getRewrittenHostAndPortSwagger2(Exchange exc) { + return rewriteHost(exc.getOriginalHostHeaderHost()) + ":" + rewritePort(exc.getOriginalHostHeaderPort()); + } + + /** + * Rewrites the protocol if there was a value given. + * + * @param protocol from the OpenAPI + * @return rewritten value + */ + public String rewriteProtocol(String protocol) { + return requireNonNullElse(this.protocol, protocol); + } + + /** + * Rewrites the host if there was a value given. + * + * @param host from the OpenAPI + * @return rewritten value + */ + public String rewriteHost(String host) { + return requireNonNullElse(this.host, host); + } + + /** + * Rewrites the port if there was a value given. + * + * @param port from the OpenAPI + * @return rewritten value + */ + public String rewritePort(String port) { + if (this.port != null) { + return this.port.toString(); + } + return port; + } + + + public int getPort() { + return port; + } + + @MCAttribute() + public void setPort(int port) { + this.port = port; + } + + public String getProtocol() { + return protocol; + } + + @MCAttribute() + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public String getHost() { + return host; + } + + @MCAttribute() + public void setHost(String host) { + this.host = host; + } +} diff --git a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxyTest.java b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxyTest.java index 8dad6b7cd..c876446ac 100644 --- a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxyTest.java +++ b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxyTest.java @@ -22,8 +22,9 @@ import java.util.*; -import static com.predic8.membrane.core.openapi.serviceproxy.APIProxy.Spec.YesNoOpenAPIOption.*; import static com.predic8.membrane.core.openapi.serviceproxy.APIProxy.*; +import static com.predic8.membrane.core.openapi.serviceproxy.OpenAPISpec.YesNoOpenAPIOption.NO; +import static com.predic8.membrane.core.openapi.serviceproxy.OpenAPISpec.YesNoOpenAPIOption.YES; import static com.predic8.membrane.core.openapi.util.TestUtils.*; import static org.junit.jupiter.api.Assertions.*; @@ -39,7 +40,7 @@ public void setUp() { @Test public void noOptionsNoExtensions() throws Exception { - APIProxy.Spec spec = new APIProxy.Spec(); + OpenAPISpec spec = new OpenAPISpec(); spec.location = "src/test/resources/openapi/openapi-proxy/no-extensions.yml"; APIProxy proxy = createProxy(router,spec); @@ -61,7 +62,7 @@ public void noOptionsNoExtensions() throws Exception { @Test public void validationRequestNoExtensions() throws Exception { - APIProxy.Spec spec = new APIProxy.Spec(); + OpenAPISpec spec = new OpenAPISpec(); spec.location = "src/test/resources/openapi/openapi-proxy/no-extensions.yml"; spec.validateRequests = YES; @@ -83,7 +84,7 @@ public void validationRequestNoExtensions() throws Exception { @Test public void validationResponsesNoExtensions() throws Exception { - APIProxy.Spec spec = new APIProxy.Spec(); + OpenAPISpec spec = new OpenAPISpec(); spec.location = "src/test/resources/openapi/openapi-proxy/no-extensions.yml"; spec.validateResponses = YES; @@ -106,7 +107,7 @@ public void validationResponsesNoExtensions() throws Exception { @Test public void validationAllNoExtensions() throws Exception { - APIProxy.Spec spec = new APIProxy.Spec(); + OpenAPISpec spec = new OpenAPISpec(); spec.location = "src/test/resources/openapi/openapi-proxy/no-extensions.yml"; spec.validateRequests = YES; spec.validateResponses = YES; @@ -129,7 +130,7 @@ public void validationAllNoExtensions() throws Exception { @Test public void requestsExtensions() throws Exception { - APIProxy.Spec spec = new APIProxy.Spec(); + OpenAPISpec spec = new OpenAPISpec(); spec.location = "src/test/resources/openapi/openapi-proxy/validate-requests-extensions.yml"; APIProxy proxy = createProxy(router,spec); @@ -150,7 +151,7 @@ public void requestsExtensions() throws Exception { @Test public void responsesExtensions() throws Exception { - APIProxy.Spec spec = new APIProxy.Spec(); + OpenAPISpec spec = new OpenAPISpec(); spec.location = "src/test/resources/openapi/openapi-proxy/validate-responses-extensions.yml"; APIProxy proxy = createProxy(router,spec); @@ -172,7 +173,7 @@ public void responsesExtensions() throws Exception { @Test public void validationRequestNoDetailsNoExtensions() throws Exception { - APIProxy.Spec spec = new APIProxy.Spec(); + OpenAPISpec spec = new OpenAPISpec(); spec.location = "src/test/resources/openapi/openapi-proxy/no-extensions.yml"; spec.validateRequests = YES; spec.validationDetails = NO; @@ -196,7 +197,7 @@ public void validationRequestNoDetailsNoExtensions() throws Exception { @Test public void validationDetailsFalseExtensions() throws Exception { - APIProxy.Spec spec = new APIProxy.Spec(); + OpenAPISpec spec = new OpenAPISpec(); spec.location = "src/test/resources/openapi/openapi-proxy/validation-details-false-extensions.yml"; spec.validateRequests = YES; @@ -238,8 +239,8 @@ public void oneOpenAPIWithMultipleServerUrlsSharingTheSamePath() throws Exceptio api.setSpecs(List.of(extracted("api-c-multiple-server-urls.yml"))); } - private APIProxy.Spec extracted(String location) { - APIProxy.Spec spec = new APIProxy.Spec(); + private OpenAPISpec extracted(String location) { + OpenAPISpec spec = new OpenAPISpec(); spec.location = location; return spec; } diff --git a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptorTest.java b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptorTest.java index 38461faea..ed667e189 100644 --- a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptorTest.java @@ -29,8 +29,6 @@ import static com.predic8.membrane.core.http.MimeType.APPLICATION_JSON; import static com.predic8.membrane.core.http.MimeType.APPLICATION_PROBLEM_JSON; import static com.predic8.membrane.core.interceptor.Outcome.*; -import static com.predic8.membrane.core.openapi.serviceproxy.APIProxy.Spec.YesNoOpenAPIOption.NO; -import static com.predic8.membrane.core.openapi.serviceproxy.APIProxy.Spec.YesNoOpenAPIOption.YES; import static com.predic8.membrane.core.openapi.util.JsonUtil.*; import static com.predic8.membrane.core.openapi.util.TestUtils.*; import static org.junit.jupiter.api.Assertions.*; @@ -39,9 +37,9 @@ public class OpenAPIInterceptorTest { Router router; - APIProxy.Spec specInfoServers; - APIProxy.Spec specInfo3Servers; - APIProxy.Spec specCustomers; + OpenAPISpec specInfoServers; + OpenAPISpec specInfo3Servers; + OpenAPISpec specCustomers; Exchange exc = new Exchange(null); OpenAPIInterceptor interceptor1Server; @@ -52,13 +50,13 @@ public void setUp() throws Exception { router = new Router(); router.setUriFactory(new URIFactory()); - specInfoServers = new APIProxy.Spec(); + specInfoServers = new OpenAPISpec(); specInfoServers.location = "src/test/resources/openapi/specs/info-servers.yml"; - specInfo3Servers = new APIProxy.Spec(); + specInfo3Servers = new OpenAPISpec(); specInfo3Servers.location = "src/test/resources/openapi/specs/info-3-servers.yml"; - specCustomers = new APIProxy.Spec(); + specCustomers = new OpenAPISpec(); specCustomers.location = "src/test/resources/openapi/specs/customers.yml"; exc.setRequest(new Request.Builder().method("GET").build()); @@ -128,7 +126,7 @@ public void destinationsTargetSet() throws Exception { @Test public void validateRequest() throws Exception { - specCustomers.validateRequests = YES; + specCustomers.validateRequests = OpenAPISpec.YesNoOpenAPIOption.YES; Map customer = new HashMap<>(); customer.put("id","CUST-7"); @@ -151,20 +149,20 @@ public void validateRequest() throws Exception { @SuppressWarnings({"unchecked"}) @Test public void validateResponse() throws Exception { - specCustomers.validateResponses = YES; + specCustomers.validateResponses = OpenAPISpec.YesNoOpenAPIOption.YES; assertEquals("PUT", getMapFromResponse(callPut(specCustomers)).get("method")); testValidationResults(getMapFromResponse(callPut(specCustomers)), "RESPONSE"); } @Test public void validateResponseLessDetails() throws Exception { - specCustomers.validateResponses = YES; - specCustomers.validationDetails = NO; + specCustomers.validateResponses = OpenAPISpec.YesNoOpenAPIOption.YES; + specCustomers.validationDetails = OpenAPISpec.YesNoOpenAPIOption.NO; assertEquals("Message validation failed!", getMapFromResponse(callPut(specCustomers)).get("error")); } @NotNull - private Exchange callPut(APIProxy.Spec spec) throws Exception { + private Exchange callPut(OpenAPISpec spec) throws Exception { Exchange exc = new Exchange(null); exc.setOriginalRequestUri("/customers"); exc.setRequest(new Request.Builder().method("PUT").url(new URIFactory(), "/customers").contentType(APPLICATION_JSON).build()); diff --git a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptorTest.java b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptorTest.java index 38b3e5d04..7df3fc40e 100644 --- a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptorTest.java @@ -27,7 +27,6 @@ import org.junit.jupiter.api.*; import java.io.*; -import java.net.*; import java.util.*; import static com.predic8.membrane.core.http.MimeType.*; @@ -50,11 +49,11 @@ void setUp() throws Exception { router.setUriFactory(new URIFactory()); router.setBaseLocation(""); openAPIRecordFactory = new OpenAPIRecordFactory(router); - APIProxy.Spec spec = new APIProxy.Spec(); + OpenAPISpec spec = new OpenAPISpec(); spec.setDir("src/test/resources/openapi/specs"); records = openAPIRecordFactory.create(Collections.singletonList(spec)); - interceptor = new OpenAPIPublisherInterceptor(records); + interceptor = new OpenAPIPublisherInterceptor( records); interceptor.init(router); get.setRequest(new Request.Builder().method("GET").build()); @@ -64,7 +63,7 @@ void setUp() throws Exception { @Test public void constuctor() { - assertEquals(27, interceptor.apis.size()); + assertTrue(interceptor.apis.size() >= 27); assertNotNull(interceptor.apis.get("references-test-v1-0")); assertNotNull(interceptor.apis.get("strings-test-api-v1-0")); assertNotNull(interceptor.apis.get("extension-sample-v1-4")); @@ -77,7 +76,7 @@ public void constuctor() { public void getApiDirectory() throws Exception { get.getRequest().setUri(OpenAPIPublisherInterceptor.PATH); assertEquals( RETURN, interceptor.handleRequest(get)); - assertEquals(27, TestUtils.getMapFromResponse(get).size()); + assertTrue(TestUtils.getMapFromResponse(get).size() >= 27); } @Test @@ -134,39 +133,4 @@ public void getApiById() throws Exception { private JsonNode getJsonFromYamlResponse(Exchange exc) throws IOException { return omYaml.readTree(exc.getResponse().getBody().getContent()); } - - @Test - public void rewriteOpenAPIAccordingToRequestTest() throws URISyntaxException { - OpenAPIRecord rec = records.get("servers-1-api-v1-0"); - interceptor.rewriteOpenAPIaccordingToRequest(get, rec); - assertEquals("http://api.predic8.de/base/v2",rec.node.get("servers").get(0).get("url").asText()); - assertEquals("Test System",rec.node.get("servers").get(0).get("description").asText()); - } - - @Test - public void rewriteOpenAPIAccordingToRequest3Servers() throws URISyntaxException { - OpenAPIRecord rec = records.get("servers-3-api-v1-0"); - interceptor.rewriteOpenAPIaccordingToRequest(get, rec); - assertEquals(3,rec.node.get("servers").size()); - assertEquals("http://api.predic8.de/foo",rec.node.get("servers").get(0).get("url").asText()); - assertEquals("http://api.predic8.de/foo",rec.node.get("servers").get(1).get("url").asText()); - assertEquals("http://api.predic8.de/foo",rec.node.get("servers").get(2).get("url").asText()); - } - - @Test - void rewriteRequestHostHeaderWithoutPort() throws URISyntaxException { - OpenAPIRecord rec = records.get("servers-3-api-v1-0"); - get.setOriginalHostHeader("api.predic8.de"); - interceptor.rewriteOpenAPIaccordingToRequest(get, rec); - JsonNode servers = rec.node.get("servers"); - assertEquals("http://api.predic8.de/foo", servers.get(0).get("url").textValue()); - assertEquals("http://api.predic8.de/foo", servers.get(1).get("url").textValue()); - assertEquals("http://api.predic8.de/foo", servers.get(2).get("url").textValue()); - } - - @Test - void rewriteUrl() throws URISyntaxException { - assertEquals("http://api.predic8.de/foo",interceptor.rewriteUrl(get,"http://localhost:3000/foo")); - assertEquals("http://api.predic8.de/foo",interceptor.rewriteUrl(get,"http://localhost/foo")); - } } \ No newline at end of file diff --git a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIRecordTest.java b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIRecordTest.java index e6371760d..579540d08 100644 --- a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIRecordTest.java +++ b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIRecordTest.java @@ -16,6 +16,11 @@ package com.predic8.membrane.core.openapi.serviceproxy; +import com.predic8.membrane.core.*; +import com.predic8.membrane.core.exchange.*; +import com.predic8.membrane.core.http.*; +import com.predic8.membrane.core.rules.*; +import com.predic8.membrane.core.util.*; import org.junit.jupiter.api.*; import java.io.*; @@ -25,16 +30,29 @@ class OpenAPIRecordTest { + Exchange get = new Exchange(null); + + @BeforeEach + void setUp() { + Router router = new Router(); + router.setUriFactory(new URIFactory()); + router.setBaseLocation(""); + + get.setRequest(new Request.Builder().method("GET").build()); + get.setRule(new NullRule()); + get.setOriginalHostHeader("api.predic8.de:80"); + } + @Test void isVersion3() throws IOException { - OpenAPIRecord rec = new OpenAPIRecord(null, getYAMLResource(this,"/openapi/specs/array.yml")); + OpenAPIRecord rec = new OpenAPIRecord(null, getYAMLResource(this,"/openapi/specs/array.yml"), null); assertEquals("3.0.2", rec.version); assertTrue(rec.isVersion3()); } @Test void isVersion2() throws IOException { - OpenAPIRecord rec = new OpenAPIRecord(null, getYAMLResource(this,"/openapi/specs/fruitshop-swagger-2.0.json")); + OpenAPIRecord rec = new OpenAPIRecord(null, getYAMLResource(this,"/openapi/specs/fruitshop-swagger-2.0.json"), null); assertEquals("2.0", rec.version); assertTrue(rec.isVersion2()); } diff --git a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/RewriteTest.java b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/RewriteTest.java new file mode 100644 index 000000000..198be1010 --- /dev/null +++ b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/RewriteTest.java @@ -0,0 +1,172 @@ +/* + * Copyright 2023 predic8 GmbH, www.predic8.com + * + * 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.predic8.membrane.core.openapi.serviceproxy; + +import com.fasterxml.jackson.databind.*; +import com.predic8.membrane.core.*; +import com.predic8.membrane.core.exchange.*; +import com.predic8.membrane.core.http.*; +import com.predic8.membrane.core.rules.*; +import com.predic8.membrane.core.util.*; +import org.jetbrains.annotations.*; +import org.junit.jupiter.api.*; + +import java.io.*; +import java.net.*; +import java.util.*; + +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.*; + +class RewriteTest { + + Rewrite rewriteAll = new Rewrite(); + Rewrite rewriteNothing = new Rewrite(); + + OpenAPISpec rewritesAllSpec; + + Map records; + + Exchange get = new Exchange(null); + + @BeforeEach + void setUp() throws IOException { + rewriteAll.host = "predic8.de"; + rewriteAll.port = 8080; + rewriteAll.protocol = "https"; + + Router router = new Router(); + router.setUriFactory(new URIFactory()); + router.setBaseLocation(""); + + OpenAPIRecordFactory openAPIRecordFactory = new OpenAPIRecordFactory(router); + + rewritesAllSpec = getSpecRewriteAll(); + + OpenAPISpec spec = new OpenAPISpec(); + spec.setDir("src/test/resources/openapi/specs"); + records = openAPIRecordFactory.create(singletonList(spec)); + + get.setRequest(new Request.Builder().method("GET").build()); + get.setRule(new NullRule()); + get.setOriginalHostHeader("api.predic8.de:80"); + } + + @NotNull + private static OpenAPISpec getSpecRewriteAll() { + OpenAPISpec spec = new OpenAPISpec(); + spec.setLocation("src/test/resources/openapi/specs/info-servers.yml"); + Rewrite rewrite = new Rewrite(); + rewrite.setProtocol("https"); + rewrite.setPort(443); + rewrite.setHost("membrane-api.io"); + spec.setRewrite(rewrite); + return spec; + } + + @Test + void rewriteProtocol() { + assertEquals("https", rewriteAll.rewriteProtocol("http")); + assertEquals("http", rewriteNothing.rewriteProtocol("http")); + } + + @Test + void rewriteHost() { + assertEquals("predic8.de", rewriteAll.rewriteHost("membrane-api.io")); + assertEquals("membrane-api.io", rewriteNothing.rewriteHost("membrane-api.io")); + } + + @Test + void rewritePort() { + assertEquals("8080", rewriteAll.rewritePort("80")); + assertEquals("80", rewriteNothing.rewritePort("80")); + } + + /** + * Test if rewritting changes the OpenAPI document. + * Secures currency. + */ + @Test + void rewritingDoesNotChangeTheSpec() throws Exception { + OpenAPIRecord rec = records.get("servers-1-api-v1-0"); + JsonNode servers = rec.node.get("servers"); + + String urlBefore = servers.get(0).get("url").asText(); + + rec.rewriteOpenAPI(get, new URIFactory()); + + JsonNode servers1 = rec.node.get("servers"); + assertEquals(urlBefore, servers1.get(0).get("url").asText()); + } + + @Test + void rewriteSwagger2AccordingToRequestTest() throws Exception { + assertEquals("api.predic8.de:80", records.get("fruit-shop-api-swagger-2-v1-0-0").rewriteOpenAPI(get, new URIFactory()).get("host").asText()); + } + + @Test + void rewriteOpenAPIAccordingToRequestTest() throws Exception { + JsonNode servers = records.get("servers-1-api-v1-0").rewriteOpenAPI(get, new URIFactory()).get("servers"); + assertEquals(1,servers.size()); + assertEquals("http://api.predic8.de/base/v2", servers.get(0).get("url").asText()); + assertEquals("Test System", records.get("servers-1-api-v1-0").node.get("servers").get(0).get("description").asText()); + } + + @Test + void rewriteOpenAPIAccordingToRequest3Servers() throws Exception { + OpenAPIRecord rec = records.get("servers-3-api-v1-0"); + JsonNode servers = rec.rewriteOpenAPI(get, new URIFactory()).get("servers"); + assertEquals(3,servers.size()); + assertEquals("http://api.predic8.de/foo",servers.get(0).get("url").asText()); + assertEquals("http://api.predic8.de/foo",servers.get(1).get("url").asText()); + assertEquals("http://api.predic8.de/foo",servers.get(2).get("url").asText()); + } + + @Test + void rewriteOpenAPIAccordingToRewrite3Servers() throws Exception { + OpenAPIRecord rec = records.get("servers-3-api-v1-0"); + rec.spec.rewrite.setHost("membrane-api.do"); + rec.spec.rewrite.setPort(8443); + rec.spec.rewrite.setProtocol("https"); + JsonNode servers = rec.rewriteOpenAPI(get, new URIFactory()).get("servers"); + assertEquals(3,servers.size()); + assertEquals("https://membrane-api.do:8443/foo",servers.get(0).get("url").asText()); + assertEquals("https://membrane-api.do:8443/foo",servers.get(1).get("url").asText()); + assertEquals("https://membrane-api.do:8443/foo",servers.get(2).get("url").asText()); + } + + @Test + void rewriteRequestHostHeaderWithoutPort() throws Exception { + OpenAPIRecord rec = records.get("servers-3-api-v1-0"); + get.setOriginalHostHeader("api.predic8.de"); + JsonNode servers = rec.rewriteOpenAPI(get, new URIFactory()).get("servers"); + assertEquals("http://api.predic8.de/foo", servers.get(0).get("url").textValue()); + assertEquals("http://api.predic8.de/foo", servers.get(1).get("url").textValue()); + assertEquals("http://api.predic8.de/foo", servers.get(2).get("url").textValue()); + } + + @Test + void rewriteUrl() throws URISyntaxException { + assertEquals("http://api.predic8.de/foo", new Rewrite().rewriteUrl(get,"http://localhost:3000/foo", new URIFactory())); + assertEquals("http://api.predic8.de/foo", new Rewrite().rewriteUrl(get,"http://localhost/foo", new URIFactory())); + } + + @Test + void rewriteOpenAPI3WithNoServers() throws Exception { + assertTrue(records.get("no-servers-v1-0").rewriteOpenAPI(get, new URIFactory()).get("servers").isEmpty()); + } +} \ No newline at end of file diff --git a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/XMembraneExtentionTest.java b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/XMembraneExtentionTest.java index 43b930cb0..70e9ecd03 100644 --- a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/XMembraneExtentionTest.java +++ b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/XMembraneExtentionTest.java @@ -16,11 +16,9 @@ package com.predic8.membrane.core.openapi.serviceproxy; -import com.fasterxml.jackson.databind.*; import com.predic8.membrane.core.*; import com.predic8.membrane.core.exchange.*; import com.predic8.membrane.core.http.*; -import io.swagger.v3.parser.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -38,7 +36,7 @@ public void setUp() throws IOException, ClassNotFoundException { Router router = new Router(); router.setBaseLocation(""); OpenAPIRecordFactory factory = new OpenAPIRecordFactory(router); - APIProxy.Spec spec = new APIProxy.Spec(); + OpenAPISpec spec = new OpenAPISpec(); spec.setDir("src/test/resources/openapi/specs"); Map records = factory.create(Collections.singletonList(spec)); diff --git a/core/src/test/java/com/predic8/membrane/core/openapi/util/TestUtils.java b/core/src/test/java/com/predic8/membrane/core/openapi/util/TestUtils.java index 57c8b98ee..e8c69718e 100644 --- a/core/src/test/java/com/predic8/membrane/core/openapi/util/TestUtils.java +++ b/core/src/test/java/com/predic8/membrane/core/openapi/util/TestUtils.java @@ -51,7 +51,7 @@ public static InputStream getResourceAsStream(Object obj, String fileName) { return obj.getClass().getResourceAsStream(fileName); } - public static APIProxy createProxy(Router router, APIProxy.Spec spec) throws Exception { + public static APIProxy createProxy(Router router, OpenAPISpec spec) throws Exception { APIProxy proxy = new APIProxy(); proxy.init(router); proxy.setSpecs(singletonList(spec)); diff --git a/core/src/test/resources/openapi/specs/info-no-servers.yml b/core/src/test/resources/openapi/specs/info-no-servers.yml new file mode 100644 index 000000000..8404c77ca --- /dev/null +++ b/core/src/test/resources/openapi/specs/info-no-servers.yml @@ -0,0 +1,11 @@ +openapi: '3.0.2' +info: + title: No Servers + version: '1.0' +servers: [] +paths: + /foo: + get: + responses: + '200': + description: OK