diff --git a/runtime/common-openapi/src/main/java/io/aklivity/zilla/runtime/common/openapi/view/OpenapiOperationView.java b/runtime/common-openapi/src/main/java/io/aklivity/zilla/runtime/common/openapi/view/OpenapiOperationView.java index c37d3328aa..dfda07ced8 100644 --- a/runtime/common-openapi/src/main/java/io/aklivity/zilla/runtime/common/openapi/view/OpenapiOperationView.java +++ b/runtime/common-openapi/src/main/java/io/aklivity/zilla/runtime/common/openapi/view/OpenapiOperationView.java @@ -20,9 +20,11 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Stream; import io.aklivity.zilla.runtime.common.openapi.config.OpenapiServerConfig; import io.aklivity.zilla.runtime.common.openapi.model.OpenapiOperation; +import io.aklivity.zilla.runtime.common.openapi.model.OpenapiServer; import io.aklivity.zilla.runtime.common.openapi.model.resolver.OpenapiResolver; public final class OpenapiOperationView @@ -50,6 +52,7 @@ public final class OpenapiOperationView OpenapiResolver resolver, String method, String path, + List pathServers, OpenapiOperation model) { this.specification = specification; @@ -83,9 +86,13 @@ public final class OpenapiOperationView .toList() : specification.security; - this.servers = model.servers != null - ? model.servers.stream() - .flatMap(s -> configs.stream().map(c -> new OpenapiServerView(resolver, s, c))) + List effectiveServers = model.servers != null ? model.servers : pathServers; + + this.servers = effectiveServers != null + ? effectiveServers.stream() + .flatMap(s -> configs.isEmpty() + ? Stream.of(new OpenapiServerView(resolver, s, null)) + : configs.stream().map(c -> new OpenapiServerView(resolver, s, c))) .toList() : specification.servers; diff --git a/runtime/common-openapi/src/main/java/io/aklivity/zilla/runtime/common/openapi/view/OpenapiPathView.java b/runtime/common-openapi/src/main/java/io/aklivity/zilla/runtime/common/openapi/view/OpenapiPathView.java index c570c6658c..757d08a1ec 100644 --- a/runtime/common-openapi/src/main/java/io/aklivity/zilla/runtime/common/openapi/view/OpenapiPathView.java +++ b/runtime/common-openapi/src/main/java/io/aklivity/zilla/runtime/common/openapi/view/OpenapiPathView.java @@ -80,7 +80,7 @@ public final class OpenapiPathView .filter(e -> e.getValue().apply(model) != null) .collect(Collectors.toMap(Map.Entry::getKey, e -> new OpenapiOperationView(specification, supplyCompositeId.getAsLong(), - configs, resolver, e.getKey(), path, e.getValue().apply(model)))); + configs, resolver, e.getKey(), path, model.servers, e.getValue().apply(model)))); this.extensions = model.extensions; } diff --git a/runtime/common-openapi/src/test/java/io/aklivity/zilla/runtime/common/openapi/view/OpenapiViewTest.java b/runtime/common-openapi/src/test/java/io/aklivity/zilla/runtime/common/openapi/view/OpenapiViewTest.java index ed83334b24..9c41db01f7 100644 --- a/runtime/common-openapi/src/test/java/io/aklivity/zilla/runtime/common/openapi/view/OpenapiViewTest.java +++ b/runtime/common-openapi/src/test/java/io/aklivity/zilla/runtime/common/openapi/view/OpenapiViewTest.java @@ -39,6 +39,7 @@ import io.aklivity.zilla.runtime.common.openapi.model.OpenapiSchema; import io.aklivity.zilla.runtime.common.openapi.model.OpenapiSecurityScheme; import io.aklivity.zilla.runtime.common.openapi.model.OpenapiServer; +import io.aklivity.zilla.runtime.common.openapi.model.OpenapiServerVariable; public class OpenapiViewTest { @@ -305,6 +306,154 @@ public void shouldNotResolveScopesWithoutFlows() throws Exception assertEquals(List.of(), schemeView.scopes); } + @Test + public void shouldResolveOperationLevelServersOverPathAndRoot() throws Exception + { + OpenapiServer rootServer = new OpenapiServer(); + rootServer.url = "http://root.example.com"; + + OpenapiServer pathServer = new OpenapiServer(); + pathServer.url = "http://path.example.com"; + + OpenapiServer operationServer = new OpenapiServer(); + operationServer.url = "http://operation.example.com"; + + OpenapiOperation operation = new OpenapiOperation(); + operation.operationId = "ReadItems"; + operation.servers = List.of(operationServer); + + OpenapiPath path = new OpenapiPath(); + path.get = operation; + path.servers = List.of(pathServer); + + Openapi model = new Openapi(); + model.servers = List.of(rootServer); + model.paths = Map.of("/items", path); + + OpenapiServerConfig config = OpenapiServerConfig.builder().build(); + OpenapiView view = OpenapiView.of(model, List.of(config)); + OpenapiOperationView operationView = view.paths.get("/items").methods.get("GET"); + + assertEquals(1, operationView.servers.size()); + assertEquals(URI.create("http://operation.example.com:80"), operationView.servers.get(0).url); + } + + @Test + public void shouldResolvePathLevelServersOverRootWhenOperationHasNone() throws Exception + { + OpenapiServer rootServer = new OpenapiServer(); + rootServer.url = "http://root.example.com"; + + OpenapiServer pathServer = new OpenapiServer(); + pathServer.url = "http://path.example.com"; + + OpenapiOperation operation = new OpenapiOperation(); + operation.operationId = "ReadItems"; + + OpenapiPath path = new OpenapiPath(); + path.get = operation; + path.servers = List.of(pathServer); + + Openapi model = new Openapi(); + model.servers = List.of(rootServer); + model.paths = Map.of("/items", path); + + OpenapiServerConfig config = OpenapiServerConfig.builder().build(); + OpenapiView view = OpenapiView.of(model, List.of(config)); + OpenapiOperationView operationView = view.paths.get("/items").methods.get("GET"); + + assertEquals(1, operationView.servers.size()); + assertEquals(URI.create("http://path.example.com:80"), operationView.servers.get(0).url); + } + + @Test + public void shouldResolveRootLevelServersWhenNoOperationOrPathServers() throws Exception + { + OpenapiServer rootServer = new OpenapiServer(); + rootServer.url = "http://root.example.com"; + + OpenapiOperation operation = new OpenapiOperation(); + operation.operationId = "ReadItems"; + + OpenapiPath path = new OpenapiPath(); + path.get = operation; + + Openapi model = new Openapi(); + model.servers = List.of(rootServer); + model.paths = Map.of("/items", path); + + OpenapiServerConfig config = OpenapiServerConfig.builder().build(); + OpenapiView view = OpenapiView.of(model, List.of(config)); + OpenapiOperationView operationView = view.paths.get("/items").methods.get("GET"); + + assertEquals(1, operationView.servers.size()); + assertEquals(URI.create("http://root.example.com:80"), operationView.servers.get(0).url); + } + + @Test + public void shouldResolveServerUrlVariableDefaultWhenNoOverride() throws Exception + { + OpenapiServerVariable variable = new OpenapiServerVariable(); + variable.values = List.of("prod", "staging"); + variable.defaultValue = "prod"; + + OpenapiServer server = new OpenapiServer(); + server.url = "http://{env}.example.com"; + server.variables = Map.of("env", variable); + + Openapi model = new Openapi(); + model.servers = List.of(server); + + OpenapiServerConfig config = OpenapiServerConfig.builder().build(); + OpenapiView view = OpenapiView.of(model, List.of(config)); + + assertEquals(URI.create("http://prod.example.com:80"), view.servers.get(0).url); + } + + @Test + public void shouldResolveServerUrlVariableOverrideMatchingPattern() throws Exception + { + OpenapiServerVariable variable = new OpenapiServerVariable(); + variable.values = List.of("prod", "staging"); + variable.defaultValue = "prod"; + + OpenapiServer server = new OpenapiServer(); + server.url = "http://{env}.example.com"; + server.variables = Map.of("env", variable); + + Openapi model = new Openapi(); + model.servers = List.of(server); + + OpenapiServerConfig config = OpenapiServerConfig.builder() + .url("http://staging.example.com") + .build(); + OpenapiView view = OpenapiView.of(model, List.of(config)); + + assertEquals(URI.create("http://staging.example.com:80"), view.servers.get(0).url); + } + + @Test + public void shouldDefaultServerUrlVariableWhenOverrideDoesNotMatchPattern() throws Exception + { + OpenapiServerVariable variable = new OpenapiServerVariable(); + variable.values = List.of("prod", "staging"); + variable.defaultValue = "prod"; + + OpenapiServer server = new OpenapiServer(); + server.url = "http://{env}.example.com"; + server.variables = Map.of("env", variable); + + Openapi model = new Openapi(); + model.servers = List.of(server); + + OpenapiServerConfig config = OpenapiServerConfig.builder() + .url("http://dev.example.com") + .build(); + OpenapiView view = OpenapiView.of(model, List.of(config)); + + assertEquals(URI.create("http://prod.example.com:80"), view.servers.get(0).url); + } + public static final class SampleExtension { public String key;