From 05d4884ccdbb8ec5eff2b2a0d69f05e3fb19c3c7 Mon Sep 17 00:00:00 2001 From: Jose Tomas Robles Hahn Date: Tue, 16 Sep 2025 12:32:31 -0300 Subject: [PATCH] feat: Add regex for canonical RUT that is compatible with JSON Schema Add a regular expression for canonical RUTs that is compatible with JSON Schema and OpenAPI: `cl_sii.rut.constants.RUT_CANONICAL_STRICT_JSON_SCHEMA_REGEX` --- src/cl_sii/extras/pydantic_types.py | 9 +--- src/cl_sii/rut/constants.py | 12 +++++ src/tests/test_rut_constants.py | 81 ++++++++++++++++++++++++++++- 3 files changed, 94 insertions(+), 8 deletions(-) diff --git a/src/cl_sii/extras/pydantic_types.py b/src/cl_sii/extras/pydantic_types.py index 86724434..66ae221d 100644 --- a/src/cl_sii/extras/pydantic_types.py +++ b/src/cl_sii/extras/pydantic_types.py @@ -4,7 +4,6 @@ from __future__ import annotations -import re import sys from typing import Any, ClassVar, Pattern @@ -79,12 +78,8 @@ class _RutPydanticAnnotation: >>> example_json_schema = example_type_adapter.json_schema() """ - RUT_CANONICAL_STRICT_REGEX: ClassVar[Pattern] = re.compile( - re.sub( - pattern=r'\?P<\w+>', - repl='', - string=cl_sii.rut.constants.RUT_CANONICAL_STRICT_REGEX.pattern, - ) + RUT_CANONICAL_STRICT_REGEX: ClassVar[Pattern] = ( + cl_sii.rut.constants.RUT_CANONICAL_STRICT_JSON_SCHEMA_REGEX ) """ RUT (strict) regex for canonical format, without named groups. diff --git a/src/cl_sii/rut/constants.py b/src/cl_sii/rut/constants.py index b5086386..751c2e61 100644 --- a/src/cl_sii/rut/constants.py +++ b/src/cl_sii/rut/constants.py @@ -7,6 +7,7 @@ """ import re +from typing import Pattern import cryptography.x509 @@ -22,6 +23,17 @@ RUT_DIGITS_MIN_VALUE = 1 """RUT digits min value.""" +RUT_CANONICAL_STRICT_JSON_SCHEMA_REGEX: Pattern[str] = re.compile("^(\\d{1,8})-([\\dK])$") +""" +RUT (strict) JSON Schema regex for canonical format. + +This regex is compatible with JSON Schema and OpenAPI, which use the regular expression syntax from +JavaScript (ECMA 262), which does not support Python’s named groups. + +.. tip:: If you need the regex as a string, for example to use it in a JSON Schema or + OpenAPI schema, use ``RUT_CANONICAL_STRICT_JSON_SCHEMA_REGEX.pattern``. +""" + SII_CERT_TITULAR_RUT_OID = cryptography.x509.oid.ObjectIdentifier("1.3.6.1.4.1.8321.1") """OID of the RUT of the certificate holder""" # - Organismo: MINISTERIO DE ECONOMÍA / SUBSECRETARIA DE ECONOMIA diff --git a/src/tests/test_rut_constants.py b/src/tests/test_rut_constants.py index 84e62a7e..7031ff89 100644 --- a/src/tests/test_rut_constants.py +++ b/src/tests/test_rut_constants.py @@ -1,7 +1,10 @@ from __future__ import annotations +import re import unittest -from typing import ClassVar +from typing import ClassVar, Pattern + +import jsonschema from cl_sii.rut import constants @@ -34,3 +37,79 @@ def test_persona_juridica_min_value(self) -> None: self.assertGreaterEqual(min_rut_digits, self.RUT_DIGITS_MIN_VALUE) self.assertLessEqual(min_rut_digits, self.RUT_DIGITS_MAX_VALUE) + + +class RutRegexConstantsTestCase(unittest.TestCase): + RUT_CANONICAL_STRICT_REGEX: ClassVar[Pattern[str]] + RUT_CANONICAL_STRICT_JSON_SCHEMA_REGEX: ClassVar[Pattern[str]] + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + + cls.RUT_CANONICAL_STRICT_REGEX = constants.RUT_CANONICAL_STRICT_REGEX + cls.RUT_CANONICAL_STRICT_JSON_SCHEMA_REGEX = ( + constants.RUT_CANONICAL_STRICT_JSON_SCHEMA_REGEX + ) + + def test_json_schema_regex_is_python_regex_without_named_groups(self) -> None: + # %% -----Arrange----- + + python_regex = self.RUT_CANONICAL_STRICT_REGEX + python_regex_without_named_groups = re.compile( + re.sub( + pattern=r'\?P<\w+>', + repl='', + string=python_regex.pattern, + ) + ) + expected = python_regex_without_named_groups + + # %% -----Act----- + + actual = self.RUT_CANONICAL_STRICT_JSON_SCHEMA_REGEX + + # %% -----Assert----- + + self.assertEqual(expected, actual) + + # %% ----- + + def test_json_schema_regex_is_valid_schema(self) -> None: + # %% -----Arrange----- + + schema = { + "type": "string", + "pattern": self.RUT_CANONICAL_STRICT_JSON_SCHEMA_REGEX.pattern, + } + valid_test_values = [ + '0-0', + '1-9', + '6-K', + '78773510-K', + ] + invalid_test_values = [ + '6K', + '6-k', + '78773510-k', + '78.773.510-K', + 78773510, + 1.9, + None, + ] + + # %% -----Act & Assert----- + + for test_value in valid_test_values: + with self.subTest(test_value=test_value): + try: + jsonschema.validate(instance=test_value, schema=schema) + except jsonschema.exceptions.ValidationError as exc: + self.fail(f'{exc.__class__.__name__} raised') + + for invalid_test_value in invalid_test_values: + with self.subTest(test_value=invalid_test_value): + with self.assertRaises(jsonschema.exceptions.ValidationError): + jsonschema.validate(instance=invalid_test_value, schema=schema) + + # %% -----