ADR 0006: matches accepts one portable subset of regular expressions, with one meaning
Status: accepted
Status: accepted
Context
1.0.0-alpha.1 asked authors to stay within "the syntax shared by RE2, Java, JavaScript and
Python". Nothing enforced it, and shared syntax is not shared meaning. $ also matches before a
final line break in Python and Java and not in JavaScript or Go. Which line terminators . excludes
depends on the engine. \d and \w cover Unicode in Python and ASCII in JavaScript; \s is a
different set in all four. Go refuses back-references, look-around and counts above 1000. A pattern
could pass in the browser and fail on the server for the same input.
Decision
Specification section 4.4 defines the pattern language: literals, . (any code point, line breaks
included), ^ and $ (the very start and end), \d \D \w \W (ASCII), \t \n \r, escaped syntax
characters, character classes, groups, alternation and bounded repetition. Counts, nested counts
multiplied, and the pattern length are each limited to 1000. Everything else is rejected.
Each runtime has a small scanner that accepts exactly this subset, and translates an accepted
pattern into the dialect of the engine it uses. A pattern must be a string literal, and one outside
the subset fails at load (PATTERN_NOT_PORTABLE) and at evaluation, in every runtime, whether or
not the local engine could run it. The patterns in the ruleset schema follow the same rule.
Consequences
- A pattern means the same in the browser and on the server; the conformance suite has the cases.
- Authors lose
\s,\b, look-around, back-references and flags. White space is written out, and case-insensitive matching needsloweror explicit classes. rulecheck deriveskips OpenAPI patterns outside the subset and says so.- Every runtime carries the scanner in addition to its regular-expression engine.
- The subset bounds what a pattern can ask for. It does not make backtracking engines linear:
SECURITY.mdsays what the host still has to do.
Alternatives considered
- Document the pitfalls and trust authors, as before. The differences are invisible until two runtimes disagree about a customer.
- One engine everywhere, for example RE2. A dependency for the Java library, which has none, and for the Python package, and an engine to download in the browser.
- Our own matcher in every runtime. One meaning by construction, but four implementations of a regular-expression engine to keep identical instead of four scanners.
- No patterns, only string operators. Too weak for formats such as postal codes and SWIFT codes.