diff --git a/CHANGELOG.txt b/CHANGELOG.txt index ed859b8..dd8f8fd 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,7 @@ +=== UNRELEASED + +* Fixed `$` in `patternProperties` matching before a trailing newline, which also made a `pattern` with the same regex accept a trailing newline + === 2.22.3 (2026-11-01) * Fixed property names with `{` or `}` being evaluated as format string in error messages diff --git a/fastjsonschema/draft04.py b/fastjsonschema/draft04.py index 9d8eca3..cba1e48 100644 --- a/fastjsonschema/draft04.py +++ b/fastjsonschema/draft04.py @@ -596,7 +596,8 @@ def generate_pattern_properties(self): if pattern_prop_definition == {}: return for pattern, definition in pattern_prop_definition.items(): - self._compile_regexps[pattern] = re.compile(pattern) + end_of_string_fixed_pattern = DOLLAR_FINDER.sub(r'\\Z', pattern) + self._compile_regexps[pattern] = re.compile(end_of_string_fixed_pattern) with self.l('for {variable}_key, {variable}_val in {variable}.items():'): for pattern, definition in self._definition['patternProperties'].items(): with self.l('if REGEX_PATTERNS[{}].search({variable}_key):', repr(pattern)): diff --git a/tests/test_pattern_properties.py b/tests/test_pattern_properties.py index 3c07aff..9f4e661 100644 --- a/tests/test_pattern_properties.py +++ b/tests/test_pattern_properties.py @@ -2,6 +2,8 @@ import pytest +from fastjsonschema import JsonSchemaValueException + def test_dont_override_variable_names(asserter): value = { @@ -76,3 +78,30 @@ def test_empty_pattern(asserter): 'type': 'object', 'patternProperties': {} }, value, value) + + +@pytest.mark.parametrize('value, expected', [ + ({'abc': 1}, {'abc': 1}), + ({'abc\n': 1}, JsonSchemaValueException('data must not contain {\'abc\\n\'} properties', value='{data}', name='data', definition='{definition}', rule='additionalProperties')), +]) +def test_pattern_dollar_does_not_match_trailing_newline(asserter, value, expected): + asserter({ + 'type': 'object', + 'patternProperties': { + '^[a-z]+$': {}, + }, + 'additionalProperties': False, + }, value, expected) + + +@pytest.mark.parametrize('value, expected', [ + ('abc', 'abc'), + ('abc\n', JsonSchemaValueException('data must match pattern ^[a-z]+$', value='{data}', name='data', definition='{definition}', rule='pattern')), +]) +def test_pattern_shared_with_pattern_properties(asserter, value, expected): + asserter({ + 'pattern': '^[a-z]+$', + 'patternProperties': { + '^[a-z]+$': {}, + }, + }, value, expected)