Skip to content

fix(schema_engine): follow ECMA-262 semantics in pattern - #136

Open
jd-lara wants to merge 2 commits into
JuliaComputing:mainfrom
QXT-Energy:jd/ecma-262-patterns
Open

jd-lara wants to merge 2 commits into
JuliaComputing:mainfrom
QXT-Energy:jd/ecma-262-patterns

Conversation

@jd-lara

@jd-lara jd-lara commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

JSON Schema defines pattern and patternProperties as ECMA-262 regular expressions.
_ecma_regex compiles them with Julia's default PCRE options. Those include UCP and
lack DOLLAR_ENDONLY, so several ECMA-262 rules did not hold:

Pattern Input ECMA-262 Before
^abc$ "abc\n" no match match ($ before a trailing newline)
^\d$ "߀" (NKO digit zero) no match match (UCP)
^\w$ "é" no match match (UCP)
^\s$ U+FEFF, U+2029 match no match
^\p{digit}+$ "߀" match compile error
^a.b$ "a\rb", "a\u2028b" no match match (. accepts CR, U+2028, U+2029)
^[]$, ^[^]$ "", "\n" no match, match compile error (] read as a literal)

The official suite's optional/ecmascript-regex.json covers these cases, but it did not
run.

Changes (src/schema_engine/dialects.jl)

  • Compile with DOLLAR_ENDONLY and without UCP. $ matches only at the end, and \d,
    \w and \b are ASCII, as in ECMA-262. \p{…} still matches Unicode properties,
    because it needs only UTF mode.
  • Translate \s and \S to explicit ranges of the ECMA-262 white-space set: tab,
    vertical tab, form feed, the Unicode 16 Zs code points, U+FEFF, and the line
    terminators LF, CR, U+2028 and U+2029. The ranges work inside and outside a class, so
    [\S] and [^\S] also follow ECMA-262. Julia compiles with ALT_BSUX, so code points
    up to U+00FF use \xhh, other BMP code points use \uhhhh, and U+10FFFF is a literal.
  • Translate an unescaped . outside a class to [^\n\r\u2028\u2029], [] to (?!),
    and [^] to (?s:.).
  • Read each escape as one token, so an escaped [, ] or . does not change the class
    state.
  • Add Unicode's extra General_Category aliases to ECMA_GENERAL_CATEGORIES: digit,
    cntrl, punct and Combining_Mark.

Tests

  • test/schema_engine/official.jl also runs optional/ecmascript-regex.json for each
    draft (2020-12, 2019-09, 7, 6, 4) when OPENAPI_SCHEMA_SUITE=all. Before this change,
    the 2020-12 file stops with a compile error at \p{digit}. The other cases also fail:
    \d, \D, \w, \W, \s, \S. After it, all 74 tests pass for each draft.
  • test/schema_engine/compiled.jl has unit tests that run by default: the trailing
    newline, ASCII \d and \w, Unicode \s and \S (also inside a class), escaped
    brackets, ., [], [^], \p{digit} and \p{Letter}.

OPENAPI_SCHEMA_SUITE=all julia --project=. -e 'using Pkg; Pkg.test()': "OpenAPI tests
passed". 12071 passes, 0 failures, 0 errors. Official JSON Schema suite: 10624 of 10624.

Context

This PR is independent of #135 (date-time-local). Both come from the Sienna SDK
(Sienna-Platform/SiennaSchemas#109), which relies on pattern for RFC 3339 timestamps.
Without DOLLAR_ENDONLY, "2030-01-01T00:00:00\n" passes a ^…$ timestamp pattern.

JSON Schema defines `pattern` and `patternProperties` as ECMA-262
regular expressions. Julia compiles PCRE with UCP and without
DOLLAR_ENDONLY, so `$` matched before a trailing newline and `\d`,
`\w` matched Unicode digits and letters.

- Compile with DOLLAR_ENDONLY and without UCP: `$` matches only at the
  end, and `\d`, `\w`, `\b` are ASCII.
- Translate `\s` and `\S` to ECMA-262 white space: tab, vertical tab,
  form feed, every Zs, U+FEFF, and the line terminators.
- Map Unicode's extra General_Category aliases (digit, cntrl, punct,
  Combining_Mark).
- Run optional/ecmascript-regex.json from the official suite for each
  draft, and add unit tests that run by default.
@jd-lara

jd-lara commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

@tanmaykm can we get a review of this PR to comply with the semantics for timeseries validation? We've notices that typescript and rust OpenAPI generators are more strict with this standard

@tanmaykm

Copy link
Copy Markdown
Member

Thanks, this looks good. Turning off UCP and adding DOLLAR_ENDONLY is the right fix, and running optional/ecmascript-regex.json is a good addition. One regression to fix before merging:

Escaped brackets change the character-class tracking

In _ecma_pattern, when an escaped character isn't s, S, p or P, the loop continues without consuming it. The next iteration then sees that character as an ordinary one, so an escaped \[ or \] flips in_class. Patterns that used to work now fail:

SchemaEngine._ecma_pattern("^\\[\\s\\]\$")
# ^\[\t\n\x0B\f\r\p{Zs}

\]$   -- \s expanded as if inside a class
# "[ ]" matched on main, no longer matches

SchemaEngine._ecma_pattern("^[\\]\\s]+\$")
# ^[\][\t\n\x0B\f\r\p{Zs}

]]+$   -- \] ended the class early
# "] " matched on main, no longer matches

Consuming the escaped character in the escape branch should fix both. After isodd(slashes) || continue, for anything other than s, S, p or P, write pattern[i], advance i and continue, so escaped characters never reach the [/] check. Could you add both patterns above to the unit tests in test/schema_engine/compiled.jl?

Optional, fine as a follow-up: ECMA-262 . doesn't match any line terminator (\n, \r, U+2028, U+2029), but PCRE's . excludes only \n. So ^a.b$ still matches "a\rb" and "a
b". This predates the PR, and the official suite doesn't seem to cover it.

Behavior note for the release notes: \d, \w, \b and POSIX classes such as [[:alpha:]] are now ASCII-only, and $ no longer matches before a trailing newline. That is correct per the spec, but values that passed validation before can now fail it.

- Read each escape as one token. An escaped `[` or `]` no longer changes
  the class state, so `^\[\s\]$` and `^[\]\s]$` match as in ECMA-262.
- Write `\s` and `\S` as explicit ranges of the ECMA-262 white-space set.
  The ranges work inside a class, so `[\S]` and `[^\S]` are correct.
- Translate an unescaped `.` outside a class to exclude every
  LineTerminator, `[]` to an empty match, and `[^]` to any code point.
@jd-lara

jd-lara commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

tanmaykm I fixed the comments, this in conjunction with #135 can be released together as general improvements to time zone handling

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants