hypern 0.3.12__tar.gz → 0.3.13__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. {hypern-0.3.12 → hypern-0.3.13}/Cargo.lock +1 -1
  2. {hypern-0.3.12 → hypern-0.3.13}/Cargo.toml +1 -1
  3. {hypern-0.3.12 → hypern-0.3.13}/PKG-INFO +1 -1
  4. {hypern-0.3.12 → hypern-0.3.13}/pyproject.toml +2 -2
  5. hypern-0.3.13/src/router/radix.rs +226 -0
  6. hypern-0.3.12/src/router/radix.rs +0 -160
  7. {hypern-0.3.12 → hypern-0.3.13}/.github/dependabot.yml +0 -0
  8. {hypern-0.3.12 → hypern-0.3.13}/.github/workflows/codeql.yml +0 -0
  9. {hypern-0.3.12 → hypern-0.3.13}/.github/workflows/codspeed.yml +0 -0
  10. {hypern-0.3.12 → hypern-0.3.13}/.github/workflows/preview-deployments.yml +0 -0
  11. {hypern-0.3.12 → hypern-0.3.13}/.github/workflows/release-CI.yml +0 -0
  12. {hypern-0.3.12 → hypern-0.3.13}/.github/workflows/security-scan.yml +0 -0
  13. {hypern-0.3.12 → hypern-0.3.13}/.gitignore +0 -0
  14. {hypern-0.3.12 → hypern-0.3.13}/.pre-commit-config.yaml +0 -0
  15. {hypern-0.3.12 → hypern-0.3.13}/CODE_OF_CONDUCT.md +0 -0
  16. {hypern-0.3.12 → hypern-0.3.13}/LICENSE +0 -0
  17. {hypern-0.3.12 → hypern-0.3.13}/README.md +0 -0
  18. {hypern-0.3.12 → hypern-0.3.13}/SECURITY.md +0 -0
  19. {hypern-0.3.12 → hypern-0.3.13}/benchmark.sh +0 -0
  20. {hypern-0.3.12 → hypern-0.3.13}/docs/background.md +0 -0
  21. {hypern-0.3.12 → hypern-0.3.13}/docs/cache.md +0 -0
  22. {hypern-0.3.12 → hypern-0.3.13}/docs/database.md +0 -0
  23. {hypern-0.3.12 → hypern-0.3.13}/docs/index.md +0 -0
  24. {hypern-0.3.12 → hypern-0.3.13}/docs/response.md +0 -0
  25. {hypern-0.3.12 → hypern-0.3.13}/docs/schedule.md +0 -0
  26. {hypern-0.3.12 → hypern-0.3.13}/docs/websocket.md +0 -0
  27. {hypern-0.3.12 → hypern-0.3.13}/hypern/__init__.py +0 -0
  28. {hypern-0.3.12 → hypern-0.3.13}/hypern/application.py +0 -0
  29. {hypern-0.3.12 → hypern-0.3.13}/hypern/args_parser.py +0 -0
  30. {hypern-0.3.12 → hypern-0.3.13}/hypern/auth/__init__.py +0 -0
  31. {hypern-0.3.12 → hypern-0.3.13}/hypern/auth/authorization.py +0 -0
  32. {hypern-0.3.12 → hypern-0.3.13}/hypern/caching/__init__.py +0 -0
  33. {hypern-0.3.12 → hypern-0.3.13}/hypern/caching/backend.py +0 -0
  34. {hypern-0.3.12 → hypern-0.3.13}/hypern/caching/redis_backend.py +0 -0
  35. {hypern-0.3.12 → hypern-0.3.13}/hypern/caching/strategies.py +0 -0
  36. {hypern-0.3.12 → hypern-0.3.13}/hypern/cli/__init__.py +0 -0
  37. {hypern-0.3.12 → hypern-0.3.13}/hypern/cli/commands.py +0 -0
  38. {hypern-0.3.12 → hypern-0.3.13}/hypern/config.py +0 -0
  39. {hypern-0.3.12 → hypern-0.3.13}/hypern/database/__init__.py +0 -0
  40. {hypern-0.3.12 → hypern-0.3.13}/hypern/database/sqlalchemy/__init__.py +0 -0
  41. {hypern-0.3.12 → hypern-0.3.13}/hypern/database/sqlalchemy/config.py +0 -0
  42. {hypern-0.3.12 → hypern-0.3.13}/hypern/database/sqlalchemy/repository.py +0 -0
  43. {hypern-0.3.12 → hypern-0.3.13}/hypern/database/sqlx/__init__.py +0 -0
  44. {hypern-0.3.12 → hypern-0.3.13}/hypern/database/sqlx/field.py +0 -0
  45. {hypern-0.3.12 → hypern-0.3.13}/hypern/database/sqlx/migrate.py +0 -0
  46. {hypern-0.3.12 → hypern-0.3.13}/hypern/database/sqlx/model.py +0 -0
  47. {hypern-0.3.12 → hypern-0.3.13}/hypern/database/sqlx/query.py +0 -0
  48. {hypern-0.3.12 → hypern-0.3.13}/hypern/datastructures.py +0 -0
  49. {hypern-0.3.12 → hypern-0.3.13}/hypern/enum.py +0 -0
  50. {hypern-0.3.12 → hypern-0.3.13}/hypern/exceptions/__init__.py +0 -0
  51. {hypern-0.3.12 → hypern-0.3.13}/hypern/exceptions/base.py +0 -0
  52. {hypern-0.3.12 → hypern-0.3.13}/hypern/exceptions/common.py +0 -0
  53. {hypern-0.3.12 → hypern-0.3.13}/hypern/exceptions/errors.py +0 -0
  54. {hypern-0.3.12 → hypern-0.3.13}/hypern/exceptions/formatters.py +0 -0
  55. {hypern-0.3.12 → hypern-0.3.13}/hypern/exceptions/http.py +0 -0
  56. {hypern-0.3.12 → hypern-0.3.13}/hypern/gateway/__init__.py +0 -0
  57. {hypern-0.3.12 → hypern-0.3.13}/hypern/gateway/aggregator.py +0 -0
  58. {hypern-0.3.12 → hypern-0.3.13}/hypern/gateway/gateway.py +0 -0
  59. {hypern-0.3.12 → hypern-0.3.13}/hypern/gateway/proxy.py +0 -0
  60. {hypern-0.3.12 → hypern-0.3.13}/hypern/gateway/service.py +0 -0
  61. {hypern-0.3.12 → hypern-0.3.13}/hypern/hypern.pyi +0 -0
  62. {hypern-0.3.12 → hypern-0.3.13}/hypern/i18n/__init__.py +0 -0
  63. {hypern-0.3.12 → hypern-0.3.13}/hypern/logging/__init__.py +0 -0
  64. {hypern-0.3.12 → hypern-0.3.13}/hypern/logging/logger.py +0 -0
  65. {hypern-0.3.12 → hypern-0.3.13}/hypern/middleware/__init__.py +0 -0
  66. {hypern-0.3.12 → hypern-0.3.13}/hypern/middleware/base.py +0 -0
  67. {hypern-0.3.12 → hypern-0.3.13}/hypern/middleware/cache.py +0 -0
  68. {hypern-0.3.12 → hypern-0.3.13}/hypern/middleware/compress.py +0 -0
  69. {hypern-0.3.12 → hypern-0.3.13}/hypern/middleware/cors.py +0 -0
  70. {hypern-0.3.12 → hypern-0.3.13}/hypern/middleware/i18n.py +0 -0
  71. {hypern-0.3.12 → hypern-0.3.13}/hypern/middleware/limit.py +0 -0
  72. {hypern-0.3.12 → hypern-0.3.13}/hypern/middleware/security.py +0 -0
  73. {hypern-0.3.12 → hypern-0.3.13}/hypern/openapi/__init__.py +0 -0
  74. {hypern-0.3.12 → hypern-0.3.13}/hypern/openapi/schemas.py +0 -0
  75. {hypern-0.3.12 → hypern-0.3.13}/hypern/openapi/swagger.py +0 -0
  76. {hypern-0.3.12 → hypern-0.3.13}/hypern/processpool.py +0 -0
  77. {hypern-0.3.12 → hypern-0.3.13}/hypern/py.typed +0 -0
  78. {hypern-0.3.12 → hypern-0.3.13}/hypern/reload.py +0 -0
  79. {hypern-0.3.12 → hypern-0.3.13}/hypern/response/__init__.py +0 -0
  80. {hypern-0.3.12 → hypern-0.3.13}/hypern/response/response.py +0 -0
  81. {hypern-0.3.12 → hypern-0.3.13}/hypern/routing/__init__.py +0 -0
  82. {hypern-0.3.12 → hypern-0.3.13}/hypern/routing/dispatcher.py +0 -0
  83. {hypern-0.3.12 → hypern-0.3.13}/hypern/routing/endpoint.py +0 -0
  84. {hypern-0.3.12 → hypern-0.3.13}/hypern/routing/parser.py +0 -0
  85. {hypern-0.3.12 → hypern-0.3.13}/hypern/routing/queue.py +0 -0
  86. {hypern-0.3.12 → hypern-0.3.13}/hypern/routing/route.py +0 -0
  87. {hypern-0.3.12 → hypern-0.3.13}/hypern/worker.py +0 -0
  88. {hypern-0.3.12 → hypern-0.3.13}/hypern/ws/__init__.py +0 -0
  89. {hypern-0.3.12 → hypern-0.3.13}/hypern/ws/channel.py +0 -0
  90. {hypern-0.3.12 → hypern-0.3.13}/hypern/ws/heartbeat.py +0 -0
  91. {hypern-0.3.12 → hypern-0.3.13}/hypern/ws/room.py +0 -0
  92. {hypern-0.3.12 → hypern-0.3.13}/hypern/ws/route.py +0 -0
  93. {hypern-0.3.12 → hypern-0.3.13}/poetry.lock +0 -0
  94. {hypern-0.3.12 → hypern-0.3.13}/scripts/format.sh +0 -0
  95. {hypern-0.3.12 → hypern-0.3.13}/sonar-project.properties +0 -0
  96. {hypern-0.3.12 → hypern-0.3.13}/src/background/background_task.rs +0 -0
  97. {hypern-0.3.12 → hypern-0.3.13}/src/background/background_tasks.rs +0 -0
  98. {hypern-0.3.12 → hypern-0.3.13}/src/background/mod.rs +0 -0
  99. {hypern-0.3.12 → hypern-0.3.13}/src/database/context.rs +0 -0
  100. {hypern-0.3.12 → hypern-0.3.13}/src/database/mod.rs +0 -0
  101. {hypern-0.3.12 → hypern-0.3.13}/src/database/sql/config.rs +0 -0
  102. {hypern-0.3.12 → hypern-0.3.13}/src/database/sql/connection.rs +0 -0
  103. {hypern-0.3.12 → hypern-0.3.13}/src/database/sql/db_trait.rs +0 -0
  104. {hypern-0.3.12 → hypern-0.3.13}/src/database/sql/mod.rs +0 -0
  105. {hypern-0.3.12 → hypern-0.3.13}/src/database/sql/mysql.rs +0 -0
  106. {hypern-0.3.12 → hypern-0.3.13}/src/database/sql/postgresql.rs +0 -0
  107. {hypern-0.3.12 → hypern-0.3.13}/src/database/sql/sqlite.rs +0 -0
  108. {hypern-0.3.12 → hypern-0.3.13}/src/database/sql/transaction.rs +0 -0
  109. {hypern-0.3.12 → hypern-0.3.13}/src/di.rs +0 -0
  110. {hypern-0.3.12 → hypern-0.3.13}/src/executor.rs +0 -0
  111. {hypern-0.3.12 → hypern-0.3.13}/src/instants.rs +0 -0
  112. {hypern-0.3.12 → hypern-0.3.13}/src/lib.rs +0 -0
  113. {hypern-0.3.12 → hypern-0.3.13}/src/middlewares/base.rs +0 -0
  114. {hypern-0.3.12 → hypern-0.3.13}/src/middlewares/mod.rs +0 -0
  115. {hypern-0.3.12 → hypern-0.3.13}/src/openapi/mod.rs +0 -0
  116. {hypern-0.3.12 → hypern-0.3.13}/src/openapi/schemas.rs +0 -0
  117. {hypern-0.3.12 → hypern-0.3.13}/src/openapi/swagger.rs +0 -0
  118. {hypern-0.3.12 → hypern-0.3.13}/src/router/mod.rs +0 -0
  119. {hypern-0.3.12 → hypern-0.3.13}/src/router/route.rs +0 -0
  120. {hypern-0.3.12 → hypern-0.3.13}/src/router/router.rs +0 -0
  121. {hypern-0.3.12 → hypern-0.3.13}/src/scheduler/job.rs +0 -0
  122. {hypern-0.3.12 → hypern-0.3.13}/src/scheduler/mod.rs +0 -0
  123. {hypern-0.3.12 → hypern-0.3.13}/src/scheduler/retry.rs +0 -0
  124. {hypern-0.3.12 → hypern-0.3.13}/src/scheduler/scheduler.rs +0 -0
  125. {hypern-0.3.12 → hypern-0.3.13}/src/server.rs +0 -0
  126. {hypern-0.3.12 → hypern-0.3.13}/src/types/body.rs +0 -0
  127. {hypern-0.3.12 → hypern-0.3.13}/src/types/function_info.rs +0 -0
  128. {hypern-0.3.12 → hypern-0.3.13}/src/types/header.rs +0 -0
  129. {hypern-0.3.12 → hypern-0.3.13}/src/types/middleware.rs +0 -0
  130. {hypern-0.3.12 → hypern-0.3.13}/src/types/mod.rs +0 -0
  131. {hypern-0.3.12 → hypern-0.3.13}/src/types/query.rs +0 -0
  132. {hypern-0.3.12 → hypern-0.3.13}/src/types/request.rs +0 -0
  133. {hypern-0.3.12 → hypern-0.3.13}/src/types/response.rs +0 -0
  134. {hypern-0.3.12 → hypern-0.3.13}/src/ws/mod.rs +0 -0
  135. {hypern-0.3.12 → hypern-0.3.13}/src/ws/route.rs +0 -0
  136. {hypern-0.3.12 → hypern-0.3.13}/src/ws/router.rs +0 -0
  137. {hypern-0.3.12 → hypern-0.3.13}/src/ws/socket.rs +0 -0
  138. {hypern-0.3.12 → hypern-0.3.13}/src/ws/websocket.rs +0 -0
  139. {hypern-0.3.12 → hypern-0.3.13}/tests/__init__.py +0 -0
  140. {hypern-0.3.12 → hypern-0.3.13}/tests/conftest.py +0 -0
  141. {hypern-0.3.12 → hypern-0.3.13}/tests/server.py +0 -0
  142. {hypern-0.3.12 → hypern-0.3.13}/tests/test_functional_handler.py +0 -0
  143. {hypern-0.3.12 → hypern-0.3.13}/tests/test_request_file.py +0 -0
  144. {hypern-0.3.12 → hypern-0.3.13}/tests/test_response_type.py +0 -0
  145. {hypern-0.3.12 → hypern-0.3.13}/tests/test_sync_async.py +0 -0
  146. {hypern-0.3.12 → hypern-0.3.13}/tests/test_validate_field.py +0 -0
  147. {hypern-0.3.12 → hypern-0.3.13}/tests/utils.py +0 -0
@@ -985,7 +985,7 @@ dependencies = [
985
985
 
986
986
  [[package]]
987
987
  name = "hypern"
988
- version = "0.3.12"
988
+ version = "0.3.13"
989
989
  dependencies = [
990
990
  "bytes",
991
991
  "chrono",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "hypern"
3
- version = "0.3.12"
3
+ version = "0.3.13"
4
4
  edition = "2021"
5
5
 
6
6
  # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hypern
3
- Version: 0.3.12
3
+ Version: 0.3.13
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -5,7 +5,7 @@ build-backend = "maturin"
5
5
 
6
6
  [project]
7
7
  name = "hypern"
8
- version = "0.3.12"
8
+ version = "0.3.13"
9
9
  description = "A Fast Async Python backend with a Rust runtime."
10
10
  authors = [{ name = "Martin Dang", email = "vannghiem848@gmail.com" }]
11
11
  requires-python = ">=3.10"
@@ -37,7 +37,7 @@ module-name = "hypern"
37
37
 
38
38
  [tool.poetry]
39
39
  name = "hypern"
40
- version = "0.3.12"
40
+ version = "0.3.13"
41
41
  description = "A Fast Async Python backend with a Rust runtime."
42
42
  authors = ["Martin Dang <vannghiem848@gmail.com>"]
43
43
 
@@ -0,0 +1,226 @@
1
+ use crate::router::route::Route;
2
+ use pyo3::prelude::*;
3
+ use std::collections::HashMap;
4
+
5
+ #[derive(Debug)]
6
+ #[pyclass]
7
+ #[derive(Clone)]
8
+ pub struct RadixNode {
9
+ pub path: String,
10
+ pub children: HashMap<char, RadixNode>,
11
+ pub is_endpoint: bool,
12
+ pub routes: HashMap<String, Route>,
13
+ pub param_name: Option<String>,
14
+ }
15
+
16
+ impl Default for RadixNode {
17
+ fn default() -> Self {
18
+ Self::new()
19
+ }
20
+ }
21
+
22
+ impl RadixNode {
23
+ pub fn new() -> Self {
24
+ Self {
25
+ path: String::new(),
26
+ children: HashMap::new(),
27
+ is_endpoint: false,
28
+ routes: HashMap::new(),
29
+ param_name: None,
30
+ }
31
+ }
32
+
33
+ fn find_common_prefix(a: &str, b: &str) -> String {
34
+ a.chars()
35
+ .zip(b.chars())
36
+ .take_while(|(ac, bc)| ac == bc)
37
+ .map(|(c, _)| c)
38
+ .collect()
39
+ }
40
+
41
+ pub fn insert(&mut self, path: &str, route: Route) {
42
+ let normalized_path = if path == "/" {
43
+ String::new()
44
+ } else {
45
+ path.trim_end_matches('/').to_string()
46
+ };
47
+
48
+ if normalized_path.is_empty() {
49
+ self.is_endpoint = true;
50
+ self.routes.insert(route.method.to_uppercase(), route);
51
+ return;
52
+ }
53
+
54
+ let segments: Vec<&str> = normalized_path.split('/')
55
+ .filter(|s| !s.is_empty())
56
+ .collect();
57
+
58
+ self._insert_segments(&segments, 0, route);
59
+ }
60
+
61
+ fn _insert_segments(&mut self, segments: &[&str], index: usize, route: Route) {
62
+ if index >= segments.len() {
63
+ self.is_endpoint = true;
64
+ self.routes.insert(route.method.to_uppercase(), route);
65
+ return;
66
+ }
67
+
68
+ let segment = segments[index];
69
+
70
+ if segment.starts_with(':') {
71
+ let param_name = segment[1..].to_string();
72
+ let param_node = self.children
73
+ .entry(':')
74
+ .or_insert_with(|| {
75
+ let mut node = RadixNode::new();
76
+ node.param_name = Some(param_name.clone());
77
+ node
78
+ });
79
+ param_node._insert_segments(segments, index + 1, route);
80
+ return;
81
+ }
82
+
83
+ let first_char = match segment.chars().next() {
84
+ Some(c) => c,
85
+ None => return, // Empty segment, shouldn't happen due to normalization
86
+ };
87
+
88
+ if let Some(existing_node) = self.children.get_mut(&first_char) {
89
+ let common_prefix = Self::find_common_prefix(&existing_node.path, segment);
90
+
91
+ if common_prefix == existing_node.path {
92
+ // Full match of existing node's path, check remaining segment
93
+ let remaining = &segment[common_prefix.len()..];
94
+ if remaining.is_empty() {
95
+ // Exact match, proceed to next segment
96
+ existing_node._insert_segments(segments, index + 1, route);
97
+ } else {
98
+ // Split remaining part and insert
99
+ let mut new_node = RadixNode::new();
100
+ new_node.path = remaining.to_string();
101
+ new_node._insert_segments(segments, index + 1, route);
102
+ existing_node.children
103
+ .entry(remaining.chars().next().unwrap())
104
+ .or_insert(new_node);
105
+ }
106
+ } else if !common_prefix.is_empty() {
107
+ // Split existing node and insert new path
108
+ let existing_remaining = existing_node.path[common_prefix.len()..].to_string();
109
+ let new_remaining = segment[common_prefix.len()..].to_string();
110
+
111
+ // Create new parent node with common prefix
112
+ let mut new_parent = RadixNode::new();
113
+ new_parent.path = common_prefix;
114
+
115
+ // Modify existing node to hold remaining path
116
+ let mut existing_child = RadixNode::new();
117
+ existing_child.path = existing_remaining;
118
+ existing_child.children = existing_node.children.clone();
119
+ existing_child.is_endpoint = existing_node.is_endpoint;
120
+ existing_child.routes = existing_node.routes.clone();
121
+ existing_child.param_name = existing_node.param_name.clone();
122
+
123
+ // Create new node for the new remaining path
124
+ let mut new_child = RadixNode::new();
125
+ new_child.path = new_remaining;
126
+ new_child._insert_segments(segments, index + 1, route);
127
+
128
+ // Attach children to new parent
129
+ if let Some(c) = existing_child.path.chars().next() {
130
+ new_parent.children.insert(c, existing_child);
131
+ }
132
+ if let Some(c) = new_child.path.chars().next() {
133
+ new_parent.children.insert(c, new_child);
134
+ }
135
+
136
+ // Replace existing node with new parent
137
+ *existing_node = new_parent;
138
+ } else {
139
+ // No common prefix, create new sibling node
140
+ let mut new_node = RadixNode::new();
141
+ new_node.path = segment.to_string();
142
+ new_node._insert_segments(segments, index + 1, route);
143
+ self.children.insert(first_char, new_node);
144
+ }
145
+ } else {
146
+ // No existing node, create new
147
+ let mut new_node = RadixNode::new();
148
+ new_node.path = segment.to_string();
149
+ new_node._insert_segments(segments, index + 1, route);
150
+ self.children.insert(first_char, new_node);
151
+ }
152
+ }
153
+
154
+ pub fn find(&self, path: &str, method: &str) -> Option<(&Route, HashMap<String, String>)> {
155
+ let normalized_path = if path == "/" {
156
+ String::new()
157
+ } else {
158
+ path.trim_end_matches('/').to_string()
159
+ };
160
+
161
+ let mut params = HashMap::new();
162
+ if normalized_path.is_empty() {
163
+ return if self.is_endpoint {
164
+ self.routes.get(&method.to_uppercase()).map(|r| (r, params))
165
+ } else {
166
+ None
167
+ };
168
+ }
169
+
170
+ let segments: Vec<&str> = normalized_path.split('/')
171
+ .filter(|s| !s.is_empty())
172
+ .collect();
173
+
174
+ self._find_segments(&segments, 0, method, &mut params)
175
+ }
176
+
177
+ fn _find_segments<'a>(
178
+ &'a self,
179
+ segments: &[&str],
180
+ index: usize,
181
+ method: &str,
182
+ params: &mut HashMap<String, String>,
183
+ ) -> Option<(&'a Route, HashMap<String, String>)> {
184
+ if index >= segments.len() {
185
+ return if self.is_endpoint {
186
+ self.routes.get(&method.to_uppercase()).map(|r| (r, params.clone()))
187
+ } else {
188
+ None
189
+ };
190
+ }
191
+
192
+ let segment = segments[index];
193
+
194
+ // Check static nodes
195
+ if let Some(first_char) = segment.chars().next() {
196
+ if let Some(child) = self.children.get(&first_char) {
197
+ if let Some(remaining) = segment.strip_prefix(&child.path) {
198
+ if remaining.is_empty() {
199
+ // Full match, proceed to next segment
200
+ if let Some(result) = child._find_segments(segments, index + 1, method, params) {
201
+ return Some(result);
202
+ }
203
+ } else {
204
+ // Check if remaining part matches any child
205
+ if let Some(result) = child._find_segments(&[remaining], 0, method, params) {
206
+ return Some(result);
207
+ }
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ // Check parameter node
214
+ if let Some(param_node) = self.children.get(&':') {
215
+ if let Some(param_name) = &param_node.param_name {
216
+ params.insert(param_name.clone(), segment.to_string());
217
+ if let Some(result) = param_node._find_segments(segments, index + 1, method, params) {
218
+ return Some(result);
219
+ }
220
+ params.remove(param_name);
221
+ }
222
+ }
223
+
224
+ None
225
+ }
226
+ }
@@ -1,160 +0,0 @@
1
- use crate::router::route::Route;
2
- use pyo3::prelude::*;
3
- use std::collections::HashMap;
4
-
5
- #[derive(Debug)]
6
- #[pyclass]
7
- #[derive(Clone)]
8
- pub struct RadixNode {
9
- pub path: String,
10
- pub children: HashMap<char, RadixNode>,
11
- pub is_endpoint: bool,
12
- pub routes: HashMap<String, Route>,
13
- pub param_name: Option<String>,
14
- }
15
-
16
- impl Default for RadixNode {
17
- fn default() -> Self {
18
- Self::new()
19
- }
20
- }
21
- impl RadixNode {
22
- pub fn new() -> Self {
23
- Self {
24
- path: String::new(),
25
- children: HashMap::new(),
26
- is_endpoint: false,
27
- routes: HashMap::new(),
28
- param_name: None,
29
- }
30
- }
31
-
32
- pub fn insert(&mut self, path: &str, route: Route) {
33
- // Normalize the path first
34
- let normalized_path = if path == "/" {
35
- String::new()
36
- } else {
37
- path.trim_end_matches('/').to_string()
38
- };
39
-
40
- // For root path or empty path after normalization
41
- if normalized_path.is_empty() {
42
- self.is_endpoint = true;
43
- self.routes.insert(route.method.to_uppercase(), route);
44
- return;
45
- }
46
-
47
- let segments: Vec<&str> = normalized_path.split('/')
48
- .filter(|s| !s.is_empty())
49
- .collect();
50
-
51
- self._insert_segments(&segments, 0, route);
52
- }
53
-
54
- fn _insert_segments(&mut self, segments: &[&str], index: usize, route: Route) {
55
- if index >= segments.len() {
56
- self.is_endpoint = true;
57
- self.routes.insert(route.method.to_uppercase(), route);
58
- return;
59
- }
60
-
61
- let segment = segments[index];
62
-
63
- // For parameter segments
64
- if segment.starts_with(':') {
65
- let param_name = segment[1..].to_string();
66
- let param_node = self.children
67
- .entry(':')
68
- .or_insert_with(|| {
69
- let mut node = RadixNode::new();
70
- node.param_name = Some(param_name.clone());
71
- node
72
- });
73
- param_node._insert_segments(segments, index + 1, route);
74
- return;
75
- }
76
-
77
- // For static segments
78
- let first_char = segment.chars().next().unwrap();
79
- let node = self.children
80
- .entry(first_char)
81
- .or_insert_with(|| {
82
- let mut node = RadixNode::new();
83
- node.path = segment.to_string();
84
- node
85
- });
86
-
87
- if node.path == segment {
88
- node._insert_segments(segments, index + 1, route);
89
- } else {
90
- // Create new node for different path
91
- let mut new_node = RadixNode::new();
92
- new_node.path = segment.to_string();
93
- new_node._insert_segments(segments, index + 1, route);
94
- self.children.insert(first_char, new_node);
95
- }
96
- }
97
-
98
- pub fn find(&self, path: &str, method: &str) -> Option<(&Route, HashMap<String, String>)> {
99
- let normalized_path = if path == "/" {
100
- String::new()
101
- } else {
102
- path.trim_end_matches('/').to_string()
103
- };
104
-
105
- let mut params = HashMap::new();
106
- if normalized_path.is_empty() {
107
- return if self.is_endpoint {
108
- self.routes.get(&method.to_uppercase()).map(|r| (r, params))
109
- } else {
110
- None
111
- };
112
- }
113
-
114
- let segments: Vec<&str> = normalized_path.split('/')
115
- .filter(|s| !s.is_empty())
116
- .collect();
117
-
118
- self._find_segments(&segments, 0, method, &mut params)
119
- }
120
-
121
- fn _find_segments<'a>(
122
- &'a self,
123
- segments: &[&str],
124
- index: usize,
125
- method: &str,
126
- params: &mut HashMap<String, String>,
127
- ) -> Option<(&'a Route, HashMap<String, String>)> {
128
- if index >= segments.len() {
129
- return if self.is_endpoint {
130
- self.routes.get(&method.to_uppercase()).map(|r| (r, params.clone()))
131
- } else {
132
- None
133
- };
134
- }
135
-
136
- let segment = segments[index];
137
-
138
- // Try exact static match first
139
- for (_, child) in self.children.iter() {
140
- if child.path == segment {
141
- if let Some(result) = child._find_segments(segments, index + 1, method, params) {
142
- return Some(result);
143
- }
144
- }
145
- }
146
-
147
- // Try parameter match
148
- if let Some(param_node) = self.children.get(&':') {
149
- if let Some(param_name) = &param_node.param_name {
150
- params.insert(param_name.clone(), segment.to_string());
151
- if let Some(result) = param_node._find_segments(segments, index + 1, method, params) {
152
- return Some(result);
153
- }
154
- params.remove(param_name);
155
- }
156
- }
157
-
158
- None
159
- }
160
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes