From 7a02efd42d3184f591639fe6fbc7fa7a878a60e5 Mon Sep 17 00:00:00 2001 From: Wong Ding Feng Date: Sun, 31 May 2026 23:53:00 +0800 Subject: [PATCH] feat: path templating for endpoint signatures --- src/auto_reverse/store.py | 26 ++++++++++++++++++++++++++ tests/test_store.py | 23 +++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/auto_reverse/store.py create mode 100644 tests/test_store.py diff --git a/src/auto_reverse/store.py b/src/auto_reverse/store.py new file mode 100644 index 0000000..da0c662 --- /dev/null +++ b/src/auto_reverse/store.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +import re + +_UUID = re.compile(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$") +_HEX_TOKEN = re.compile(r"^[0-9a-fA-F]{16,}$") +_LONG_OPAQUE = re.compile(r"^[A-Za-z0-9_\-]{20,}$") + + +def _is_variable(segment: str) -> bool: + if segment.isdigit(): + return True + if _UUID.match(segment): + return True + if _HEX_TOKEN.match(segment): + return True + return bool(_LONG_OPAQUE.match(segment) and any(c.isdigit() for c in segment)) + + +def path_template(path: str) -> str: + """Collapse variable path segments (ids, UUIDs, hashes, opaque tokens) to {id}.""" + if not path or path == "/": + return "/" + parts = path.split("/") + out = ["{id}" if part and _is_variable(part) else part for part in parts] + return "/".join(out) diff --git a/tests/test_store.py b/tests/test_store.py new file mode 100644 index 0000000..995a05e --- /dev/null +++ b/tests/test_store.py @@ -0,0 +1,23 @@ +from auto_reverse.store import path_template + + +def test_collapses_numeric_ids(): + assert path_template("/api/users/4812/orders/99") == "/api/users/{id}/orders/{id}" + + +def test_collapses_uuid(): + p = "/api/items/550e8400-e29b-41d4-a716-446655440000" + assert path_template(p) == "/api/items/{id}" + + +def test_collapses_long_hex_token(): + assert path_template("/files/a1b2c3d4e5f60718293a4b5c") == "/files/{id}" + + +def test_keeps_short_words(): + assert path_template("/api/users/me/settings") == "/api/users/me/settings" + + +def test_root_and_empty(): + assert path_template("/") == "/" + assert path_template("") == "/"