///|
// 匹配路径并提取参数 - 重写版本,消除复杂性
fn match_path(template : String, path : String) -> Map[String, StringView]? {
// 静态路径直接比较 - 好品味:消除特殊情况
if !template.contains(":") && !template.contains("*") {
return if template == path { Some({}) } else { None }
}
// 使用递归进行路径匹配和参数提取
let template_parts = template.split("/")
let path_parts = path.split("/").collect()
match_path_segments(template_parts.to_array(), path_parts, 0, 0, {})
}
///|
// 递归匹配路径段 - 简洁的核心逻辑
fn match_path_segments(
template_parts : Array[StringView],
path_parts : Array[StringView],
template_idx : Int,
path_idx : Int,
params : Map[String, StringView],
) -> Map[String, StringView]? {
// 都结束了,匹配成功
if template_idx >= template_parts.length() && path_idx >= path_parts.length() {
return Some(params)
}
// 模板结束但路径还有,失败
if template_idx >= template_parts.length() {
return None
}
// 路径结束但模板还有
if path_idx >= path_parts.length() {
let template_part = template_parts[template_idx]
if template_part == "**" {
params.set("_", "")
return match_path_segments(
template_parts,
path_parts,
template_idx + 1,
path_idx,
params,
)
}
return None
}
// 都有内容,继续匹配
let template_part = template_parts[template_idx]
let path_part = path_parts[path_idx]
// 使用正则匹配参数模式
if template_part.view() =~ (re"^:", after=param_name) {
// 命名参数::param_name
params.set(param_name.to_owned(), path_part)
match_path_segments(
template_parts,
path_parts,
template_idx + 1,
path_idx + 1,
params,
)
} else if template_part == "*" {
// 单级通配符
params.set("_", path_part)
match_path_segments(
template_parts,
path_parts,
template_idx + 1,
path_idx + 1,
params,
)
} else if template_part == "**" {
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
}
}
None
} else if template_part == path_part {
// 静态段匹配
match_path_segments(
template_parts,
path_parts,
template_idx + 1,
path_idx + 1,
params,
)
} else {
// 不匹配
None
}
}
///|
priv struct DynamicRouteHandlerEntry {
order : Int
handler : HttpHandler
}
///|
priv enum DynamicRouteTrieEdge {
Static(String, DynamicRouteTrieNode)
Param(String, DynamicRouteTrieNode)
Wildcard(DynamicRouteTrieNode)
DeepWildcard(DynamicRouteHandlerEntry)
}
///|
priv struct DynamicRouteTrieNode {
mut handler_entry : DynamicRouteHandlerEntry?
children : Map[String, DynamicRouteTrieNode]
edges : Array[DynamicRouteTrieEdge]
}
///|
priv struct DynamicRouteMatch {
order : Int
handler : HttpHandler
params : Map[String, StringView]
}
///|
fn new_dynamic_route_trie_node() -> DynamicRouteTrieNode {
{ handler_entry: None, children: {}, edges: [] }
}
///|
fn copy_route_params(
params : Map[String, StringView],
) -> Map[String, StringView] {
let copied : Map[String, StringView] = Map([])
params.iter().each(item => copied.set(item.0, item.1))
copied
}
///|
fn better_dynamic_route_match(
current : DynamicRouteMatch?,
candidate : DynamicRouteMatch?,
) -> DynamicRouteMatch? {
match (current, candidate) {
(None, None) => None
(Some(found), None) => Some(found)
(None, Some(found)) => Some(found)
(Some(found), Some(next)) =>
if next.order < found.order {
Some(next)
} else {
Some(found)
}
}
}
///|
fn dynamic_route_remaining_path(
path_parts : Array[StringView],
path_idx : Int,
) -> String {
let remaining_path = []
let mut i = path_idx
while i < path_parts.length() {
remaining_path.push(path_parts[i])
i = i + 1
}
remaining_path.join("/")
}
///|
fn DynamicRouteTrieNode::static_child(
self : DynamicRouteTrieNode,
segment : String,
) -> DynamicRouteTrieNode? {
self.children.get(segment)
}
///|
fn DynamicRouteTrieNode::param_child(
self : DynamicRouteTrieNode,
name : String,
) -> DynamicRouteTrieNode? {
let mut found : DynamicRouteTrieNode? = None
let mut searching = true
let mut i = 0
while i < self.edges.length() && searching {
match self.edges[i] {
Param(param_name, child) =>
if param_name == name {
found = Some(child)
searching = false
}
_ => ignore(())
}
i = i + 1
}
found
}
///|
fn DynamicRouteTrieNode::wildcard_child(
self : DynamicRouteTrieNode,
) -> DynamicRouteTrieNode? {
let mut found : DynamicRouteTrieNode? = None
let mut searching = true
let mut i = 0
while i < self.edges.length() && searching {
match self.edges[i] {
Wildcard(child) => {
found = Some(child)
searching = false
}
_ => ignore(())
}
i = i + 1
}
found
}
///|
fn DynamicRouteTrieNode::insert(
self : DynamicRouteTrieNode,
template : String,
handler : HttpHandler,
order : Int,
) -> Unit {
let template_parts = template.split("/")
let parts = template_parts.to_array()
let mut node = self
let mut i = 0
while i < parts.length() {
let part = parts[i]
if part == "**" {
node.edges.push(DeepWildcard({ order, handler }))
return
} else if part == "*" {
node = match node.wildcard_child() {
Some(child) => child
None => {
let child = new_dynamic_route_trie_node()
node.edges.push(Wildcard(child))
child
}
}
} else if part.view() =~ (re"^:", after=param_name) {
let param_name = param_name.to_owned()
node = match node.param_child(param_name) {
Some(child) => child
None => {
let child = new_dynamic_route_trie_node()
node.edges.push(Param(param_name, child))
child
}
}
} else {
let segment = part.to_owned()
node = match node.static_child(segment) {
Some(child) => child
None => {
let child = new_dynamic_route_trie_node()
node.children.set(segment, child)
node.edges.push(Static(segment, child))
child
}
}
}
i = i + 1
}
match node.handler_entry {
None => node.handler_entry = Some({ order, handler })
Some(_) => ignore(())
}
}
///|
fn DynamicRouteTrieNode::find(
self : DynamicRouteTrieNode,
path_parts : Array[StringView],
path_idx : Int,
params : Map[String, StringView],
) -> DynamicRouteMatch? {
let mut found = if path_idx >= path_parts.length() {
match self.handler_entry {
Some(entry) =>
Some({ order: entry.order, handler: entry.handler, params })
None => None
}
} else {
None
}
let mut i = 0
while i < self.edges.length() {
let edge = self.edges[i]
let candidate = match edge {
Static(segment, child) =>
if path_idx < path_parts.length() && path_parts[path_idx] == segment {
child.find(path_parts, path_idx + 1, params)
} else {
None
}
Param(param_name, child) =>
if path_idx < path_parts.length() {
let next_params = copy_route_params(params)
next_params.set(param_name, path_parts[path_idx])
child.find(path_parts, path_idx + 1, next_params)
} else {
None
}
Wildcard(child) =>
if path_idx < path_parts.length() {
let next_params = copy_route_params(params)
next_params.set("_", path_parts[path_idx])
child.find(path_parts, path_idx + 1, next_params)
} else {
None
}
DeepWildcard(entry) => {
let next_params = copy_route_params(params)
if path_idx < path_parts.length() {
next_params.set(
"_",
dynamic_route_remaining_path(path_parts, path_idx),
)
}
Some({ order: entry.order, handler: entry.handler, params: next_params })
}
}
found = better_dynamic_route_match(found, candidate)
i = i + 1
}
found
}
///|
fn Mocket::insert_dynamic_route(
self : Mocket,
event : String,
path : String,
handler : HttpHandler,
) -> Unit {
let order = match self.dynamic_routes.get(event) {
Some(routes) => {
let order = routes.length()
routes.push((path, handler))
order
}
None => {
self.dynamic_routes.set(event, [(path, handler)])
0
}
}
let trie = match self.dynamic_route_tries.get(event) {
Some(existing) => existing
None => {
let trie = new_dynamic_route_trie_node()
self.dynamic_route_tries.set(event, trie)
trie
}
}
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], Int)? {
let path_parts = path.split("/").collect()
match self.find(path_parts, 0, {}) {
Some(found) => Some((found.handler, found.params, found.order))
None => None
}
}
///|
// 查找匹配的路由和参数
fn Mocket::find_route(
self : Mocket,
http_method : String,
path : String,
) -> (HttpHandler, Map[String, StringView])? {
// 优化:首先尝试静态路由缓存
match self.static_routes.get(http_method) {
Some(http_methodroutes) =>
match http_methodroutes.get(path) {
Some(handler) => return Some((handler, {}))
None => ignore(())
}
None => ignore(())
}
// 检查通配符方法的静态路由
match self.static_routes.get("*") {
Some(http_methodroutes) =>
match http_methodroutes.get(path) {
Some(handler) => return Some((handler, {}))
None => ignore(())
}
None => ignore(())
}
// 动态路由:按方法优先级(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))
}
}
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
}
///|
// 路径匹配测试用例 - 全面覆盖各种场景
test "静态路径匹配" {
@test.assert_eq(match_path("/api/users", "/api/users"), Some({}))
@test.assert_eq(match_path("/api/users", "/api/posts"), None)
@test.assert_eq(match_path("/", "/"), Some({}))
}
///|
test "命名参数匹配" {
@test.assert_eq(match_path("/users/:id", "/users/123"), Some({ "id": "123" }))
@test.assert_eq(
match_path("/users/:userId/posts/:postId", "/users/456/posts/789"),
Some({ "userId": "456", "postId": "789" }),
)
@test.assert_eq(match_path("/users/:id", "/users/123/extra"), None)
}
///|
test "单级通配符匹配" {
@test.assert_eq(
match_path("/files/*", "/files/document.pdf"),
Some({ "_": "document.pdf" }),
)
@test.assert_eq(
match_path("/api/*/status", "/api/v1/status"),
Some({ "_": "v1" }),
)
@test.assert_eq(match_path("/files/*", "/files/docs/readme.txt"), None)
}
///|
test "多级通配符匹配" {
@test.assert_eq(
match_path("/static/**", "/static/css/main.css"),
Some({ "_": "css/main.css" }),
)
@test.assert_eq(
match_path("/assets/**", "/assets/images/icons/user.png"),
Some({ "_": "images/icons/user.png" }),
)
@test.assert_eq(
match_path("/docs/**", "/docs/readme.md"),
Some({ "_": "readme.md" }),
)
@test.assert_eq(match_path("/api/v1/**", "/api/v1/"), Some({ "_": "" }))
}
///|
test "复杂混合模式" {
// 参数 + 通配符
let result = match_path("/users/:id/files/*", "/users/123/files/avatar.jpg")
@test.assert_eq(result, Some({ "id": "123", "_": "avatar.jpg" }))
// 参数 + 多级通配符
let result2 = match_path(
"/projects/:projectId/**", "/projects/abc/src/main.mbt",
)
@test.assert_eq(result2, Some({ "projectId": "abc", "_": "src/main.mbt" }))
// 多个参数 + 静态段
let result3 = match_path(
"/api/:version/users/:id/profile", "/api/v2/users/456/profile",
)
@test.assert_eq(result3, Some({ "version": "v2", "id": "456" }))
}
///|
test "边界情况" {
// 空路径段
let result = match_path("/api//users", "/api//users")
@test.assert_eq(result, Some({}))
// 路径末尾斜杠
let result2 = match_path("/api/users/", "/api/users/")
@test.assert_eq(result2, Some({}))
// 参数名为空
let result3 = match_path("/users/:", "/users/123")
@test.assert_eq(result3, Some({ "": "123" }))
// 模板比路径短
let result4 = match_path("/api", "/api/users")
@test.assert_eq(result4, None)
// 路径比模板短(非通配符)
let result5 = match_path("/api/users", "/api")
@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 "性能对比场景" {
// 静态路径应该快速返回
let result = match_path("/health", "/health")
@test.assert_eq(result, Some({}))
// 复杂模式也应该高效
let result2 = match_path(
"/api/:v/users/:id/posts/:postId/comments/*", "/api/v1/users/123/posts/456/comments/789",
)
@test.assert_eq(
result2,
Some({ "v": "v1", "id": "123", "postId": "456", "_": "789" }),
)
}
///|
test "lexmatch 特殊字符处理" {
// 包含特殊字符的参数
let result = match_path("/search/:query", "/search/hello%20world")
@test.assert_eq(result, Some({ "query": "hello%20world" }))
// 包含点号的文件名
let result2 = match_path("/files/*", "/files/config.json")
@test.assert_eq(result2, Some({ "_": "config.json" }))
}
///|
test "动态路由 trie 匹配命名参数" {
let app = new()
app.get("/name/:id/x", _ => "ok")
match app.find_route("GET", "/name/42/x") {
Some((_, params)) => @test.assert_eq(params, { "id": "42" })
None => fail("Expected dynamic route match")
}
}
///|
test "动态路由 trie 保留注册顺序" {
let app = new()
app.get("/users/:id/profile", _ => "first")
app.get("/users/me/:tab", _ => "second")
match app.find_route("GET", "/users/me/profile") {
Some((_, params)) => @test.assert_eq(params, { "id": "me" })
None => fail("Expected first registered dynamic route")
}
}
///|
test "动态路由 trie 合并分组路由" {
let app = new()
app.group("/api", group => group.get("/users/:id", _ => "ok"))
match app.find_route("GET", "/api/users/7") {
Some((_, params)) => @test.assert_eq(params, { "id": "7" })
None => fail("Expected grouped dynamic route match")
}
}