Repository navigation
Conversation
- A `google.api.http` template whose last segment is a variable followed
by a custom verb (`post: "/v2/ops/{operation}:cancel"`, AIP-136) is
served instead of panicking the router at startup; bindings that
differ only by verb reach their own RPCs, and a template the router
cannot match is skipped with an error in the log
- Bindings of one path shape share one route; a verb some binding of
the path binds owns the URL, an unbound verb stays part of the last
variable, a template ending in a literal keeps its exact URL. Verbs
and plain bindings are indexed in a segment trie that ranks paths as
the router does
- The transcoded paths are ranked among every other route of the
proxy: a request goes to its best match, one a binding refuses passes
on in that order, a transcoded HEAD binding answers HEAD before
another route's GET, and a 405 lists the methods of every route at
the path that answered
- Startup refuses routes the router cannot hold together, a clash of
one method or verb, and an extra route on a path with verbs; only
mounted routes reserve paths or lend guard methods
- Multi-segment field templates bind only values of their shape and
keep `%2F` encoded; a trailing `**` may match no segment before a
verb; captures are told from a nesting prefix's by position
- The proxy service serves the transcoded routes past the router behind
the same layers and guards; per-request work done once is done once
(forwarded header names, trace ids, rate-limit keys, path parameters
borrowed)
- CORS is the proxy's own and answers only real preflights; for every
origin it names back the request headers asked for. No Content-Length
on 204, 304 or a 2xx CONNECT answer
- CI pulls its Redis service through a Docker Hub mirror and runs the
released cargo-deny binary
- CONTRIBUTING.md, SECURITY.md and the CLA note
Closes #138
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 20 minutes. View limit detailsLimit details: You’ve used the included review currently available. Review configuration: ⚙️ Run configuration
📒 Files selected for processing (35)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
## 🤖 New release * `structured-proxy`: 8.1.0 -> 8.1.1 (✓ API compatible changes) <details><summary><i><b>Changelog</b></i></summary><p> <blockquote> ## [8.1.1](v8.1.0...v8.1.1) - 2026-10-09 ### Fixed - *(transcode)* route custom verbs after a path variable ([#142](#142)) ### Other - *(deps)* bump taiki-e/install-action from 2.87.21 to 2.87.24 ([#137](#137)) </blockquote> </p></details> --- This PR was generated with [release-plz](https://github.com/release-plz/release-plz/). Co-authored-by: sw-release-bot[bot] <255865126+sw-release-bot[bot]@users.noreply.github.com>
Summary
google.api.httptemplate whose last segment is a variable followed by a custom verb (post: "/v2/ops/{operation}:cancel", AIP-136) is served instead of panicking the router at startup.Replaces #141 (and #139 before it), squashed into one commit on top of
main.Changes
transcode/path.rs: template conversion moves here; a verb after a variable or wildcard is split off the mounted path, a verb after a literal stays in it. A template of one segment ({name=foo}) is a plain capture; a trailing**, alone or ending a field template, may match no segment before a verb. Templates are checked with matchit (the router axum uses) before registration.transcode/table.rs: bindings of one path shape share one route, each with its own capture names. A request's verb is its last segment from the first unencoded:(neither a variable's value nor a verb holds one, google/api/http.proto). A verb some binding of the path binds owns the URL and only its bindings answer (escapes compared case-insensitively); an unbound verb stays part of the last variable, as in Envoy and grpc-gateway. A template ending in a literal keeps its exact URL. Verbs, and the bindings without one, are indexed in a segment trie that ranks paths as matchit does, so neither is hidden by a better-ranked path and the work per request is bounded by its path; an encoded verb is taken off the decoded value. A URL no binding answers is no transcoded request and goes to the proxy's other routes, then to the fallback (or 404), preflight included; one answered for other methods is 405 with them inAllow. The body is not read before that decision.Allowon a 405 lists the methods of the other routes at the path that answered too. The proxy service serves them past the router, behind the same layers and guards;router()keeps them in an axum router for embedding, choosing the binding after it routes. Captures are told from those of a prefix the router is nested under by position.transcode/mod.rs: one dispatcher per route replaces the per-method router; path parameters are borrowed rather than copied;route_pathslists each route the router mounts, under the spelling it is mounted with (a path with verbs as*, since it answers every method), and both sides of a real clash (one method or a*rule, one verb), soProxyServerrefuses such a clash, an extra route on a path with verbs, or paths the router cannot hold together, at startup instead of panicking. Extra routes of a method the router skips, and transcoded routes it does not mount, reserve no path and lend no method to guard scopes.{name=shelves/*/books/*}) takes only a value of that shape: at the end of a path it is checked past the router's catch-all, before it the template is written into the router path segment by segment;**before the last segment is left out with an error. A variable over several segments,{name=**}included, keeps%2Fencoded in its value (google/api/http.proto); a route with more variables than the router holds is refused rather than panicking it.Access-Control-Request-Method) is answered by it, for REST and gRPC-Web alike, and for every origin it names back the request headers asked for, since*does not coverAuthorization. Tracing wraps CORS, so a preflight is traced. A transcoded answer served past the router carries noContent-Lengthon a 204, a 304 or a 2xx CONNECT answer, which has no content either (RFC 9110 §8.6, §9.3.6).TranscodeState::forwarded_header_names, a provided method).custom*rule, the verify endpoint) lets a scope over its own traffic name any method. OpenAPI documents only the routes the router mounts.ProxyService::with_fallbackhands it a URL whose path a transcoded route matches but no binding answers.CONTRIBUTING.md,SECURITY.md, CLA line in the README license section; two broken private rustdoc links fixed.Testing
Formatting, clippy and nextest for the default, no-backend and all-features builds, doc tests, rustdoc with warnings denied, the advisory audit and the MSRV check pass locally on macOS; CI runs the full matrix including musl.
Closes #138