Add format: presets, which export the JSON Schema format keyword - #20
Open
VSN2015 wants to merge 1 commit into
Open
Add format: presets, which export the JSON Schema format keyword#20VSN2015 wants to merge 1 commit into
VSN2015 wants to merge 1 commit into
Conversation
VSN2015
commented
Sep 5, 2026
| # extension. :url and :hostname are shape checks, not reachability | ||
| # guarantees. | ||
| FORMATS = { | ||
| email: { pattern: URI::MailTo::EMAIL_REGEXP, json: "email" }.freeze, |
Owner
Author
There was a problem hiding this comment.
Using URI::MailTo::EMAIL_REGEXP under the hood for :email maintains exact parity with the regexp Rails developers commonly use, avoiding unexpected validation divergences.
| # which UNTRANSLATABLE reads — deliberately over-eagerly — as a | ||
| # possessive quantifier, and the most common format in Rails would | ||
| # otherwise publish no pattern at all. | ||
| def apply_pattern!(schema, regexp, vouched: false) |
Owner
Author
There was a problem hiding this comment.
The vouched: true parameter thoughtfully permits gem-authored presets to bypass the overly conservative regex translation checks, ensuring RFC email patterns emit valid JSON Schema patterns.
The regexps every app writes by hand, named once — :email, :uuid, :url, :slug, :hostname — mirroring how normalize: already works. :email is deliberately URI::MailTo::EMAIL_REGEXP itself, the regexp Rails apps already paste into their contracts, so adopting the preset cannot change which addresses an endpoint accepts. The rest avoid flags and Ruby-only constructs so they translate to ECMA-262 rather than exporting as an extension. A preset name resolves to its Regexp at class load, so request-time matching stays a plain Regexp#match? and an authored default:/example: is checked against the resolved pattern like any other. The preset name is kept on the frozen field so exporters and the matcher can speak in presets. Two mistakes now fail at class load: an unknown preset (listing the presets) and a format: that is neither a Regexp nor a preset name. That second one was a latent trap — a String was silently accepted and behaved as String#match?, i.e. an unanchored substring test, which is not what anyone writing `format: "..."` meant. The reason to prefer a preset over a hand-written Regexp is what it carries into the export: the JSON Schema `format` keyword the wider ecosystem understands, next to the `pattern` that still does the asserting (in draft 2020-12 `format` is an annotation unless a validator opts in). That required one narrow change to the exporter. A preset's pattern is authored by this gem rather than by the app, so it skips the deliberately over-eager untranslatable-construct scan. It has to: the RFC-derived :email pattern contains `*+` inside a character class, which the scan reads as a possessive quantifier, so the single most common format in Rails would otherwise have published no pattern at all. App-authored regexps keep the conservative treatment unchanged — the point of that heuristic is that a wrong pattern in published docs is worse than a missing one, and this gem can vouch for its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
VSN2015
force-pushed
the
feature/format-presets
branch
from
September 11, 2026 22:01
bbf31cc to
84c8577
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The regexps every app writes by hand, named once — mirroring how
normalize:already works.format:emailURI::MailTo::EMAIL_REGEXPemail:uuid8-4-4-4-12, either caseuuid:urlhttp/httpsURL — a shape check, not reachability, but it does rejectjavascript:and other schemesuri:slug:hostnamehostname:emailis deliberatelyURI::MailTo::EMAIL_REGEXPitself — the regexp Rails apps already paste into their contracts — so adopting the preset cannot change which addresses an endpoint accepts.A preset name resolves to its
Regexpat class load, so request-time matching stays a plainRegexp#match?and an authoreddefault:/example:is checked against the resolved pattern like any other.The real reason to prefer a preset
It carries something a hand-written Regexp cannot: the JSON Schema
formatkeyword the wider ecosystem understands.{ "type": "string", "format": "uuid", "minLength": 1, "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-...$" }patternis still emitted next to it — in draft 2020-12formatis an annotation unless a validator opts into asserting it, so the pattern is what actually enforces.One narrow exporter change worth reviewing
A preset's pattern is authored by this gem, not by the app, so it skips the deliberately over-eager
UNTRANSLATABLEscan (vouched: true).It has to. The RFC-derived
:emailpattern contains*+inside a character class:which the scan reads as a possessive quantifier — so the single most common format in Rails would otherwise have published
x-permittable-patternand no real pattern at all. App-authored regexps keep the conservative treatment completely unchanged: the point of that heuristic is that a wrong pattern in published docs is worse than a missing one, and the gem can vouch for its own patterns without loosening it for anyone else's.A latent trap closed
format:that is neither aRegexpnor a preset name now fails at class load. Previously a String was silently accepted and behaved asString#match?— an unanchored substring test — which is not what anyone writingformat: "..."meant.Also
matching(:email)asserts the preset by name,matching(/re/)the Regexp, and a mismatch says which of the two the contract declares.Verification