Ver Fonte

docs(playbook): record actix builder-API routing validation

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Colby McHenry há 1 mês atrás
pai
commit
8e42d45961
1 ficheiros alterados com 15 adições e 5 exclusões
  1. 15 5
      docs/design/dynamic-dispatch-coverage-playbook.md

+ 15 - 5
docs/design/dynamic-dispatch-coverage-playbook.md

@@ -183,7 +183,7 @@ Status legend: ✅ done+validated · 🔬 hole identified · ⬜ not started.
 | Python | Django / DRF (views) | url → view → model | R + X | ✅ url→view (`path`/`url`/`as_view`) + **DRF `router.register`→ViewSet** (realworld S / wagtail M / saleor L); ORM QuerySet→SQL (prior work). 🔬 signals (`post_save`→receiver), DRF viewset CRUD actions (inherited), saleor GraphQL resolvers |
 | Python | Flask / FastAPI | request → route → handler → dependency | R + X | ✅ **Flask: handler resolved across intervening decorators (`@login_required`) + stacked `@x.route` lines** (microblog S 6→27, redash L decorator routes 6/6); **FastAPI: empty-path router-root routes `@router.get("")` incl. multi-line** (realworld S 12→20 / Netflix dispatch L **290/290 100%**) + **bare-name builtin guard** — a handler named after a Python builtin method (`index`/`get`/`update`/`count`…) was filtered as a builtin and lost its route→handler edge. 🔬 Flask-RESTful class-based `add_resource(Resource, '/x')` (redash — separate mechanism, not the README decorator/blueprint shape); FastAPI `Depends()` dependency edges (resolver exists, light validation) |
 | Go | Gin / chi / net-http | request → route → handler → service | X | ✅ **routes on ANY group var** (`v1.GET`, `PublicGroup.GET`) not just `r/router` (gin-vue-admin S→M 4→259 / realworld S / gitness L) — was missing all group-routed apps; named handlers resolve precisely. 🔬 inline `func(c){}` handlers (anonymous, body lost), gitness chi custom (26/321) |
-| Rust | Axum / actix / Rocket | request → route → handler | R + X | ✅ **Axum chained methods + namespaced handlers** — `.route("/x", get(h1).post(h2))` emitted only the first method+handler, and `get(mod::handler)` captured the module not the fn (realworld-axum S **12→19, 19/19**); balanced-paren scan + per-method nodes + last-`::`-segment handler. **Rocket attribute macros 550/556 (99%)** (Rocket repo L) — already strong. crates.io named axum routes resolve (6/8; rest are closures/var handlers; its API is mostly the utoipa `routes!` macro = frontier). Cargo-workspace module resolution (prior work). 🔬 **actix runtime routing** `web::get().to(handler)` / `web::resource().route()` (67 calls in actix-examples — the dominant actix style, unextracted; attribute macros 35/51) |
+| Rust | Axum / actix / Rocket | request → route → handler | R + X | ✅ **Axum chained methods + namespaced handlers** — `.route("/x", get(h1).post(h2))` emitted only the first method+handler, and `get(mod::handler)` captured the module not the fn (realworld-axum S **12→19, 19/19**); balanced-paren scan + per-method nodes + last-`::`-segment handler. **Rocket attribute macros 550/556 (99%)** (Rocket repo L) — already strong. crates.io named axum routes resolve (6/8; rest are closures/var handlers; its API is mostly the utoipa `routes!` macro = frontier). Cargo-workspace module resolution (prior work). **actix builder API** `web::resource("/x").route(web::get().to(h))` / `.to(h)` / App `.route("/x", web::get().to(h))` (actix-examples **51→128 routes, 35→112 resolved**) — was the dominant actix style and fully missed (the handler is in `.to(h)`, not `get(h)`). 🔬 actix `web::scope("/api")` prefix (not prepended to nested resource paths) + anonymous `.to` closure handlers |
 | Java | Spring | request → @RestController → @Autowired service → repo | R + X | ✅ **bare `@GetMapping`/`@PostMapping` + class `@RequestMapping` prefix join → route→method** (realworld S / mall M / halo L) — was missing all path-less method mappings; DI controller→service resolves (name + dir). 🔬 Spring Data JPA derived queries (`findByEmail`) — metaprogramming frontier |
 | Kotlin | (coroutines / DI) | flow/callback dispatch | ? | ⬜ |
 | Swift | Vapor | request → route → controller | R + X | ✅ **was 0 routes on every real app** — the extractor required an `app/router/routes` receiver + a `"path"` literal, but real Vapor routes on grouped builders (`let todos = routes.grouped("todos"); todos.get(use: index)`) with NO path arg. Rewrote: any receiver, optional/non-string path segments, `.grouped`/`.group{}` prefix tracking, `use:` discriminator. vapor-template S **0→3 (3/3**, nested `/todos/:todoID`), SteamPress M **0→27 (27/27)**, SwiftPackageIndex-Server L **0→14 (14/14** handler resolution). 🔬 typed-route enums (SPI `SiteURL.x.pathComponents` — path label only, handler still resolves) + closure handlers `app.get("x"){ }` (anonymous) |
@@ -382,12 +382,22 @@ Status legend: ✅ done+validated · 🔬 hole identified · ⬜ not started.
   `feed_articles` resolves). **Rocket needed nothing** (550/556, 99% — attribute macros). crates.io confirms
   namespaced axum handlers resolve (router.rs 6/6) but defines most of its API via the `utoipa_axum` `routes!`
   macro (frontier) and has a SvelteKit frontend (42 of its 50 "routes" are `+page.svelte`, correctly
-  attributed to SvelteKit). **Residual (frontier): actix runtime routing** — `web::get().to(handler)` /
-  `web::resource("/x").route(...)` is the dominant actix style (actix-examples: 67 runtime calls vs 51
-  attribute) and is unextracted; only the attribute macros are covered (35/51). Agent A/B (update-user flow,
+  attributed to SvelteKit). Agent A/B (update-user flow,
   n=2/arm): codegraph **0–2 read / 0 grep / 32–40s** vs without **3 read / 0–1 grep + glob / 33–41s** — modest
   (realworld-axum is in the small-repo tie zone) but consistent, with one fully-clean 0-read/0-grep run. Node
-  count stable; the fix is Axum-scoped (the attribute/actix/Rocket path is untouched).
+  count stable; the Axum fix is Axum-scoped (the attribute/actix/Rocket path is untouched).
+- **Actix runtime routing (validated 2026-05-23, actix-examples) — the builder API was the dominant style and fully missed.**
+  Actix's attribute macros (`#[get("/x")] fn h`) were covered, but real actix apps route via the builder API:
+  `web::resource("/path").route(web::get().to(handler))`, `web::resource("/").to(handler)` (all methods), and
+  App-level `.route("/path", web::get().to(handler))`. The handler lives in `.to(handler)`, not `get(handler)`,
+  so the Axum `.route` scan extracted nothing for them — actix-examples had **80 `web::resource` calls** all
+  unlinked. Added an actix block: scan each `web::resource("/path")` (bounding its method chain at the next
+  resource to avoid bleed) for `web::METHOD().to(h)` pairs, fall back to a direct `.to(h)` (method `ANY`), plus
+  the App-level `.route("/x", web::METHOD().to(h))` form. actix-examples **51→128 routes, 35→112 resolved
+  (87.5%)** (`GET /user/{name}`→with_param, `POST /user`→add_user). No regression on Axum (realworld-axum still
+  19/19) — the actix patterns (`web::resource`/`web::method().to()`) don't appear in Axum code. **Residuals
+  (frontier):** `web::scope("/api")` prefixes aren't prepended to nested resource paths, and anonymous `.to(|req|
+  …)` closure handlers have no named target (the ~16 still-unresolved).
 - **Swift / Vapor (validated 2026-05-23, vapor-template S / SteamPress M / SwiftPackageIndex-Server L) — the resolver was effectively dead on real apps.**
   The Vapor extractor only matched `(app|router|routes).METHOD("path", use: handler)`, but modern Vapor routes
   on a grouped builder inside `RouteCollection.boot(routes:)`: `let todos = routes.grouped("todos");