Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 104 additions & 22 deletions path_match.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,17 @@ fn match_path_segments(
// 路径结束但模板还有
if path_idx >= path_parts.length() {
let template_part = template_parts[template_idx]
return if template_part == "**" { Some(params) } else { None }
if template_part == "**" {
params.set("_", "")
return match_path_segments(
template_parts,
path_parts,
template_idx + 1,
path_idx,
params,
)
}
return None
}

// 都有内容,继续匹配
Expand Down Expand Up @@ -63,15 +73,29 @@ fn match_path_segments(
params,
)
} else if template_part == "**" {
// 多级通配符 - 贪婪匹配剩余所有
let remaining_path = []
let mut i = path_idx
while i < path_parts.length() {
remaining_path.push(path_parts[i])
i = i + 1
for consumed = path_idx
consumed <= path_parts.length()
consumed = consumed + 1 {
let remaining : Array[StringView] = []
for j = path_idx; j < consumed; j = j + 1 {
remaining.push(path_parts[j])
}
let trial : Map[String, StringView] = Map([])
params.each(fn(k, v) { trial.set(k, v) })
trial.set("_", remaining.join("/"))
match
match_path_segments(
template_parts,
path_parts,
template_idx + 1,
consumed,
trial,
) {
Some(result) => return Some(result)
None => continue
}
}
params.set("_", remaining_path.join("/"))
Some(params)
None
} else if template_part == path_part {
// 静态段匹配
match_path_segments(
Expand Down Expand Up @@ -351,17 +375,26 @@ fn Mocket::insert_dynamic_route(
trie
}
}
trie.insert(path, handler, order)
let parts = path.split("/").to_array()
let mut deep_wild_mid = false
for i = 0; i < parts.length(); i = i + 1 {
if parts[i] == "**" && i + 1 < parts.length() {
deep_wild_mid = true
}
}
if !deep_wild_mid {
trie.insert(path, handler, order)
}
}

///|
fn DynamicRouteTrieNode::find_path(
self : DynamicRouteTrieNode,
path : String,
) -> (HttpHandler, Map[String, StringView])? {
) -> (HttpHandler, Map[String, StringView], Int)? {
let path_parts = path.split("/").collect()
match self.find(path_parts, 0, {}) {
Some(found) => Some((found.handler, found.params))
Some(found) => Some((found.handler, found.params, found.order))
None => None
}
}
Expand Down Expand Up @@ -393,17 +426,31 @@ fn Mocket::find_route(
None => ignore(())
}

// 然后尝试动态路由 trie
if self.dynamic_route_tries.get(http_method) is Some(trie) {
if trie.find_path(path) is Some(found) {
return Some(found)
// 动态路由:按方法优先级(method-specific 优先于 wildcard),
// 每组内取 trie 和线性中 order 最小的匹配
for meth in [http_method, "*"] {
let mut best_order = -1
let mut best_result : (HttpHandler, Map[String, StringView])? = None
if self.dynamic_route_tries.get(meth) is Some(trie) {
if trie.find_path(path) is Some((handler, params, order)) {
best_order = order
best_result = Some((handler, params))
}
}
}

// 最后检查通配符方法的动态路由 trie
if self.dynamic_route_tries.get("*") is Some(trie) {
if trie.find_path(path) is Some(found) {
return Some(found)
if self.dynamic_routes.get(meth) is Some(routes) {
for i = 0; i < routes.length(); i = i + 1 {
if best_order >= 0 && i >= best_order {
break
}
let (template, handler) = routes[i]
if match_path(template, path) is Some(params) {
best_order = i
best_result = Some((handler, params))
}
}
}
if best_result is Some(_) {
return best_result
}
}
None
Expand Down Expand Up @@ -500,6 +547,41 @@ test "边界情况" {
@test.assert_eq(result5, None)
}

///|
test "** with trailing segments" {
// ** mid-pattern must check suffix
@test.assert_eq(
match_path("/admin/**/settings", "/admin/x/settings"),
Some({ "_": "x" }),
)
@test.assert_eq(
match_path("/admin/**/settings", "/admin/x/y/settings"),
Some({ "_": "x/y" }),
)
// path exhausted before suffix — must NOT match
@test.assert_eq(match_path("/admin/**/settings", "/admin"), None)
@test.assert_eq(match_path("/admin/**/settings", "/admin/x"), None)
// ** at end still works when path is exhausted
@test.assert_eq(match_path("/files/**", "/files"), Some({ "_": "" }))
}

///|
test "** mid-pattern preserves registration order" {
let app = new()
let noop : HttpHandler = fn(_) noraise { text("") }
app.get("/admin/**/settings", noop)
app.get("/admin/:id/settings", noop)
let result = app.find_route("GET", "/admin/x/settings")
// first-registered ** route should win; its params use "_" not "id"
match result {
Some((_, params)) => {
@test.assert_eq(params.get("_"), Some("x"))
@test.assert_eq(params.get("id"), None)
}
None => @test.fail("expected a match")
}
}

///|
test "性能对比场景" {
// 静态路径应该快速返回
Expand Down
Loading