From 8d98901355f1291efdb090b26a734497952cf79d Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Thu, 27 Jan 2022 09:51:06 +0200 Subject: [PATCH 1/2] Only match non-empty string --- sanic_routing/patterns.py | 9 +++++- sanic_routing/router.py | 2 +- tests/test_builtin_param_types.py | 54 ++++++++++++++++++++++++++++++- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/sanic_routing/patterns.py b/sanic_routing/patterns.py index 696be15..0ea604c 100644 --- a/sanic_routing/patterns.py +++ b/sanic_routing/patterns.py @@ -19,6 +19,12 @@ def slug(param: str) -> str: return param +def nonemptystr(param: str) -> str: + if not param: + raise ValueError(f"Value {param} is an empty string") + return param + + REGEX_PARAM_NAME = re.compile(r"^<([a-zA-Z_][a-zA-Z0-9_]*)(?::(.*))?>$") # Predefined path parameter types. The value is a tuple consisteing of a @@ -30,7 +36,8 @@ def slug(param: str) -> str: # The regular expression is generally NOT used. Unless the path is forced # to use regex patterns. REGEX_TYPES = { - "str": (str, re.compile(r"^[^/]+$")), + "strorempty": (str, re.compile(r"^[^/]*$")), + "str": (nonemptystr, re.compile(r"^[^/]+$")), "slug": (slug, re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")), "alpha": (alpha, re.compile(r"^[A-Za-z]+$")), "path": (str, re.compile(r"^[^/]?.*?$")), diff --git a/sanic_routing/router.py b/sanic_routing/router.py index c978b7c..a904d56 100644 --- a/sanic_routing/router.py +++ b/sanic_routing/router.py @@ -27,7 +27,7 @@ from datetime import datetime # noqa isort:skip from urllib.parse import unquote # noqa isort:skip from uuid import UUID # noqa isort:skip -from .patterns import parse_date, alpha, slug # noqa isort:skip +from .patterns import parse_date, alpha, slug, nonemptystr # noqa isort:skip class BaseRouter(ABC): diff --git a/tests/test_builtin_param_types.py b/tests/test_builtin_param_types.py index cd61b0a..2100912 100644 --- a/tests/test_builtin_param_types.py +++ b/tests/test_builtin_param_types.py @@ -1,5 +1,4 @@ import pytest - from sanic_routing import BaseRouter from sanic_routing.exceptions import NotFound @@ -131,3 +130,56 @@ def test_correct_slug_v_string(handler): assert isinstance(retval, str) assert retval == "FooBar" + + +@pytest.mark.parametrize( + "value,matches", + ( + ("foo", True), + ("FooBar", True), + ("with123456789", True), + ("", False), + ), +) +def test_nonempty_string(handler, value, matches): + def test(path): + nonlocal handler + router = Router() + + router.add(path, handler) + router.finalize() + + if matches: + _, handler, params = router.get(f"/{value}", "BASE") + retval = handler(**params) + + assert isinstance(retval, str) + assert retval == value + else: + with pytest.raises(NotFound): + router.get(f"/{value}", "BASE") + + for path in ("/", "/"): + test(path) + + +@pytest.mark.parametrize( + "value", + ( + "foo", + "FooBar", + "with123456789", + "", + ), +) +def test_empty_string(handler, value): + router = Router() + + router.add("/", handler) + router.finalize() + + _, handler, params = router.get(f"/{value}", "BASE") + retval = handler(**params) + + assert isinstance(retval, str) + assert retval == value From b3fe0a0e1bd10230006338a971cd95ada40bd4df Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Thu, 27 Jan 2022 10:01:20 +0200 Subject: [PATCH 2/2] Add unit test --- tests/test_builtin_param_types.py | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/test_builtin_param_types.py b/tests/test_builtin_param_types.py index 2100912..b08e503 100644 --- a/tests/test_builtin_param_types.py +++ b/tests/test_builtin_param_types.py @@ -1,3 +1,5 @@ +from unittest.mock import Mock + import pytest from sanic_routing import BaseRouter from sanic_routing.exceptions import NotFound @@ -183,3 +185,50 @@ def test_empty_string(handler, value): assert isinstance(retval, str) assert retval == value + + +def test_nonempty_hierarchy(): + handler1 = Mock() + handler2 = Mock() + router = Router() + + router.add("/one/", handler1) + router.add("/one//", handler2) + router.finalize() + + _, handler, params = router.get("/one/two/", "BASE") + expected = {"foo": "two"} + handler(**params) + + assert params == expected + handler1.assert_called_once_with(**expected) + handler2.assert_not_called() + + handler1.reset_mock() + handler2.reset_mock() + + _, handler, params = router.get("/one/two/three/", "BASE") + expected = {"foo": "two", "bar": "three"} + handler(**params) + + assert params == expected + handler1.assert_not_called() + handler2.assert_called_once_with(**expected) + + +def test_empty_hierarchy(): + handler1 = Mock() + handler2 = Mock() + router = Router() + + router.add("/one/", handler1) + router.add("/one//", handler2) + router.finalize() + + _, handler, params = router.get("/one/two/", "BASE") + expected = {"foo": "two", "bar": ""} + handler(**params) + + assert params == expected + handler1.assert_not_called() + handler2.assert_called_once_with(**expected)