From 417a4e8a44ee1c32413d53b13c1c7fd8deb15d2d Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 20 Nov 2025 00:11:59 -0500 Subject: [PATCH 01/17] Add a default validation exception handler --- http-inject-plugin/pom.xml | 22 ++++++ .../io/avaje/http/inject/HelidonHandler.java | 34 ++++++++++ .../http/inject/HttpValidatorHandler.java | 67 +++++++++++++++++++ .../io/avaje/http/inject/JavalinHandler.java | 38 +++++++++++ .../java/io/avaje/http/inject/JexHandler.java | 40 +++++++++++ .../avaje/http/inject/ValidationResponse.java | 28 ++++++++ .../src/main/java/module-info.java | 9 ++- 7 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java create mode 100644 http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java create mode 100644 http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java create mode 100644 http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java create mode 100644 http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java diff --git a/http-inject-plugin/pom.xml b/http-inject-plugin/pom.xml index 93c2b6966..d77bf018d 100644 --- a/http-inject-plugin/pom.xml +++ b/http-inject-plugin/pom.xml @@ -38,6 +38,28 @@ provided true + + io.javalin + javalin + 6.7.0 + provided + true + + + io.avaje + avaje-jex + 3.4-RC1 + provided + true + + + io.helidon.webserver + helidon-webserver + 4.3.2 + provided + true + + diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java new file mode 100644 index 000000000..394555a7f --- /dev/null +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java @@ -0,0 +1,34 @@ +package io.avaje.http.inject; + +import static java.util.stream.Collectors.joining; + +import io.avaje.http.api.ValidationException; +import io.helidon.webserver.http.HttpFeature; +import io.helidon.webserver.http.HttpRouting.Builder; +import io.helidon.webserver.http.ServerRequest; +import io.helidon.webserver.http.ServerResponse; + +public class HelidonHandler implements HttpFeature { + + @Override + public void setup(Builder routing) { + + routing.error(ValidationException.class, this::handle); + } + + private void handle(ServerRequest req, ServerResponse res, ValidationException exception) { + var violations = exception.getErrors(); + + int violationCount = violations.size(); + String violationList = + violations.stream() + .map(violation -> "'" + violation.getField() + "' " + violation.getMessage()) + .collect(joining("\n")); + + res.status(exception.getStatus()) + .send( + String.format( + "Bad Request [%s validation violations: \n%s]", violationCount, violationList) + .getBytes()); + } +} diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java new file mode 100644 index 000000000..9afea2ec4 --- /dev/null +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java @@ -0,0 +1,67 @@ +package io.avaje.http.inject; + +import io.avaje.http.api.AvajeJavalinPlugin; +import io.avaje.inject.BeanScopeBuilder; +import io.avaje.inject.spi.InjectPlugin; +import io.avaje.inject.spi.PluginProvides; +import io.avaje.jex.Routing.HttpService; +import io.avaje.spi.ServiceProvider; +import io.helidon.webserver.http.HttpFeature; + +/** Plugin for avaje inject that provides a default Validator Handler */ +@ServiceProvider +@PluginProvides( + providesStrings = { + "io.helidon.webserver.http.HttpFeature", + "io.avaje.http.api.AvajeJavalinPlugin", + "io.avaje.jex.Routing.HttpService", + }) +public final class HttpValidatorHandler implements InjectPlugin { + + enum Server { + HELIDON("io.helidon.webserver.http.HttpFeature"), + JAVALIN("io.javalin.plugin.Plugin"), + JEX("io.avaje.jex.Routing.HttpService"); + + String register; + + Server(String register) { + this.register = register; + } + } + + private static final Server type = server(); + + private static Server server() { + + for (var register : Server.values()) { + + try { + Class.forName(register.register); + return register; + } catch (ClassNotFoundException e) { + continue; + } + } + + return null; + } + + @Override + public void apply(BeanScopeBuilder builder) { + if (type == null) { + return; + } + switch (type) { + case HELIDON: + builder.bean(HttpFeature.class, new HelidonHandler()); + break; + case JAVALIN: + builder.bean(AvajeJavalinPlugin.class, new JavalinHandler()); + break; + case JEX: + builder.bean(HttpService.class, new JexHandler()); + break; + } + } +} diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java new file mode 100644 index 000000000..b03ac718a --- /dev/null +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java @@ -0,0 +1,38 @@ +package io.avaje.http.inject; + +import static java.util.stream.Collectors.joining; + +import java.util.List; + +import io.avaje.http.api.AvajeJavalinPlugin; +import io.avaje.http.api.ValidationException; +import io.javalin.config.JavalinConfig; +import io.javalin.http.Context; + +public class JavalinHandler extends AvajeJavalinPlugin { + @Override + public void onStart(JavalinConfig config) { + config.router.mount(r -> r.exception(ValidationException.class, this::handler)); + } + + private void handler(ValidationException ex, Context ctx) { + + var json = ctx.status(ex.getStatus()).jsonMapper(); + + List violations = ex.getErrors(); + if (json == null) { + int violationCount = violations.size(); + String violationList = + violations.stream() + .map(violation -> "'" + violation.getField() + "' " + violation.getMessage()) + .collect(joining("\n")); + + // return a plain-text error message + ctx.result( + String.format( + "Bad Request [%s validation violations: \n%s]", violationCount, violationList)); + return; + } + ctx.json(new ValidationResponse(violations)); + } +} diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java new file mode 100644 index 000000000..de685491a --- /dev/null +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java @@ -0,0 +1,40 @@ +package io.avaje.http.inject; + +import static java.util.stream.Collectors.joining; + +import java.util.List; + +import io.avaje.http.api.ValidationException; +import io.avaje.jex.Routing; +import io.avaje.jex.Routing.HttpService; +import io.avaje.jex.http.Context; + +public class JexHandler implements HttpService { + + @Override + public void add(Routing arg0) { + + arg0.error(ValidationException.class, this::handler); + } + + private void handler(Context ctx, ValidationException ex) { + + var json = ctx.status(ex.getStatus()).jsonService(); + + List violations = ex.getErrors(); + if (json == null) { + int violationCount = violations.size(); + String violationList = + violations.stream() + .map(violation -> "'" + violation.getField() + "' " + violation.getMessage()) + .collect(joining("\n")); + + // return a plain-text error message + ctx.text( + String.format( + "Bad Request [%s validation violations: \n%s]", violationCount, violationList)); + return; + } + ctx.json(new ValidationResponse(violations)); + } +} diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java new file mode 100644 index 000000000..435b5c38a --- /dev/null +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java @@ -0,0 +1,28 @@ +package io.avaje.http.inject; + +import java.util.List; + +import io.avaje.http.api.ValidationException.Violation; + +public class ValidationResponse { + + private static String type = "https://avaje.io/http/#bean-validation"; + private static String title = "Failed Constraints"; + private List errors; + + public ValidationResponse(List errors) { + this.errors = errors; + } + + public String type() { + return type; + } + + public String title() { + return title; + } + + public List errors() { + return errors; + } +} diff --git a/http-inject-plugin/src/main/java/module-info.java b/http-inject-plugin/src/main/java/module-info.java index e13c5e735..501507f3e 100644 --- a/http-inject-plugin/src/main/java/module-info.java +++ b/http-inject-plugin/src/main/java/module-info.java @@ -1,8 +1,13 @@ +import io.avaje.http.inject.DefaultResolverProvider; +import io.avaje.http.inject.HttpValidatorHandler; + module io.avaje.http.plugin { requires io.avaje.http.api; requires io.avaje.inject; requires static io.avaje.spi; - - provides io.avaje.inject.spi.InjectExtension with io.avaje.http.inject.DefaultResolverProvider; + requires static io.avaje.jex; + requires static io.javalin; + requires static io.helidon.webserver; + provides io.avaje.inject.spi.InjectExtension with DefaultResolverProvider, HttpValidatorHandler; } From 6e65e69b56cb248345474c573a9212b841db34b1 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 20 Nov 2025 00:17:36 -0500 Subject: [PATCH 02/17] Update pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index df6722420..a7f5fa50c 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,6 @@ http-client http-client-gson-adapter http-client-moshi-adapter - http-inject-plugin http-generator-core http-generator-javalin http-generator-sigma @@ -67,6 +66,7 @@ [21,) + http-inject-plugin htmx-nima htmx-nima-jstache http-generator-helidon From d86f6dd4558a8891547ac5de3b4b603b2a0f391b Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 20 Nov 2025 00:19:23 -0500 Subject: [PATCH 03/17] Update pom.xml --- http-inject-plugin/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http-inject-plugin/pom.xml b/http-inject-plugin/pom.xml index d77bf018d..b25fca158 100644 --- a/http-inject-plugin/pom.xml +++ b/http-inject-plugin/pom.xml @@ -48,7 +48,7 @@ io.avaje avaje-jex - 3.4-RC1 + 3.3 provided true From 1c86eeb94bcb7c9166cd10440b236cb94aa73eda Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 20 Nov 2025 00:31:27 -0500 Subject: [PATCH 04/17] Update HttpValidatorHandler.java --- .../java/io/avaje/http/inject/HttpValidatorHandler.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java index 9afea2ec4..748702a42 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java @@ -54,13 +54,13 @@ public void apply(BeanScopeBuilder builder) { } switch (type) { case HELIDON: - builder.bean(HttpFeature.class, new HelidonHandler()); + builder.provideDefault(HttpFeature.class, HelidonHandler::new); break; case JAVALIN: - builder.bean(AvajeJavalinPlugin.class, new JavalinHandler()); + builder.provideDefault(AvajeJavalinPlugin.class, JavalinHandler::new); break; case JEX: - builder.bean(HttpService.class, new JexHandler()); + builder.provideDefault(HttpService.class, JexHandler::new); break; } } From bba90b3d4d2c1385cb41a4ec83eae2d8cae5079d Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 20 Nov 2025 13:06:49 -0500 Subject: [PATCH 05/17] follow spec --- .../io/avaje/http/inject/JavalinHandler.java | 6 ++++- .../java/io/avaje/http/inject/JexHandler.java | 3 ++- .../avaje/http/inject/ValidationResponse.java | 26 ++++++++++++++++--- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java index b03ac718a..ec47f4e5f 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java @@ -33,6 +33,10 @@ private void handler(ValidationException ex, Context ctx) { "Bad Request [%s validation violations: \n%s]", violationCount, violationList)); return; } - ctx.json(new ValidationResponse(violations)); + ctx.contentType("application/problem+json") + .result( + json.toJsonString( + new ValidationResponse(ex.getStatus(), violations, ctx.path()), + ValidationResponse.class)); } } diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java index de685491a..d19fd3e7a 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java @@ -35,6 +35,7 @@ private void handler(Context ctx, ValidationException ex) { "Bad Request [%s validation violations: \n%s]", violationCount, violationList)); return; } - ctx.json(new ValidationResponse(violations)); + ctx.contentType("application/problem+json") + .write(json.toJsonString(new ValidationResponse(ex.getStatus(), violations, ctx.path()))); } } diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java index 435b5c38a..a41b1c829 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java @@ -7,11 +7,17 @@ public class ValidationResponse { private static String type = "https://avaje.io/http/#bean-validation"; - private static String title = "Failed Constraints"; - private List errors; - - public ValidationResponse(List errors) { + private static String title = "Request Failed Validation"; + private static String detail = + "You tried to call this endpoint, but your data failed the vibe check"; + private final int status; + private final List errors; + private final String instance; + + public ValidationResponse(int status, List errors, String instance) { + this.status = status; this.errors = errors; + this.instance = instance; } public String type() { @@ -22,6 +28,18 @@ public String title() { return title; } + public String detail() { + return detail; + } + + public String instance() { + return instance; + } + + public int status() { + return status; + } + public List errors() { return errors; } From d42502a04c4b808eee18c361454036d53e0e0662 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 20 Nov 2025 13:07:08 -0500 Subject: [PATCH 06/17] Update ValidationResponse.java --- .../src/main/java/io/avaje/http/inject/ValidationResponse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java index a41b1c829..af2b7f5c9 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java @@ -9,7 +9,7 @@ public class ValidationResponse { private static String type = "https://avaje.io/http/#bean-validation"; private static String title = "Request Failed Validation"; private static String detail = - "You tried to call this endpoint, but your data failed the vibe check"; + "You tried to call this endpoint, but your data failed validation"; private final int status; private final List errors; private final String instance; From d55a7a6bbae41d303bdbdb7fbfb80a1bebf0742f Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:36:22 -0500 Subject: [PATCH 07/17] Update ValidationResponse.java --- .../src/main/java/io/avaje/http/inject/ValidationResponse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java index af2b7f5c9..a204b82cc 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java @@ -6,7 +6,7 @@ public class ValidationResponse { - private static String type = "https://avaje.io/http/#bean-validation"; + private static String type = "tag:io.avaje.http.api.Validator"; private static String title = "Request Failed Validation"; private static String detail = "You tried to call this endpoint, but your data failed validation"; From a9d93ff7dce1a6fe31248f1475d11bb84c0299da Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:31:46 -0500 Subject: [PATCH 08/17] always json --- .../io/avaje/http/inject/HelidonHandler.java | 18 +---- .../io/avaje/http/inject/JavalinHandler.java | 25 +------ .../java/io/avaje/http/inject/JexHandler.java | 22 +----- .../avaje/http/inject/ValidationResponse.java | 75 ++++++++++++++++++- 4 files changed, 78 insertions(+), 62 deletions(-) diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java index 394555a7f..e2a517e02 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java @@ -1,7 +1,5 @@ package io.avaje.http.inject; -import static java.util.stream.Collectors.joining; - import io.avaje.http.api.ValidationException; import io.helidon.webserver.http.HttpFeature; import io.helidon.webserver.http.HttpRouting.Builder; @@ -16,19 +14,9 @@ public void setup(Builder routing) { routing.error(ValidationException.class, this::handle); } - private void handle(ServerRequest req, ServerResponse res, ValidationException exception) { - var violations = exception.getErrors(); - - int violationCount = violations.size(); - String violationList = - violations.stream() - .map(violation -> "'" + violation.getField() + "' " + violation.getMessage()) - .collect(joining("\n")); + private void handle(ServerRequest req, ServerResponse res, ValidationException ex) { - res.status(exception.getStatus()) - .send( - String.format( - "Bad Request [%s validation violations: \n%s]", violationCount, violationList) - .getBytes()); + res.status(ex.getStatus()).header("Content-Type", "application/problem+json") + .send(new ValidationResponse(ex.getStatus(), ex.getErrors(), req.path().rawPath()).toJson()); } } diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java index ec47f4e5f..1d599d286 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java @@ -1,9 +1,5 @@ package io.avaje.http.inject; -import static java.util.stream.Collectors.joining; - -import java.util.List; - import io.avaje.http.api.AvajeJavalinPlugin; import io.avaje.http.api.ValidationException; import io.javalin.config.JavalinConfig; @@ -17,26 +13,7 @@ public void onStart(JavalinConfig config) { private void handler(ValidationException ex, Context ctx) { - var json = ctx.status(ex.getStatus()).jsonMapper(); - - List violations = ex.getErrors(); - if (json == null) { - int violationCount = violations.size(); - String violationList = - violations.stream() - .map(violation -> "'" + violation.getField() + "' " + violation.getMessage()) - .collect(joining("\n")); - - // return a plain-text error message - ctx.result( - String.format( - "Bad Request [%s validation violations: \n%s]", violationCount, violationList)); - return; - } ctx.contentType("application/problem+json") - .result( - json.toJsonString( - new ValidationResponse(ex.getStatus(), violations, ctx.path()), - ValidationResponse.class)); + .result(new ValidationResponse(ex.getStatus(), ex.getErrors(), ctx.path()).toJson()); } } diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java index d19fd3e7a..f5fe551a9 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java @@ -1,9 +1,5 @@ package io.avaje.http.inject; -import static java.util.stream.Collectors.joining; - -import java.util.List; - import io.avaje.http.api.ValidationException; import io.avaje.jex.Routing; import io.avaje.jex.Routing.HttpService; @@ -19,23 +15,7 @@ public void add(Routing arg0) { private void handler(Context ctx, ValidationException ex) { - var json = ctx.status(ex.getStatus()).jsonService(); - - List violations = ex.getErrors(); - if (json == null) { - int violationCount = violations.size(); - String violationList = - violations.stream() - .map(violation -> "'" + violation.getField() + "' " + violation.getMessage()) - .collect(joining("\n")); - - // return a plain-text error message - ctx.text( - String.format( - "Bad Request [%s validation violations: \n%s]", violationCount, violationList)); - return; - } ctx.contentType("application/problem+json") - .write(json.toJsonString(new ValidationResponse(ex.getStatus(), violations, ctx.path()))); + .write(new ValidationResponse(ex.getStatus(), ex.getErrors(), ctx.path()).toJson()); } } diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java index a204b82cc..2beb262b6 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java @@ -8,8 +8,7 @@ public class ValidationResponse { private static String type = "tag:io.avaje.http.api.Validator"; private static String title = "Request Failed Validation"; - private static String detail = - "You tried to call this endpoint, but your data failed validation"; + private static String detail = "You tried to call this endpoint, but your data failed validation"; private final int status; private final List errors; private final String instance; @@ -43,4 +42,76 @@ public int status() { public List errors() { return errors; } + + // Custom serialize so we don't need any json lib + public String toJson() { + StringBuilder sb = new StringBuilder(); + sb.append('{'); + sb.append("\"type\":").append(escapeJson(type)).append(","); + sb.append("\"title\":").append(escapeJson(title)).append(","); + sb.append("\"detail\":").append(escapeJson(detail)).append(","); + sb.append("\"instance\":").append(escapeJson(instance)).append(","); + sb.append("\"status\":").append(status).append(','); + + sb.append("\"errors\":["); + for (int i = 0; i < errors().size(); i++) { + if (i > 0) { + sb.append(','); + } + var e = errors.get(i); + sb.append('{'); + sb.append("\"path\":").append(escapeJson(e.getPath())).append(","); + sb.append("\"field\":").append(escapeJson(e.getField())).append(","); + sb.append("\"message\":").append(escapeJson(e.getMessage())); + sb.append('}'); + } + + sb.append(']'); + + sb.append('}'); + return sb.toString(); + } + + private static String escapeJson(String s) { + if (s == null) { + return "null"; + } + StringBuilder sb = new StringBuilder(); + sb.append('"'); + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + switch (ch) { + case '"': + sb.append("\\\""); + break; + case '\\': + sb.append("\\\\"); + break; + case '\b': + sb.append("\\b"); + break; + case '\f': + sb.append("\\f"); + break; + case '\n': + sb.append("\\n"); + break; + case '\r': + sb.append("\\r"); + break; + case '\t': + sb.append("\\t"); + break; + default: + // Handle control characters or just append + if (ch < ' ' || ch > '~') { + sb.append(String.format("\\u%04x", (int) ch)); + } else { + sb.append(ch); + } + } + } + sb.append('"'); + return sb.toString(); + } } From c4659e7e50eb7cc878466ba6a18858b520d3780b Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 20 Nov 2025 22:23:22 -0500 Subject: [PATCH 09/17] streaming --- .../io/avaje/http/inject/HelidonHandler.java | 14 ++- .../io/avaje/http/inject/JavalinHandler.java | 12 +- .../java/io/avaje/http/inject/JexHandler.java | 12 +- .../avaje/http/inject/ValidationResponse.java | 103 +++++++++++------- 4 files changed, 94 insertions(+), 47 deletions(-) diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java index e2a517e02..5e7c3c92b 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java @@ -1,5 +1,8 @@ package io.avaje.http.inject; +import java.io.IOException; +import java.io.UncheckedIOException; + import io.avaje.http.api.ValidationException; import io.helidon.webserver.http.HttpFeature; import io.helidon.webserver.http.HttpRouting.Builder; @@ -15,8 +18,13 @@ public void setup(Builder routing) { } private void handle(ServerRequest req, ServerResponse res, ValidationException ex) { - - res.status(ex.getStatus()).header("Content-Type", "application/problem+json") - .send(new ValidationResponse(ex.getStatus(), ex.getErrors(), req.path().rawPath()).toJson()); + try (var os = + res.status(ex.getStatus()) + .header("Content-Type", "application/problem+json") + .outputStream()) { + new ValidationResponse(ex.getStatus(), ex.getErrors(), req.path().rawPath()).toJson(os); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } } diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java index 1d599d286..8120e52fc 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java @@ -1,5 +1,8 @@ package io.avaje.http.inject; +import java.io.IOException; +import java.io.UncheckedIOException; + import io.avaje.http.api.AvajeJavalinPlugin; import io.avaje.http.api.ValidationException; import io.javalin.config.JavalinConfig; @@ -12,8 +15,11 @@ public void onStart(JavalinConfig config) { } private void handler(ValidationException ex, Context ctx) { - - ctx.contentType("application/problem+json") - .result(new ValidationResponse(ex.getStatus(), ex.getErrors(), ctx.path()).toJson()); + try (var os = + ctx.contentType("application/problem+json").status(ex.getStatus()).outputStream()) { + new ValidationResponse(ex.getStatus(), ex.getErrors(), ctx.path()).toJson(os); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } } diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java index f5fe551a9..3284444ef 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java @@ -1,5 +1,8 @@ package io.avaje.http.inject; +import java.io.IOException; +import java.io.UncheckedIOException; + import io.avaje.http.api.ValidationException; import io.avaje.jex.Routing; import io.avaje.jex.Routing.HttpService; @@ -9,13 +12,16 @@ public class JexHandler implements HttpService { @Override public void add(Routing arg0) { - arg0.error(ValidationException.class, this::handler); } private void handler(Context ctx, ValidationException ex) { - ctx.contentType("application/problem+json") - .write(new ValidationResponse(ex.getStatus(), ex.getErrors(), ctx.path()).toJson()); + try (var os = + ctx.contentType("application/problem+json").status(ex.getStatus()).outputStream()) { + new ValidationResponse(ex.getStatus(), ex.getErrors(), ctx.path()).toJson(os); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } } diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java index 2beb262b6..818f68aaa 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java @@ -1,5 +1,9 @@ package io.avaje.http.inject; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; import java.util.List; import io.avaje.http.api.ValidationException.Violation; @@ -43,75 +47,98 @@ public List errors() { return errors; } - // Custom serialize so we don't need any json lib - public String toJson() { - StringBuilder sb = new StringBuilder(); - sb.append('{'); - sb.append("\"type\":").append(escapeJson(type)).append(","); - sb.append("\"title\":").append(escapeJson(title)).append(","); - sb.append("\"detail\":").append(escapeJson(detail)).append(","); - sb.append("\"instance\":").append(escapeJson(instance)).append(","); - sb.append("\"status\":").append(status).append(','); - - sb.append("\"errors\":["); - for (int i = 0; i < errors().size(); i++) { + // custom serialize as this is a simple class + public void toJson(OutputStream os) throws IOException { + try (Writer writer = new OutputStreamWriter(os, "UTF-8")) { + writeJsonInternal(writer); + } + } + + private void writeJsonInternal(Writer writer) throws IOException { + writer.write('{'); + writeKeyValue("type", type, writer); + writer.write(','); + writeKeyValue("title", title, writer); + writer.write(','); + writeKeyValue("detail", detail, writer); + writer.write(','); + writeKeyValue("instance", instance, writer); + writer.write(','); + // status is a number, so no quotes or escaping needed + writer.write("\"status\":"); + writer.write(String.valueOf(status)); + writer.write(",\"errors\":["); + for (int i = 0; i < errors.size(); i++) { if (i > 0) { - sb.append(','); + writer.write(','); } var e = errors.get(i); - sb.append('{'); - sb.append("\"path\":").append(escapeJson(e.getPath())).append(","); - sb.append("\"field\":").append(escapeJson(e.getField())).append(","); - sb.append("\"message\":").append(escapeJson(e.getMessage())); - sb.append('}'); + writer.write('{'); + writeKeyValue("path", e.getPath(), writer); + writer.write(','); + writeKeyValue("field", e.getField(), writer); + writer.write(','); + writeKeyValue("message", e.getMessage(), writer); + writer.write('}'); } - sb.append(']'); + writer.write(']'); + writer.write('}'); + writer.flush(); + } - sb.append('}'); - return sb.toString(); + /** Writes a JSON key-value pair where the value is a string, handling quotes and escaping. */ + private void writeKeyValue(String key, String value, Writer writer) throws IOException { + writer.write('"'); + writer.write(key); + writer.write("\":"); + writeEscapedJsonString(value, writer); } - private static String escapeJson(String s) { + /** + * Writes the given string to the writer, JSON-escaping it and wrapping it in quotes. Writes + * 'null' (the JSON literal) if the input string is null. + */ + private static void writeEscapedJsonString(String s, Writer writer) throws IOException { if (s == null) { - return "null"; + writer.write("null"); + return; } - StringBuilder sb = new StringBuilder(); - sb.append('"'); + + writer.write('"'); for (int i = 0; i < s.length(); i++) { char ch = s.charAt(i); switch (ch) { case '"': - sb.append("\\\""); + writer.write("\\\""); break; case '\\': - sb.append("\\\\"); + writer.write("\\\\"); break; case '\b': - sb.append("\\b"); + writer.write("\\b"); break; case '\f': - sb.append("\\f"); + writer.write("\\f"); break; case '\n': - sb.append("\\n"); + writer.write("\\n"); break; case '\r': - sb.append("\\r"); + writer.write("\\r"); break; case '\t': - sb.append("\\t"); + writer.write("\\t"); break; default: - // Handle control characters or just append - if (ch < ' ' || ch > '~') { - sb.append(String.format("\\u%04x", (int) ch)); + // Check for control characters that must be escaped + if (ch < ' ' || ch >= 0x7F && ch <= 0x9F) { + writer.write(String.format("\\u%04x", (int) ch)); } else { - sb.append(ch); + writer.write(ch); } } } - sb.append('"'); - return sb.toString(); + writer.write('"'); } } From c02568e19983ebb21d65307d91f0539bee97510a Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 21 Nov 2025 00:13:21 -0500 Subject: [PATCH 10/17] fix helidon plugin overridding all --- .../src/main/java/io/avaje/http/inject/HelidonHandler.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java index 5e7c3c92b..9cc241ab1 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java @@ -4,11 +4,13 @@ import java.io.UncheckedIOException; import io.avaje.http.api.ValidationException; +import io.helidon.common.Weight; import io.helidon.webserver.http.HttpFeature; import io.helidon.webserver.http.HttpRouting.Builder; import io.helidon.webserver.http.ServerRequest; import io.helidon.webserver.http.ServerResponse; +@Weight(-67) // execute first so that it can be overridden by a custom error handler. public class HelidonHandler implements HttpFeature { @Override From 2570ab8f4db002a6d61715fae229b8c56cd1ebc2 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 21 Nov 2025 00:18:37 -0500 Subject: [PATCH 11/17] final --- .../src/main/java/io/avaje/http/inject/HelidonHandler.java | 2 +- .../java/io/avaje/http/inject/HttpValidatorHandler.java | 7 +------ .../src/main/java/io/avaje/http/inject/JavalinHandler.java | 2 +- .../src/main/java/io/avaje/http/inject/JexHandler.java | 2 +- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java index 9cc241ab1..c18f97b8f 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/HelidonHandler.java @@ -11,7 +11,7 @@ import io.helidon.webserver.http.ServerResponse; @Weight(-67) // execute first so that it can be overridden by a custom error handler. -public class HelidonHandler implements HttpFeature { +final class HelidonHandler implements HttpFeature { @Override public void setup(Builder routing) { diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java index 748702a42..e4d32c684 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java @@ -22,9 +22,7 @@ enum Server { HELIDON("io.helidon.webserver.http.HttpFeature"), JAVALIN("io.javalin.plugin.Plugin"), JEX("io.avaje.jex.Routing.HttpService"); - String register; - Server(String register) { this.register = register; } @@ -33,17 +31,14 @@ enum Server { private static final Server type = server(); private static Server server() { - for (var register : Server.values()) { - try { Class.forName(register.register); return register; } catch (ClassNotFoundException e) { - continue; + // nothing } } - return null; } diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java index 8120e52fc..84dc6ca8b 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/JavalinHandler.java @@ -8,7 +8,7 @@ import io.javalin.config.JavalinConfig; import io.javalin.http.Context; -public class JavalinHandler extends AvajeJavalinPlugin { +final class JavalinHandler extends AvajeJavalinPlugin { @Override public void onStart(JavalinConfig config) { config.router.mount(r -> r.exception(ValidationException.class, this::handler)); diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java index 3284444ef..a89678fad 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/JexHandler.java @@ -8,7 +8,7 @@ import io.avaje.jex.Routing.HttpService; import io.avaje.jex.http.Context; -public class JexHandler implements HttpService { +final class JexHandler implements HttpService { @Override public void add(Routing arg0) { From 2c16b20b775b53908a7e5086991e24255708661c Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 21 Nov 2025 00:25:10 -0500 Subject: [PATCH 12/17] Update ValidationResponse.java --- .../src/main/java/io/avaje/http/inject/ValidationResponse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java index 818f68aaa..fe9a60e3e 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java @@ -10,7 +10,7 @@ public class ValidationResponse { - private static String type = "tag:io.avaje.http.api.Validator"; + private static String type = "tag:io.avaje.http.api.ValidationException"; private static String title = "Request Failed Validation"; private static String detail = "You tried to call this endpoint, but your data failed validation"; private final int status; From 0aa5af3b80089b1671b6cf73c4cd98e148ff6056 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 21 Nov 2025 08:01:07 -0500 Subject: [PATCH 13/17] Remove unused getter methods in ValidationResponse Removed unused getter methods from ValidationResponse. --- .../avaje/http/inject/ValidationResponse.java | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java index fe9a60e3e..c0f4b50ea 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/ValidationResponse.java @@ -22,31 +22,7 @@ public ValidationResponse(int status, List errors, String instance) { this.errors = errors; this.instance = instance; } - - public String type() { - return type; - } - - public String title() { - return title; - } - - public String detail() { - return detail; - } - - public String instance() { - return instance; - } - - public int status() { - return status; - } - - public List errors() { - return errors; - } - + // custom serialize as this is a simple class public void toJson(OutputStream os) throws IOException { try (Writer writer = new OutputStreamWriter(os, "UTF-8")) { From 4e36dc51b046ca66f15a92a88fc50f57a066b350 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 22 Nov 2025 18:18:05 -0500 Subject: [PATCH 14/17] pivot to NCDF --- http-inject-plugin/pom.xml | 2 +- .../http/inject/HttpValidatorHandler.java | 48 ++++--------------- .../src/main/java/module-info.java | 1 - 3 files changed, 11 insertions(+), 40 deletions(-) diff --git a/http-inject-plugin/pom.xml b/http-inject-plugin/pom.xml index b25fca158..329d0281e 100644 --- a/http-inject-plugin/pom.xml +++ b/http-inject-plugin/pom.xml @@ -19,7 +19,7 @@ io.avaje - avaje-inject + avaje-inject-generator 12.0 provided true diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java index e4d32c684..1d068af7a 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java @@ -5,11 +5,9 @@ import io.avaje.inject.spi.InjectPlugin; import io.avaje.inject.spi.PluginProvides; import io.avaje.jex.Routing.HttpService; -import io.avaje.spi.ServiceProvider; import io.helidon.webserver.http.HttpFeature; /** Plugin for avaje inject that provides a default Validator Handler */ -@ServiceProvider @PluginProvides( providesStrings = { "io.helidon.webserver.http.HttpFeature", @@ -18,45 +16,19 @@ }) public final class HttpValidatorHandler implements InjectPlugin { - enum Server { - HELIDON("io.helidon.webserver.http.HttpFeature"), - JAVALIN("io.javalin.plugin.Plugin"), - JEX("io.avaje.jex.Routing.HttpService"); - String register; - Server(String register) { - this.register = register; - } - } - - private static final Server type = server(); - - private static Server server() { - for (var register : Server.values()) { - try { - Class.forName(register.register); - return register; - } catch (ClassNotFoundException e) { - // nothing - } - } - return null; - } - @Override public void apply(BeanScopeBuilder builder) { - if (type == null) { - return; + try { + builder.provideDefault(HttpFeature.class, HelidonHandler::new); + } catch (NoClassDefFoundError e) { + } + try { + builder.provideDefault(AvajeJavalinPlugin.class, JavalinHandler::new); + } catch (NoClassDefFoundError e) { } - switch (type) { - case HELIDON: - builder.provideDefault(HttpFeature.class, HelidonHandler::new); - break; - case JAVALIN: - builder.provideDefault(AvajeJavalinPlugin.class, JavalinHandler::new); - break; - case JEX: - builder.provideDefault(HttpService.class, JexHandler::new); - break; + try { + builder.provideDefault(HttpService.class, JexHandler::new); + } catch (NoClassDefFoundError e) { } } } diff --git a/http-inject-plugin/src/main/java/module-info.java b/http-inject-plugin/src/main/java/module-info.java index 501507f3e..a3fcd3080 100644 --- a/http-inject-plugin/src/main/java/module-info.java +++ b/http-inject-plugin/src/main/java/module-info.java @@ -5,7 +5,6 @@ requires io.avaje.http.api; requires io.avaje.inject; - requires static io.avaje.spi; requires static io.avaje.jex; requires static io.javalin; requires static io.helidon.webserver; From f1829403e59fc88209cddec0b1f0323df669ada3 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 22 Nov 2025 18:19:45 -0500 Subject: [PATCH 15/17] Update DefaultResolverProvider.java --- .../java/io/avaje/http/inject/DefaultResolverProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/DefaultResolverProvider.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/DefaultResolverProvider.java index b22eb7714..e44ab9d84 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/DefaultResolverProvider.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/DefaultResolverProvider.java @@ -4,10 +4,10 @@ import io.avaje.http.api.context.ThreadLocalRequestContextResolver; import io.avaje.inject.BeanScopeBuilder; import io.avaje.inject.spi.InjectPlugin; -import io.avaje.spi.ServiceProvider; +import io.avaje.inject.spi.PluginProvides; /** Plugin for avaje inject that provides a default RequestContextResolver instance. */ -@ServiceProvider +@PluginProvides public final class DefaultResolverProvider implements InjectPlugin { @Override From eada8ba11901c87e476fe136023869320557f299 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 22 Nov 2025 19:43:00 -0500 Subject: [PATCH 16/17] module-check --- ...ler.java => HttpValidatorErrorPlugin.java} | 27 ++++++++++++++++--- .../src/main/java/module-info.java | 4 +-- 2 files changed, 25 insertions(+), 6 deletions(-) rename http-inject-plugin/src/main/java/io/avaje/http/inject/{HttpValidatorHandler.java => HttpValidatorErrorPlugin.java} (59%) diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorErrorPlugin.java similarity index 59% rename from http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java rename to http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorErrorPlugin.java index 1d068af7a..9b7b4ea31 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorHandler.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorErrorPlugin.java @@ -14,21 +14,40 @@ "io.avaje.http.api.AvajeJavalinPlugin", "io.avaje.jex.Routing.HttpService", }) -public final class HttpValidatorHandler implements InjectPlugin { +public final class HttpValidatorErrorPlugin implements InjectPlugin { @Override public void apply(BeanScopeBuilder builder) { - try { + + ModuleLayer bootLayer = ModuleLayer.boot(); + + if (bootLayer.findModule("io.avaje.jex").isPresent()) { + builder.provideDefault(HttpService.class, JexHandler::new); + return; + } else if (bootLayer.findModule("io.helidon.webserver").isPresent()) { builder.provideDefault(HttpFeature.class, HelidonHandler::new); + return; + } else if (bootLayer.findModule("io.javalin").isPresent()) { + builder.provideDefault(AvajeJavalinPlugin.class, JavalinHandler::new); + return; + } + + try { + builder.provideDefault(HttpService.class, JexHandler::new); + return; } catch (NoClassDefFoundError e) { + // not present } try { - builder.provideDefault(AvajeJavalinPlugin.class, JavalinHandler::new); + builder.provideDefault(HttpFeature.class, HelidonHandler::new); + return; } catch (NoClassDefFoundError e) { + // not present } try { - builder.provideDefault(HttpService.class, JexHandler::new); + builder.provideDefault(AvajeJavalinPlugin.class, JavalinHandler::new); } catch (NoClassDefFoundError e) { + // not present } } } diff --git a/http-inject-plugin/src/main/java/module-info.java b/http-inject-plugin/src/main/java/module-info.java index a3fcd3080..8c6058dfd 100644 --- a/http-inject-plugin/src/main/java/module-info.java +++ b/http-inject-plugin/src/main/java/module-info.java @@ -1,5 +1,5 @@ import io.avaje.http.inject.DefaultResolverProvider; -import io.avaje.http.inject.HttpValidatorHandler; +import io.avaje.http.inject.HttpValidatorErrorPlugin; module io.avaje.http.plugin { @@ -8,5 +8,5 @@ requires static io.avaje.jex; requires static io.javalin; requires static io.helidon.webserver; - provides io.avaje.inject.spi.InjectExtension with DefaultResolverProvider, HttpValidatorHandler; + provides io.avaje.inject.spi.InjectExtension with DefaultResolverProvider, HttpValidatorErrorPlugin; } From f31599f2d12fae0316ef5efc7634ffc3d69df3b8 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 22 Nov 2025 20:17:42 -0500 Subject: [PATCH 17/17] optimize even further beyond --- .../http/inject/HttpValidatorErrorPlugin.java | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorErrorPlugin.java b/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorErrorPlugin.java index 9b7b4ea31..627daad64 100644 --- a/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorErrorPlugin.java +++ b/http-inject-plugin/src/main/java/io/avaje/http/inject/HttpValidatorErrorPlugin.java @@ -21,33 +21,36 @@ public void apply(BeanScopeBuilder builder) { ModuleLayer bootLayer = ModuleLayer.boot(); - if (bootLayer.findModule("io.avaje.jex").isPresent()) { - builder.provideDefault(HttpService.class, JexHandler::new); - return; - } else if (bootLayer.findModule("io.helidon.webserver").isPresent()) { - builder.provideDefault(HttpFeature.class, HelidonHandler::new); - return; - } else if (bootLayer.findModule("io.javalin").isPresent()) { - builder.provideDefault(AvajeJavalinPlugin.class, JavalinHandler::new); - return; - } - - try { - builder.provideDefault(HttpService.class, JexHandler::new); - return; - } catch (NoClassDefFoundError e) { - // not present - } - try { - builder.provideDefault(HttpFeature.class, HelidonHandler::new); - return; - } catch (NoClassDefFoundError e) { - // not present - } - try { - builder.provideDefault(AvajeJavalinPlugin.class, JavalinHandler::new); - } catch (NoClassDefFoundError e) { - // not present - } + bootLayer + .findModule("io.avaje.http.plugin") + .ifPresentOrElse( + m -> { + if (bootLayer.findModule("io.avaje.jex").isPresent()) { + builder.provideDefault(HttpService.class, JexHandler::new); + } else if (bootLayer.findModule("io.helidon.webserver").isPresent()) { + builder.provideDefault(HttpFeature.class, HelidonHandler::new); + } else if (bootLayer.findModule("io.javalin").isPresent()) { + builder.provideDefault(AvajeJavalinPlugin.class, JavalinHandler::new); + } + }, + () -> { + try { + builder.provideDefault(HttpService.class, JexHandler::new); + return; + } catch (NoClassDefFoundError e) { + // not present + } + try { + builder.provideDefault(HttpFeature.class, HelidonHandler::new); + return; + } catch (NoClassDefFoundError e) { + // not present + } + try { + builder.provideDefault(AvajeJavalinPlugin.class, JavalinHandler::new); + } catch (NoClassDefFoundError e) { + // not present + } + }); } }