Skip to content

Commit 0c5b50c

Browse files
committed
docs: Add design for Wave 2 item 5 (string format expansion)
1 parent 1358915 commit 0c5b50c

1 file changed

Lines changed: 81 additions & 0 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# String format expansion (Wave 2 item 5)
2+
3+
**Status:** design approved 2026-05-08
4+
**Source inventory:** `docs/superpowers/specs/2026-05-07-openapi-refactor-design.md` §9, Wave 2 item 5
5+
6+
## Goal
7+
8+
Extend `DefaultValidator` to recognize 10 additional `format` values defined by OpenAPI 3.1 / JSON Schema 2020-12 on `StringSchema`:
9+
10+
`email`, `uri`, `uri-reference`, `hostname`, `ipv4`, `ipv6`, `regex`, `byte`, `binary`, `password`.
11+
12+
These join the three already supported (`uuid`, `date`, `date-time`).
13+
14+
## Non-goals
15+
16+
- Numeric format-width validation (`int32`, `int64`, `float`, `double`) — Wave 2 item 8, separate spec/PR.
17+
- Consumer-defined custom formats / `FormatValidator` SPI — deferred; non-breaking to add later.
18+
- Toggling the JSON Schema 2020-12 `format-assertion` vocabulary on/off — we always assert, matching current behavior.
19+
- Changes to `StringSchema` record shape or `Spec` parsing.
20+
21+
## Decisions
22+
23+
- **Closed set.** Only the 13 well-known formats (current 3 + the 10 below) are recognized. Unknown `format` values continue to be silently ignored. (User decision, 2026-05-08.)
24+
- **Always assert.** Consistent with current behavior of `uuid` / `date` / `date-time`.
25+
- **Syntactic-only network checks.** No DNS lookups for `hostname`, `ipv4`, `ipv6`, `uri`. Avoid `InetAddress.getByName`.
26+
- **No new dependencies.** Java stdlib + regex only.
27+
28+
## Per-format strategy
29+
30+
| Format | Strategy |
31+
|---|---|
32+
| `email` | Regex `^[^\s@]+@[^\s@]+\.[^\s@]+$`. Pragmatic; matches what most JSON Schema validators do in practice. Full RFC 5322 grammar is not worth the complexity. |
33+
| `uri` | `URI.create(str)` succeeds *and* `isAbsolute()` is true. |
34+
| `uri-reference` | `URI.create(str)` succeeds. |
35+
| `hostname` | Regex per RFC 1123: labels 1–63 chars, alphanumeric + hyphens, hyphens not at label boundaries, total length ≤ 253. |
36+
| `ipv4` | Regex `^((25[0-5]\|2[0-4]\d\|1?\d?\d)\.){3}(25[0-5]\|2[0-4]\d\|1?\d?\d)$`. Strict dotted-quad. |
37+
| `ipv6` | The standard JSON Schema 2020-12 IPv6 regex: 8 hex groups with `::` compression and optional embedded IPv4 trailer. Single explicit regex, not the `URI("http://[…]/")` hack — avoids surprises around zone IDs and mapped forms. |
38+
| `regex` | `Pattern.compile(str)`, catch `PatternSyntaxException`. |
39+
| `byte` | `Base64.getDecoder().decode(str)` (strict, not MIME), catch `IllegalArgumentException`. |
40+
| `binary` | No-op (always passes). Not meaningful as a JSON string format. |
41+
| `password` | No-op. UI hint per OAS. |
42+
43+
## Code organization
44+
45+
Current state: `DefaultValidator.validateStringFormat` is a `switch` with a `default` that ignores unknown formats. Adding 10 more arms makes the method noisy and a bad fit for `switch`.
46+
47+
Refactor in this PR:
48+
49+
- Introduce a private static registry inside `DefaultValidator`:
50+
```java
51+
private record FormatCheck(Predicate<String> isValid, String message) {}
52+
private static final Map<String, FormatCheck> FORMAT_CHECKS = Map.ofEntries(...);
53+
```
54+
- `validateStringFormat` becomes a single map lookup; missing key → ignore (preserves current "unknown format ignored" behavior).
55+
- Pre-compiled `Pattern` constants live as `private static final` fields next to the registry.
56+
- No-op formats (`binary`, `password`) are entries with `s -> true`. Keeping them in the map (rather than as omissions falling through to the ignore branch) documents that they're recognized-and-intentionally-permissive, not unknown.
57+
58+
Error rendering is unchanged: `fail(pointer, "format", message, value)` produces the same RFC 7807 400 response shape as today.
59+
60+
## Tests
61+
62+
Plan to put new tests next to existing format tests. Before writing, check whether a `StringFormatValidationTest` (or similar) already exists; extend it if so, otherwise create one.
63+
64+
For each newly added format:
65+
66+
- ≥ 1 valid example (passes validation).
67+
- ≥ 2 invalid examples covering distinct failure modes (e.g., `ipv4`: out-of-range octet *and* wrong group count).
68+
69+
Integration coverage: at least two formats wired through `OpenApiServer` end-to-end in an `*IT.java` to confirm a 400 with the `application/problem+json` body shape currently produced for `uuid`/`date`/`date-time`. One should be a regex-based format and one should be a parsing-based format (e.g., `email` + `byte`).
70+
71+
No-op formats (`binary`, `password`) get a single test each: any string passes, including obviously non-binary / non-password content, to lock in the no-op semantics.
72+
73+
Test fixtures: `src/test/resources/openapi.json` and the parallel `openapi.yaml` will gain a small operation that exercises one of the new formats end-to-end (the IT case). Per project rule, both files must mirror each other.
74+
75+
## Acceptance criteria
76+
77+
- All 10 formats recognized; valid inputs pass, invalid inputs produce a 400 with `format` in the violation pointer and a human-readable message.
78+
- `uuid`, `date`, `date-time` behavior is byte-for-byte unchanged.
79+
- Unknown `format` values are still silently ignored.
80+
- No new runtime dependencies.
81+
- `mvn verify` passes; coverage for the new branches reflected in the JaCoCo report.

0 commit comments

Comments
 (0)