web.router
web 路由:值语义 Router + 静态段 > :param > *splat 匹配 + 405 Allow + 中间件平铺链 + dispatch + not_found/method_not_allowed 覆写挂点(2b Task 2) 设计: docs/superpowers/specs/2026-09-27-web-framework-design.md §4.1-§4.2;纪律 §14-1 serve 循环(test_call 本体)归 2b,直接复用 dispatch。
引导 interp 口径实测约定(实证见 task-3-report):
1. 泛型 struct 字面量一律裸形(Route { ... });Route[I64] { ... } 不入解析。
2. 泛型 fn 不得作值引用(no_h[S] 形 → E2020 S 未解析)——占位 handler 用闭包字面量。
3. 实参含 fn 型(内提 S)或调用方自身泛型时,调用一律显式 TypeArgs(run_chain[S](...)),
泛型 struct 型实参不参与推断(E2060);具体类型语境(测试体)UFCS .get/.middleware
可由接收者推断,冻结名面照用。
pub struct
struct RouteSeg
| Field | Type |
|---|---|
kind |
Str |
text |
Str |
struct Route[S]
| Field | Type |
|---|---|
m |
Str |
segs |
List[RouteSeg] |
chain |
List[fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp] |
h |
fn(&Req[S]) -> Resp |
struct Router[S]
| Field | Type |
|---|---|
state |
S |
routes |
List[Route[S]] |
mws |
List[fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp] |
not_found_h |
List[fn(&Req[S]) -> Resp] |
not_allowed_h |
List[fn(&Req[S]) -> Resp] |
struct Match[S]
| Field | Type |
|---|---|
kind |
I32 |
route |
Route[S] |
params |
List[Pair] |
splat |
Str |
allow |
Str |
struct Pair
| Field | Type |
|---|---|
k |
Str |
v |
Str |
struct Resp
| Field | Type |
|---|---|
status |
I32 |
headers |
List[Pair] |
body |
Str |
struct JObj
| Field | Type |
|---|---|
parts |
List[Str] |
struct Req[S]
| Field | Type |
|---|---|
method |
Str |
path |
Str |
query |
Str |
body |
Str |
headers |
List[Pair] |
cookies |
List[Pair] |
params |
List[Pair] |
session |
List[Pair] |
state |
S |
pub fn
| Signature | Returns | Description |
|---|---|---|
router[S](state: S) |
Router[S] |
— |
get[S](r: Router[S], p: Str, h: fn(&Req[S]) -> Resp) |
Router[S] |
— |
post[S](r: Router[S], p: Str, h: fn(&Req[S]) -> Resp) |
Router[S] |
— |
put[S](r: Router[S], p: Str, h: fn(&Req[S]) -> Resp) |
Router[S] |
— |
delete[S](r: Router[S], p: Str, h: fn(&Req[S]) -> Resp) |
Router[S] |
— |
patch[S](r: Router[S], p: Str, h: fn(&Req[S]) -> Resp) |
Router[S] |
— |
middleware[S](r: Router[S], mw: fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp) |
Router[S] |
组级中间件:声明序 = 执行序(链头先跑);组内全部路由生效——已注册路由的预拼链 亦回填追加(值语义:链不可变,重铸每条),后续注册路由经 r.mws 继承 |
mount_at[S](r: Router[S], prefix: Str, sub: Router[S]) |
Router[S] |
子路由器前缀挂载:每条子路由预拼前缀段(最终一张平铺表,热路径无嵌套); 中间件继承:子组中间件已随子路由注册预拼入逐路由链(合并时 chain = 宿主 mws + 子链, 执行序外层→内层→handler §4.1);合并态 mws 仅取宿主——子组 mw 不入合并态列表, 否则挂载后新注册的宿主路由经 r_add 继承之、嵌套兄弟组经外层合并互渗(双外泄向量)。 state 取宿主(单一应用状态)。同样启动期查重(前缀拼接后即最终形态)。 【钉注 2b Task 3】404/405 覆写钩子同 mws 口径:合并态仅取宿主槽——子路由器的 not_found_h/not_allowed_h 挂载时丢弃(host-level 404/405 语义:整个应用一张 错误页策略;钉死测试见「mount_at 丢弃子路由覆写钩子」)。子组 404 定制属志向。 |
has_conflict[S](r: Router[S]) |
Bool |
启动自检面(2b serve 前置调用):表内存在重复 (method, 模式) 即 true |
install[S](r: Router[S], p: fn(Router[S]) -> Router[S]) |
Router[S] |
插件挂载(§4.6:插件 = 普通 fn(Router[S]) -> Router[S],零新机制;顺序即装配序) |
not_found[S](r: Router[S], h: fn(&Req[S]) -> Resp) |
Router[S] |
— |
method_not_allowed[S](r: Router[S], h: fn(&Req[S]) -> Resp) |
Router[S] |
— |
test_call[S](r: Router[S], method: Str, path: Str, body: Str) |
Resp |
test_call —— spec §4.7 测试口(dispatch 别名;整台服务器零 socket 的确定性测试形; 2b serve 循环复用 dispatch 本体) |
route_match[S](r: Router[S], m: Str, path: Str) |
Match[S] |
— |
dispatch_h[S](r: Router[S], method: Str, path: Str, body: Str, hdrs: List[Pair]) |
Resp |
dispatch_h —— 带头变体(2b Task 3 收口):hdrs 进 Req(headers 入 Req.headers, Cookie 在 req_full 内解析 → 会话注入随 mw 链可达 handler);404/405 覆写臂同收 headered Req(task-2 交接①)。查询串:匹配面剥 "?query"(target 全形不参与段匹配), req_full 仍收全 target(其内自分裂 path/query)——dispatch 旧形不剥,查询串曾恒 404, 此为纯增量修正(既有测试路径无查询串,零扰动)。 |
dispatch[S](r: Router[S], method: Str, path: Str, body: Str) |
Resp |
— |
html(body: Str) |
Resp |
— |
json(body: Str) |
Resp |
— |
text(body: Str) |
Resp |
— |
redirect(loc: Str) |
Resp |
— |
status(n: I32, body: Str) |
Resp |
— |
xml(body: Str) |
Resp |
— |
yaml(body: Str) |
Resp |
— |
err_json(n: I32, msg: Str) |
Resp |
— |
attachment(r: Resp, filename: Str) |
Resp |
— |
with(r: Resp, k: Str, v: Str) |
Resp |
— |
with_status(r: Resp, n: I32) |
Resp |
— |
resp_header(r: Resp, k: Str) |
Str |
— |
resp_body(r: Resp) |
Str |
— |
resp_status(r: Resp) |
I32 |
— |
json_obj() |
JObj |
— |
str(b: JObj, k: Str, v: Str) |
JObj |
— |
i64(b: JObj, k: Str, v: I64) |
JObj |
— |
bool(b: JObj, k: Str, v: Bool) |
JObj |
— |
strs(b: JObj, k: Str, xs: List[Str]) |
JObj |
— |
end(b: JObj) |
Str |
— |
end_status(b: JObj, n: I32) |
Resp |
— |
csv_rows(headers: List[Str], rows: List[List[Str]]) |
Str |
— |
req_of[S](method: Str, path: Str, state: S) |
Req[S] |
— |
req_full[S](method: Str, path: Str, body: Str, hdrs: List[Pair], state: S) |
Req[S] |
— |
req_set_params[S](r: &Req[S], params: List[Pair]) |
Req[S] |
— |
req_set_session[S](r: &Req[S], sess: List[Pair]) |
Req[S] |
— |
pdec(s: Str) |
Str |
— |
param[S](r: &Req[S], name: Str) |
Str |
— |
pair_get(ps: List[Pair], k: Str) |
Str |
— |
param_i64[S](r: &Req[S], name: Str) |
Option[I64] |
— |
param_f64[S](r: &Req[S], name: Str) |
Option[F64] |
— |
query[S](r: &Req[S], name: Str) |
Str |
— |
query_all[S](r: &Req[S], name: Str) |
List[Str] |
— |
form[S](r: &Req[S], name: Str) |
Str |
— |
form_all[S](r: &Req[S], name: Str) |
List[Str] |
— |
header[S](r: &Req[S], name: Str) |
Str |
— |
cookie[S](r: &Req[S], name: Str) |
Str |
— |
session[S](r: &Req[S], name: Str) |
Str |
— |