diff --git a/src/main/java/com/retailsvc/http/OpenApiServer.java b/src/main/java/com/retailsvc/http/OpenApiServer.java index c7c8074..dbcbbc0 100644 --- a/src/main/java/com/retailsvc/http/OpenApiServer.java +++ b/src/main/java/com/retailsvc/http/OpenApiServer.java @@ -93,7 +93,8 @@ record HandlerConfig( this.httpServer = HttpServer.create(socketAddress, 0); httpServer.setExecutor(newThreadPerTaskExecutor(ofVirtual().name("http-", 0).factory())); - HttpContext ctx = httpServer.createContext(Optional.ofNullable(spec.basePath()).orElse("/")); + String basePath = Optional.ofNullable(spec.basePath()).orElse("/"); + HttpContext ctx = httpServer.createContext(basePath); ctx.getFilters().add(new ExceptionFilter(exceptionHandler)); ctx.getFilters().add(new RequestPreparationFilter(spec, router, validator, bodyMappers)); ctx.getFilters() @@ -117,7 +118,9 @@ record HandlerConfig( extraCtx.setHandler(e.getValue()); } - httpServer.createContext("/", Handlers.notFoundHandler()); + if (!"/".equals(basePath)) { + httpServer.createContext("/", Handlers.notFoundHandler()); + } httpServer.start(); this.shutdownTimeoutSeconds = shutdownTimeoutSeconds; diff --git a/src/main/java/com/retailsvc/http/spec/Spec.java b/src/main/java/com/retailsvc/http/spec/Spec.java index 00bb982..4f76fc3 100644 --- a/src/main/java/com/retailsvc/http/spec/Spec.java +++ b/src/main/java/com/retailsvc/http/spec/Spec.java @@ -245,7 +245,8 @@ private static String computeBasePath(List servers) { if (servers.isEmpty()) { throw new IllegalStateException("no servers declared"); } - return Optional.ofNullable(URI.create(servers.getFirst().url()).getPath()).orElse(""); + String path = URI.create(servers.getFirst().url()).getPath(); + return (path == null || path.isEmpty()) ? "/" : path; } private static Map indexByRef(Map components, String prefix) { diff --git a/src/test/java/com/retailsvc/http/OpenApiServerTest.java b/src/test/java/com/retailsvc/http/OpenApiServerTest.java index 4bfa192..03496b2 100644 --- a/src/test/java/com/retailsvc/http/OpenApiServerTest.java +++ b/src/test/java/com/retailsvc/http/OpenApiServerTest.java @@ -89,6 +89,45 @@ void shouldBindOnlyToLoopbackWhenBindAddressIsLoopback() throws Exception { } } + @Test + void shouldDispatchHandlerOnRootPathWhenServerUrlHasNoPath() throws Exception { + Map raw = + Map.of( + "openapi", "3.1.0", + "info", Map.of("title", "Test API", "version", "1.0"), + "servers", List.of(Map.of("url", "http://127.0.0.1:4444")), + "paths", + Map.of( + "/", + Map.of( + "get", + Map.of( + "operationId", + "root", + "responses", + Map.of("204", Map.of("description", "ok")))))); + Spec spec = Spec.from(raw); + RequestHandler rootHandler = request -> Response.empty(); + try (var server = + OpenApiServer.builder() + .spec(spec) + .handlers(Map.of("root", rootHandler)) + .port(0) + .bindAddress(InetAddress.getLoopbackAddress()) + .build()) { + int port = server.listenPort(); + HttpClient client = + HttpClient.newBuilder() + .executor(newVirtualThreadPerTaskExecutor()) + .version(HTTP_1_1) + .build(); + HttpRequest request = + HttpRequest.newBuilder().uri(URI.create("http://127.0.0.1:" + port + "/")).GET().build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.discarding()); + assertThat(response.statusCode()).isEqualTo(HttpURLConnection.HTTP_NO_CONTENT); + } + } + @Test void shouldBindToWildcardWhenBindAddressIsUnset() throws IOException { try (var server = diff --git a/src/test/java/com/retailsvc/http/spec/SpecTest.java b/src/test/java/com/retailsvc/http/spec/SpecTest.java index 091a2bd..f3b18af 100644 --- a/src/test/java/com/retailsvc/http/spec/SpecTest.java +++ b/src/test/java/com/retailsvc/http/spec/SpecTest.java @@ -33,6 +33,18 @@ void parsesMinimalSpec() { assertThat(spec.operations()).isEmpty(); } + @Test + void basePathDefaultsToRootWhenServerUrlHasNoPath() { + Map raw = + Map.of( + "openapi", "3.1.0", + "info", Map.of("title", "x", "version", "1"), + "servers", List.of(Map.of("url", "http://127.0.0.1:4444")), + "paths", Map.of()); + Spec spec = Spec.from(raw); + assertThat(spec.basePath()).isEqualTo("/"); + } + @Test void parsesPathsWithMethods() { Map raw =