feat(13-01): register overlapping constrained routes in surf
- compile groups routes ServeMux refuses side by side into overlap families
and registers each under one generated method-less pattern
- the family handler tries members in registration order on literals and
Where constraints, sets their path values and runs their own wrapped chain
- no match answers the bare 404; a method mismatch answers ServeMux's 405
and Allow for the same table without the overlap
- unsupported shapes (same shape, {name...}, shadowing route) fail at boot
- README and docs/services/routing.md describe the behaviour
This commit is contained in:
@@ -12,6 +12,7 @@ surf turns the routes that plugins declare through `pact.HasRoutes` into one `ht
|
||||
|
||||
- Laravel-style route groups: `surf.Router.Group` with a path prefix and middleware list (`surf.Use` builds the list), `surf.Router.Get`, `surf.Router.Post`, `surf.Router.Put`, `surf.Router.Patch` and `surf.Router.Delete` (the same methods exist on each `surf.Group`), with Go 1.22+ path patterns such as `/posts/{id}`.
|
||||
- Path constraints: `surf.Router.Where` (regex, anchored to the whole segment) and `surf.Router.WhereIn` (allow-list) apply to the last declared route; a request that fails a constraint gets a 404. `surf.IntParam` reads a positive integer path value.
|
||||
- Overlapping constrained routes: two routes that ServeMux would refuse side by side, such as `/shelves/items/{id}` and `/shelves/{shelfId}/follow`, boot as one overlap family when their constraints keep them apart. Requests are tried against the family's routes in registration order, as Laravel does, and each route keeps its own path values and middleware.
|
||||
- Named middleware from plugins (`pact.HasMiddleware`), parameterized middleware used as `name:param` (`pact.HasMiddlewareFactories`) and house middleware for the JSON envelope and error handling (`pact.HasHouseMiddleware`). Duplicate or unknown names fail boot.
|
||||
- Raw groups (`surf.Router.GroupRaw`) for routes that must not be wrapped in house middleware, such as webhooks or file streams: house middleware is refused there, the default body limit is skipped and a panic returns a bare 500.
|
||||
- Built-in middleware names: `throttle:<bucket>` or `throttle:<max>,<minutes>`, `body.limit:<bytes>`, `locale.from-principal`, plus `backend` (the admin guard) when the admin is enabled.
|
||||
@@ -91,6 +92,8 @@ func listPosts(w http.ResponseWriter, r *http.Request) { wire.WriteJSON(w, http
|
||||
func addComment(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusCreated) }
|
||||
```
|
||||
|
||||
Routes ported from a `routes.php` sometimes overlap: `GET /shelves/items/{id}` with `id` numeric and `GET /shelves/{shelfId}/follow` with `shelfId` numeric both match `/shelves/items/follow` as patterns, so ServeMux alone would refuse to register them. surf registers such routes as an overlap family and tries them in registration order, checking literal segments and `Where` constraints, so declare them in the same order as the PHP file. A request no member matches gets the bare 404. Overlapping routes must use literal segments and single-segment `{name}` wildcards; two routes of one method with the same shape, a `{name...}` wildcard in a family, or a more general route that would take a member's requests still fail at boot. `surf.Router.Routes` and `route:list` keep listing every route on its own.
|
||||
|
||||
The generated application `main` wires surf in with `surf.ServeCommand` and `surf.RouteListCommand`; tests can call `surf.Assemble(app, plugins)` and drive the returned handler with `net/http/httptest`.
|
||||
|
||||
## API reference
|
||||
|
||||
Reference in New Issue
Block a user