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.
- {hypern-0.3.12 → hypern-0.3.13}/Cargo.lock +1 -1
- {hypern-0.3.12 → hypern-0.3.13}/Cargo.toml +1 -1
- {hypern-0.3.12 → hypern-0.3.13}/PKG-INFO +1 -1
- {hypern-0.3.12 → hypern-0.3.13}/pyproject.toml +2 -2
- hypern-0.3.13/src/router/radix.rs +226 -0
- hypern-0.3.12/src/router/radix.rs +0 -160
- {hypern-0.3.12 → hypern-0.3.13}/.github/dependabot.yml +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/.github/workflows/codeql.yml +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/.github/workflows/codspeed.yml +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/.github/workflows/preview-deployments.yml +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/.github/workflows/release-CI.yml +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/.github/workflows/security-scan.yml +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/.gitignore +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/.pre-commit-config.yaml +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/CODE_OF_CONDUCT.md +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/LICENSE +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/README.md +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/SECURITY.md +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/benchmark.sh +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/docs/background.md +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/docs/cache.md +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/docs/database.md +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/docs/index.md +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/docs/response.md +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/docs/schedule.md +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/docs/websocket.md +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/application.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/args_parser.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/auth/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/auth/authorization.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/caching/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/caching/backend.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/caching/redis_backend.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/caching/strategies.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/cli/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/cli/commands.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/config.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/database/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/database/sqlalchemy/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/database/sqlalchemy/config.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/database/sqlalchemy/repository.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/database/sqlx/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/database/sqlx/field.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/database/sqlx/migrate.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/database/sqlx/model.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/database/sqlx/query.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/datastructures.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/enum.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/exceptions/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/exceptions/base.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/exceptions/common.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/exceptions/errors.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/exceptions/formatters.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/exceptions/http.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/gateway/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/gateway/aggregator.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/gateway/gateway.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/gateway/proxy.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/gateway/service.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/hypern.pyi +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/i18n/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/logging/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/logging/logger.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/middleware/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/middleware/base.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/middleware/cache.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/middleware/compress.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/middleware/cors.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/middleware/i18n.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/middleware/limit.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/middleware/security.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/openapi/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/openapi/schemas.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/openapi/swagger.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/processpool.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/py.typed +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/reload.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/response/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/response/response.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/routing/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/routing/dispatcher.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/routing/endpoint.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/routing/parser.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/routing/queue.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/routing/route.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/worker.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/ws/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/ws/channel.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/ws/heartbeat.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/ws/room.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/hypern/ws/route.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/poetry.lock +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/scripts/format.sh +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/sonar-project.properties +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/background/background_task.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/background/background_tasks.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/background/mod.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/database/context.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/database/mod.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/database/sql/config.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/database/sql/connection.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/database/sql/db_trait.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/database/sql/mod.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/database/sql/mysql.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/database/sql/postgresql.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/database/sql/sqlite.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/database/sql/transaction.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/di.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/executor.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/instants.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/lib.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/middlewares/base.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/middlewares/mod.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/openapi/mod.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/openapi/schemas.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/openapi/swagger.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/router/mod.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/router/route.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/router/router.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/scheduler/job.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/scheduler/mod.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/scheduler/retry.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/scheduler/scheduler.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/server.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/types/body.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/types/function_info.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/types/header.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/types/middleware.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/types/mod.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/types/query.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/types/request.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/types/response.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/ws/mod.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/ws/route.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/ws/router.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/ws/socket.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/src/ws/websocket.rs +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/tests/__init__.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/tests/conftest.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/tests/server.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/tests/test_functional_handler.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/tests/test_request_file.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/tests/test_response_type.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/tests/test_sync_async.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/tests/test_validate_field.py +0 -0
- {hypern-0.3.12 → hypern-0.3.13}/tests/utils.py +0 -0
@@ -5,7 +5,7 @@ build-backend = "maturin"
|
|
5
5
|
|
6
6
|
[project]
|
7
7
|
name = "hypern"
|
8
|
-
version = "0.3.
|
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.
|
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) = ¶m_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) = ¶m_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
|
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
|