Skip to content

Fix StringValidator counting bytes instead of code points for minLength/maxLength#1038

Open
danieleades wants to merge 1 commit into
oxidecomputer:mainfrom
danieleades:fix/string-length-chars
Open

Fix StringValidator counting bytes instead of code points for minLength/maxLength#1038
danieleades wants to merge 1 commit into
oxidecomputer:mainfrom
danieleades:fix/string-length-chars

Conversation

@danieleades

Copy link
Copy Markdown
Contributor

Bug

JSON Schema's minLength/maxLength (validation spec §6.3, and consistent with RFC 8259's treatment of JSON strings) are defined to count Unicode code points, not bytes.

StringValidator::is_valid in typify-impl/src/util.rs compared against s.len(), which in Rust is the byte length of a &str. This meant a string like "héllo" (5 code points, 6 UTF-8 bytes) could be wrongly rejected by a maxLength: 5 constraint, and multi-byte strings could wrongly pass a minLength check they shouldn't.

StringValidator is used at schema-processing time (e.g. matching string constants/enum variants against string constraints in convert.rs), separately from the generated Rust validation code.

Note: the generated FromStr/TryFrom validation code in type_entry.rs already uses value.chars().count() for this, fixed previously in #776 for issue #775. That fix didn't touch StringValidator, which has the same class of bug.

Fix

Switch both the max_length and min_length comparisons in StringValidator::is_valid from s.as_ref().len() to s.as_ref().chars().count(), matching the approach already used in the generated code.

Trade-off

chars().count() is O(n) versus O(1) for len(), but this is negligible on validation paths. Using code points (rather than grapheme clusters) matches what the JSON Schema spec actually specifies.

Testing

Added unit tests in typify-impl/src/util.rs covering multi-byte characters ("héllo") and 4-byte code points (emoji) at exact boundary lengths for both minLength and maxLength.

cargo test --workspace --locked and cargo fmt --all -- --check pass.

JSON Schema (and RFC 8259) define minLength/maxLength as counting
Unicode code points, not UTF-8 bytes. StringValidator::is_valid used
s.len(), which is a byte count in Rust, so multi-byte strings like
"héllo" (5 code points, 6 bytes) were wrongly rejected by a
maxLength: 5 constraint, and multi-byte strings could wrongly pass a
minLength check.

This mirrors the fix already applied to the generated FromStr/TryFrom
validation code in type_entry.rs (oxidecomputer#776), which this validator was
missed by. StringValidator is used at schema-processing time (e.g.
matching enum variants against string constraints), so it has the
same class of bug independently of the codegen output.

Switch both comparisons to chars().count() and add tests covering
multi-byte and 4-byte (emoji) code points at exact boundary lengths.
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.

1 participant