Skip to content

Validating input

One line: A validator is a pattern that must match all of the input and only the input you intend, and the two places it fails are the anchors — ^…$ is a line test in disguise — and the shorthands — \d and (?i)[a-z] accept characters that are not the ones you pictured; the fix is \A…\z (or the language's whole-string call), explicit classes, and code for anything the pattern cannot see.

Level: 201 · you are checking a field before you trust it

The shape of a validator

\A  <the grammar of one valid value>  \z

Both anchors, the strict ones; a grammar that names every character it allows; and nothing optional that you did not mean to be optional. Then, in the code: the checks a regex cannot make — ranges, dates, existence. Common patterns and their traps has the six patterns that skip that last step.

Measured

Verified output of topic_validating_input_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

construct                   python          perl            node  ruby      java  go        rust            fancy
^\d+$ on a plain number     42              42              42    42        42    42        42              42
^\d+$ with a trailing \n    42              42              no    42        42    no        no              no
^\d+$ with a second line    no              no              no    42        no    no        no              no
\A\d+\z instead             -               no              no    no        no    no        no              no
\A\d+\Z, Python's spelling  no              42              no    42        42    -         -               42
\d+ with no anchors         42              42              42    42        42    42        42              42
\d+ on Arabic-Indic digits  \u{660}\u{661}  \u{660}\u{661}  no    no        no    no        \u{660}\u{661}  \u{660}\u{661}
[0-9]+ on the same          no              no              no    no        no    no        no              no
(?i)[a-z]+ on KELVIN SIGN   \u{212A}        \u{212A}        -     \u{212A}  no    \u{212A}  \u{212A}        \u{212A}
[a-z]+ on KELVIN, node i    no              no              no    no        no    no        no              no

Reading the table

  • Row 1 is the happy path and it is unanimous, which is why validators pass their tests.
  • Row 2, a trailing newline. ^\d+$ accepts 42\n in Python, Perl, Ruby and Java. JavaScript, Go and both Rust crates reject it. A form field, a line read from a file, a value pasted with its newline — half of these engines say "a number".
  • Row 3, a second line. 42\nrm -rf / gets through in Ruby only — its ^ and $ are line anchors with no flag. The lesson runs the payload in every engine and explains why the other four stop at exactly one newline.
  • Rows 4–5, the strict anchors. \A\d+\z rejects 42\n everywhere it compiles — and Python 3.12 does not compile \z (3.14 does). Python's spelling is \Z, row 5, which rejects the newline there; the same \Z in Perl, Ruby, Java and fancy-regex accepts it, because their \Z is the newline-tolerant one. Go and Rust refuse \Z. Two escapes, and no engine agrees with its neighbour about both.
  • Row 6, no anchors. \d+ finds 42 inside abc 42 def. An unanchored validator validates that a number appears somewhere.
  • Rows 7–8, \d. ^\d+$ accepts Arabic-Indic digits in Python, Perl and Rust and rejects them in JavaScript, Ruby, Java and Go. ^[0-9]+$ rejects them in all eight. If "digit" means the ten ASCII digits, say so.
  • Rows 9–10, (?i)[a-z]. With case-insensitivity on, [a-z] accepts U+212A KELVIN SIGN in Python, Perl, Ruby, Go and both Rust crates, because it folds to k. Java says no (ASCII folding without UNICODE_CASE); JavaScript with the i flag says no. A username validator with (?i) lets a KELVIN SIGN register as k, and a uniqueness check that folds the other way will not find the collision. Case-insensitive matching has the rest.

The whole-string call, per language

Every language has a call that means "the whole string, no anchors needed", and it sidesteps rows 2–5 entirely:

Language Whole-string match
Python re.fullmatch(p, s)
Perl \A…\z in the pattern; there is no call
JavaScript ^…$ without m — the one engine where the plain anchors are strict
Ruby \A…\z in the pattern; match? does not anchor
Java String.matches(p) / Matcher.matches()
Go \A…\z in the pattern; MatchString does not anchor
Rust \A…\z, or is_match on an anchored pattern

The rules

  1. \A…\z, or the whole-string call. Never ^…$ in a validator, unless the engine is JavaScript and m is off.
  2. Classes, not shorthands, when the set of allowed characters is the point: [0-9], [A-Za-z], or a \p{…} property when the set is genuinely Unicode.
  3. No (?i) on a security decision. Fold in the host language with a documented function and match the folded form.
  4. Reject, then explain. A validator's error message can say what was wrong only if the code, not the pattern, made the decision — which is the argument for pattern-finds-shape, code-checks-value.

Keywords on this page

^ · $ · \A \z \Z · Shorthand classes · Character class · Inline flags.

See also