pattern_properties.py 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  1. from collections.abc import Mapping
  2. from typing import Any
  3. _UNSUPPORTED_REGEX_TOKENS = frozenset({".", "^", "$", "*", "+", "?", "{", "}", "[", "]", "|", "(", ")", "\\"})
  4. def match_pattern_properties(
  5. pattern_properties: Mapping[str, Any],
  6. key: str,
  7. ) -> tuple[list[Any], list[str]]:
  8. """Match JSON Schema patternProperties using a safe literal+anchor subset.
  9. Supported forms:
  10. - "token" -> key contains token
  11. - "^token" -> key starts with token
  12. - "token$" -> key ends with token
  13. - "^token$" -> key equals token
  14. Any pattern using additional regex tokens is treated as unsupported and skipped.
  15. The caller can log unsupported patterns using the returned list.
  16. """
  17. if not pattern_properties:
  18. return [], []
  19. matched_schemas: list[Any] = []
  20. unsupported_patterns: list[str] = []
  21. for pattern, schema in pattern_properties.items():
  22. anchored_start = pattern.startswith("^")
  23. anchored_end = pattern.endswith("$")
  24. literal = pattern[1 if anchored_start else 0 : -1 if anchored_end else None]
  25. if any(token in literal for token in _UNSUPPORTED_REGEX_TOKENS):
  26. unsupported_patterns.append(pattern)
  27. continue
  28. if anchored_start and anchored_end:
  29. is_match = key == literal
  30. elif anchored_start:
  31. is_match = key.startswith(literal)
  32. elif anchored_end:
  33. is_match = key.endswith(literal)
  34. else:
  35. is_match = literal in key
  36. if is_match:
  37. matched_schemas.append(schema)
  38. return matched_schemas, unsupported_patterns