Coverage for nexios\converters.py: 57%
75 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-21 20:31 +0100
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-21 20:31 +0100
1"""
2implemented from starlatte,
3"""
5from __future__ import annotations
7import math
8import typing
9import uuid
10import re
11from nexios.types import Scope
13T = typing.TypeVar("T")
16class Convertor(typing.Generic[T]):
17 regex: typing.ClassVar[str] = ""
19 def convert(self, value: str) -> T:
20 raise NotImplementedError() # pragma: no cover
22 def to_string(self, value: T) -> str:
23 raise NotImplementedError() # pragma: no cover
26class StringConvertor(Convertor[str]):
27 regex = "[^/]+"
29 def convert(self, value: str) -> str:
30 return value
32 def to_string(self, value: str) -> str:
33 value = str(value)
34 assert "/" not in value, "May not contain path separators"
35 assert value, "Must not be empty"
36 return value
39class PathConvertor(Convertor[str]):
40 regex = ".*"
42 def convert(self, value: str) -> str:
43 return value
45 def to_string(self, value: str) -> str:
46 return value
49class IntegerConvertor(Convertor[int]):
50 regex = "[0-9]+"
52 def convert(self, value: str) -> int:
53 return int(value)
55 def to_string(self, value: int) -> str:
56 value = int(value)
57 assert value >= 0, "Negative integers are not supported"
58 return str(value)
61class FloatConvertor(Convertor[float]):
62 regex = r"[0-9]+(\.[0-9]+)?"
64 def convert(self, value: str) -> float:
65 return float(value)
67 def to_string(self, value: float) -> str:
68 value = float(value)
69 assert value >= 0.0, "Negative floats are not supported"
70 assert not math.isnan(value), "NaN values are not supported"
71 assert not math.isinf(value), "Infinite values are not supported"
72 return ("%0.20f" % value).rstrip("0").rstrip(".")
75class UUIDConvertor(Convertor[uuid.UUID]):
76 regex = "[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}"
78 def convert(self, value: str) -> uuid.UUID:
79 return uuid.UUID(value)
81 def to_string(self, value: uuid.UUID) -> str:
82 return str(value)
85class SlugConvertor(Convertor[str]):
86 """Converter for slugs (URL-friendly strings)."""
88 regex = r"[a-z0-9]+(?:-[a-z0-9]+)*"
90 def convert(self, value: str) -> str:
91 if not re.fullmatch(self.regex, value):
92 raise ValueError(f"Invalid slug format: {value}")
93 return value
95 def to_string(self, value: str) -> str:
96 if not re.fullmatch(self.regex, value):
97 raise ValueError(f"Invalid slug format: {value}")
98 return value
101CONVERTOR_TYPES: dict[str, Convertor[typing.Any]] = {
102 "str": StringConvertor(),
103 "path": PathConvertor(),
104 "int": IntegerConvertor(),
105 "float": FloatConvertor(),
106 "uuid": UUIDConvertor(),
107 "slug": SlugConvertor(),
108}
111def register_url_convertor(key: str, convertor: Convertor[typing.Any]) -> None:
112 CONVERTOR_TYPES[key] = convertor
115def get_route_path(scope: Scope) -> str:
116 path: str = scope["path"]
117 root_path = scope.get("root_path", "")
118 if not root_path:
119 return path
121 if not path.startswith(root_path):
122 return path
124 if path == root_path:
125 return ""
127 if path[len(root_path)] == "/":
128 return path[len(root_path) :]
130 return path