From: Aleš Mrázek Date: Tue, 20 Jun 2023 12:30:18 +0000 (+0200) Subject: manager: datamodel: types: EscapedStr type X-Git-Tag: v6.0.2~31^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ee3151d7066400d3c03fa97a6323518d1441606b;p=thirdparty%2Fknot-resolver.git manager: datamodel: types: EscapedStr type --- diff --git a/manager/knot_resolver_manager/datamodel/types/__init__.py b/manager/knot_resolver_manager/datamodel/types/__init__.py index 0b708c4a9..d70d33326 100644 --- a/manager/knot_resolver_manager/datamodel/types/__init__.py +++ b/manager/knot_resolver_manager/datamodel/types/__init__.py @@ -3,6 +3,7 @@ from .files import AbsoluteDir, Dir, File, FilePath from .generic_types import ListOrItem from .types import ( DomainName, + EscapedStr, IDPattern, Int0_512, Int0_65535, @@ -31,6 +32,7 @@ __all__ = [ "PolicyFlagEnum", "DNSRecordTypeEnum", "DomainName", + "EscapedStr", "IDPattern", "Int0_512", "Int0_65535", diff --git a/manager/knot_resolver_manager/datamodel/types/types.py b/manager/knot_resolver_manager/datamodel/types/types.py index 2b409e7e5..a2ad074af 100644 --- a/manager/knot_resolver_manager/datamodel/types/types.py +++ b/manager/knot_resolver_manager/datamodel/types/types.py @@ -64,6 +64,36 @@ class TimeUnit(UnitBase): return self._base_value +class EscapedStr(StrBase): + """ + A string where escape sequences are ignored and quotes are escaped. + """ + + def __init__(self, source_value: Any, object_path: str = "/") -> None: + super().__init__(source_value, object_path) + + escape = { + "'": r"\'", + '"': r"\"", + "\a": r"\a", + "\n": r"\n", + "\r": r"\r", + "\t": r"\t", + "\b": r"\b", + "\f": r"\f", + "\v": r"\v", + "\0": r"\0", + } + + s = list(self._value) + for i, c in enumerate(self._value): + if c in escape: + s[i] = escape[c] + elif not c.isalnum(): + s[i] = repr(c)[1:-1] + self._value = "".join(s) + + class DomainName(StrBase): """ Fully or partially qualified domain name. diff --git a/manager/tests/unit/datamodel/templates/test_types_render.py b/manager/tests/unit/datamodel/templates/test_types_render.py new file mode 100644 index 000000000..15a0611a4 --- /dev/null +++ b/manager/tests/unit/datamodel/templates/test_types_render.py @@ -0,0 +1,32 @@ +from typing import Any + +import pytest +from jinja2 import Template + +from knot_resolver_manager.datamodel.types import EscapedStr +from knot_resolver_manager.utils.modeling import ConfigSchema + +str_template = Template("'{{ string }}'") + + +@pytest.mark.parametrize( + "val,exp", + [ + ("", ""), + ("string", "string"), + (2000, "2000"), + ('"\a\b\f\n\r\t\v\\"', r"\"\a\b\f\n\r\t\v\\\""), + ('""', r"\"\""), + ("''", r"\'\'"), + # fmt: off + ('\"\"', r'\"\"'), + ("\'\'", r'\'\''), + # fmt: on + ], +) +def test_escaped_str(val: Any, exp: str): + class TestSchema(ConfigSchema): + pattern: EscapedStr + + d = TestSchema({"pattern": val}) + assert str_template.render(string=d.pattern) == f"'{exp}'" diff --git a/manager/tests/unit/datamodel/types/test_custom_types.py b/manager/tests/unit/datamodel/types/test_custom_types.py index 3e1f5c61d..ac95b79cf 100644 --- a/manager/tests/unit/datamodel/types/test_custom_types.py +++ b/manager/tests/unit/datamodel/types/test_custom_types.py @@ -9,6 +9,7 @@ from pytest import raises from knot_resolver_manager.datamodel.types import ( Dir, DomainName, + EscapedStr, InterfaceName, InterfaceOptionalPort, InterfacePort, @@ -117,6 +118,34 @@ def test_pin_sha256_invalid(val: str): PinSha256(val) +@pytest.mark.parametrize( + "val,exp", + [ + ("", r""), + (2000, "2000"), + ("string", r"string"), + ("\t\n\v", r"\t\n\v"), + ("\a\b\f\n\r\t\v\\", r"\a\b\f\n\r\t\v\\"), + # fmt: off + ("''", r"\'\'"), + ('""', r'\"\"'), + ("\'\'", r"\'\'"), + ('\"\"', r'\"\"'), + ('\\"\\"', r'\\\"\\\"'), + ("\\'\\'", r"\\\'\\\'"), + # fmt: on + ], +) +def test_escaped_str_valid(val: Any, exp: str): + assert str(EscapedStr(val)) == exp + + +@pytest.mark.parametrize("val", [1.1, False]) +def test_escaped_str_invalid(val: Any): + with raises(ValueError): + EscapedStr(val) + + @pytest.mark.parametrize( "val", [