Files
Flash5/flash-extensions/flash-ext-validation/pom.xml
T
Zakaria El OrcheandClaude Opus 5 f68e661296 feat(ext-validation): add request validation with compiled constraints
Standard jakarta.validation annotations, compiled once per type into a flat
check table. No configuration: constraints come from the annotations already on
your types, and ValidationException extends HttpException with status 422 so
the default handler renders it without this extension registering anything.

    record CreateUser(@NotBlank @Size(max = 80) String name,
                      @Email String email,
                      @Min(18) int age) {}

    CreateUser dto = validation.body(req, CreateUser.class);

Annotations only — Hibernate Validator's engine is deliberately absent. It
resolves constraints reflectively per call and pulls ~2 MB plus EL, which is
the per-request cost this module exists to avoid. jakarta.validation-api is
~90 KB of annotations.

The passing path allocates nothing. Constraints resolve at first use into an
opcode plus operands cached in a ClassValue, so there is no map lookup and no
lock. Fields are read through MethodHandles adapted to an exact signature —
(Object)Object for references, (Object)long for primitive integrals — so
invokeExact neither boxes nor builds the argument array Field.get and
Method.invoke allocate. Checks are a flat array walked by a tableswitch rather
than a class hierarchy behind a virtual call. @Size reads a length the object
already knows and @Email scans with indexOf, because Pattern.matcher allocates
a matcher and two int arrays per call. Messages are pre-rendered at compile
time. The violation list and the exception exist only once something fails.

@Pattern is the marked exception: its regex compiles once but matcher()
allocates per call.

Constraints are read from declared fields, so records and plain classes take
one code path — a constraint on a record component propagates to its backing
field.

Jakarta null semantics are exact: only @NotNull rejects null.

flash-ext-openapi now mirrors the same annotations into the generated schema —
minLength, maxLength, minItems, minimum, maximum, pattern, format: email and
required — via an optional jakarta.validation dependency detected at boot. A
type declares its rules once and both the validator and the published contract
read them. An explicit @Schema still wins; the bridge only fills keys nobody
set, and without the annotations on the classpath the bridge class is never
loaded.

flash-ext-jackson is optional too: validate(value) works without it, only
body(req, type) needs a codec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 12:20:33 +00:00

52 lines
1.9 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.relism</groupId>
<artifactId>flash-extensions</artifactId>
<version>2.1.0-SNAPSHOT</version>
</parent>
<artifactId>flash-ext-validation</artifactId>
<dependencies>
<dependency>
<groupId>dev.relism</groupId>
<artifactId>flash</artifactId>
</dependency>
<!--
Annotations only (~90 KB). Hibernate Validator's engine is deliberately absent: it
resolves constraints reflectively per call and pulls ~2 MB plus EL. This module
compiles the same annotations into a flat check table once per class instead.
-->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<!-- Only needed for Validation.body(...); check(...) works without it. -->
<dependency>
<groupId>dev.relism</groupId>
<artifactId>flash-ext-jackson</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>dev.relism</groupId>
<artifactId>flash-testing</artifactId>
<scope>test</scope>
</dependency>
<!-- Interop only: proves constraints reach the published schema. -->
<dependency>
<groupId>dev.relism</groupId>
<artifactId>flash-ext-openapi</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>