Skip to main content

structured_proxy/transcode/
mod.rs

1//! REST→gRPC transcoding layer.
2//!
3//! Reads `google.api.http` annotations from proto service descriptors
4//! and builds axum routes that proxy JSON/form requests to gRPC upstream.
5//! The upstream decides the HTTP answer beyond the JSON body where it needs
6//! to: its response metadata becomes response headers, `x-http-code` sets the
7//! status of a successful unary call, and `google.api.HttpBody` carries a raw
8//! body in either direction.
9//!
10//! Generic: works with ANY proto descriptor set. No product-specific code.
11
12pub mod body;
13pub mod codec;
14pub mod error;
15pub(crate) mod httpbody;
16pub mod metadata;
17pub(crate) mod path;
18pub mod request;
19pub(crate) mod response;
20pub(crate) mod rule;
21mod select;
22mod table;
23
24pub use path::proto_path_to_axum;
25pub use select::RpcSelection;
26
27use axum::body::{Body, Bytes};
28use axum::extract::{FromRequest, FromRequestParts, OriginalUri, RawPathParams, Request, State};
29use axum::http::header::{ALLOW, CONTENT_TYPE};
30use axum::http::{HeaderMap, HeaderName, HeaderValue, Method, StatusCode, Uri};
31use axum::response::sse::{Event, KeepAlive, Sse};
32use axum::response::{IntoResponse, Response};
33use axum::Router;
34use futures::{StreamExt, TryStreamExt};
35use prost_reflect::{
36    DescriptorPool, DynamicMessage, FieldDescriptor, MessageDescriptor, MethodDescriptor,
37    SerializeOptions,
38};
39use std::borrow::Cow;
40use std::collections::HashMap;
41use std::sync::Arc;
42use std::time::Duration;
43use tonic::client::Grpc;
44use tonic::metadata::MetadataMap;
45
46use crate::client_address::ClientAddress;
47use crate::config::AliasConfig;
48use crate::guard::BoxedService;
49use crate::received::ReceivedRequest;
50use crate::service::ConnectionInfo;
51use crate::upstream::Upstream;
52use error::{ErrorDetailsPolicy, StatusDetails};
53use path::MountedPath;
54use response::UpstreamHeaders;
55use rule::RouteMethod;
56use table::{Choice, PathTable, Routes};
57
58/// How long the upstream may take to answer a call with its response headers
59/// when the client's `grpc-timeout` asks for no shorter deadline.
60pub const UPSTREAM_DEADLINE: Duration = Duration::from_secs(5);
61
62/// Trait for state types that support REST→gRPC transcoding.
63///
64/// Implement this for your application's state type to use `transcode::routes()`.
65/// Provides the minimal interface needed by transcode handlers. Each request
66/// works on its own clone of the state, so a clone should be cheap.
67pub trait TranscodeState: Clone + Send + Sync + 'static {
68    /// The gRPC service transcoded calls go to.
69    type Upstream: Upstream;
70    /// The upstream, taken out of this request's clone of the state.
71    fn into_upstream(self) -> Self::Upstream;
72    /// Headers to forward from HTTP to gRPC metadata.
73    fn forwarded_headers(&self) -> &[String];
74    /// [`forwarded_headers`](Self::forwarded_headers) as header names, when
75    /// the state holds them parsed: a request then skips parsing each name.
76    /// `None` by default.
77    fn forwarded_header_names(&self) -> Option<&[HeaderName]> {
78        None
79    }
80    /// SSE keep-alive interval (seconds) for server-streaming responses.
81    fn sse_keep_alive_secs(&self) -> u64;
82}
83
84impl<U: Upstream> TranscodeState for crate::ProxyState<U> {
85    type Upstream = U;
86    fn into_upstream(self) -> U {
87        self.upstream
88    }
89    fn forwarded_headers(&self) -> &[String] {
90        &self.forwarded_headers
91    }
92    fn forwarded_header_names(&self) -> Option<&[HeaderName]> {
93        Some(&self.forwarded_names)
94    }
95    fn sse_keep_alive_secs(&self) -> u64 {
96        self.sse_keep_alive_secs
97    }
98}
99
100/// Path parameters of a matched route, in path order: each name with its
101/// value, borrowed from the router's captures and the routes wherever the
102/// binding leaves them as they are.
103type PathParams<'a> = Vec<(&'a str, Cow<'a, str>)>;
104
105/// How the HTTP request body reaches the RPC's input message.
106#[derive(Debug, Clone)]
107enum RequestBody {
108    /// JSON or a form, mapped as the rule's `body` says.
109    Parsed(request::BodyMapping),
110    /// Raw bytes and `Content-Type` into the input message, a `google.api.HttpBody`.
111    RawRoot,
112    /// Raw bytes and `Content-Type` into the HttpBody field `body` names; the
113    /// other fields still come from path and query.
114    RawField {
115        /// The rule's `body` mapping, which keeps the field out of query binding.
116        mapping: request::BodyMapping,
117        field: FieldDescriptor,
118        /// The field's type, a `google.api.HttpBody`.
119        http_body: MessageDescriptor,
120    },
121}
122
123/// What the HTTP response body is made of.
124#[derive(Debug, Clone)]
125enum ResponseShape {
126    /// ProtoJSON of the response message, or of its `response_body` subfield.
127    Json(Option<String>),
128    /// The raw body of a `google.api.HttpBody`: the response message itself
129    /// (empty path) or the field chain `response_body` names.
130    HttpBody(Vec<FieldDescriptor>),
131}
132
133/// Route entry extracted from proto HTTP annotations.
134#[derive(Debug, Clone)]
135struct RouteEntry {
136    /// HTTP path pattern (e.g., "/v1/auth/opaque/login/start").
137    http_path: String,
138    /// The HTTP method(s) the binding answers.
139    http_method: RouteMethod,
140    /// gRPC path (e.g., "/sid.v1.AuthService/OpaqueLoginStart"), parsed once at
141    /// route-build time so each request clones a cheap `Bytes` refcount.
142    grpc_path: axum::http::uri::PathAndQuery,
143    /// Method descriptor for input/output message resolution.
144    method: MethodDescriptor,
145    /// Server-streaming RPC (NDJSON / SSE, or chunked HttpBody).
146    streaming: bool,
147    /// How the request body maps onto the gRPC request message.
148    request_body: RequestBody,
149    /// What the HTTP response body is made of.
150    response: ResponseShape,
151    /// Renderer for the status details of this route's errors; `None` when the
152    /// error-details policy switches them off for the route.
153    error_details: Option<Arc<StatusDetails>>,
154    /// Wrap NDJSON stream lines in `{"result"}` / `{"error"}` envelopes.
155    ndjson_envelope: bool,
156    /// [`codec::has_required_fields`] of the response type, computed once so a
157    /// request does not walk the descriptor.
158    response_has_required: bool,
159    /// Response metadata keys the operator keeps off the HTTP response, on top
160    /// of the ones that never go there.
161    denied_headers: Arc<[HeaderName]>,
162}
163
164impl RouteEntry {
165    fn codec(&self) -> codec::DynamicCodec {
166        codec::DynamicCodec::with_required_check(self.method.output(), self.response_has_required)
167    }
168
169    /// The raw body of `message`, on a route that answers with an HttpBody.
170    fn raw_body(&self, message: DynamicMessage) -> Option<httpbody::RawBody> {
171        match &self.response {
172            ResponseShape::HttpBody(path) => Some(httpbody::take(message, path)),
173            ResponseShape::Json(_) => None,
174        }
175    }
176}
177
178/// How [`routes_with_options`] builds the transcoded routes.
179///
180/// # Examples
181///
182/// ```
183/// use axum::http::HeaderName;
184/// use structured_proxy::transcode::error::ErrorDetailsPolicy;
185/// use structured_proxy::transcode::TranscodeOptions;
186///
187/// let options = TranscodeOptions::default()
188///     .with_error_details(ErrorDetailsPolicy::default().route("/v1/admin/**", false).unwrap())
189///     .with_ndjson_envelope(true)
190///     .with_denied_response_headers([HeaderName::from_static("x-debug-trace")]);
191/// # let _ = options;
192/// ```
193#[derive(Debug, Clone, Default)]
194pub struct TranscodeOptions {
195    pub(crate) error_details: ErrorDetailsPolicy,
196    pub(crate) ndjson_envelope: bool,
197    pub(crate) denied_response_headers: Arc<[HeaderName]>,
198    pub(crate) selection: RpcSelection,
199}
200
201impl TranscodeOptions {
202    /// Transcode only the RPCs `selection` names (every annotated one by
203    /// default).
204    pub fn with_selection(mut self, selection: RpcSelection) -> Self {
205        self.selection = selection;
206        self
207    }
208
209    /// Which routes return `google.rpc.Status` details in their error bodies
210    /// (all of them by default).
211    pub fn with_error_details(mut self, policy: ErrorDetailsPolicy) -> Self {
212        self.error_details = policy;
213        self
214    }
215
216    /// Wrap every NDJSON line of a server-streaming response in an envelope:
217    /// `{"result": <message>}` for data, `{"error": <error body>}` for the
218    /// terminal error (the grpc-gateway stream shape). Off by default, when a
219    /// data line is the bare message and the error line carries an
220    /// `@type: google.rpc.Status` marker instead. Only the envelope keeps the
221    /// two apart for RPCs that stream `google.protobuf.Any`, `Struct`, `Value`
222    /// or `ListValue`, whose messages can carry any keys. SSE is unaffected.
223    pub fn with_ndjson_envelope(mut self, enabled: bool) -> Self {
224        self.ndjson_envelope = enabled;
225        self
226    }
227
228    /// Response metadata keys that never become HTTP response headers, on top
229    /// of gRPC's own keys, hop-by-hop fields and `x-http-code`, which never do.
230    /// Replaces any list set before. Use it to keep internal headers (debug
231    /// traces, backend names) off a public edge; an upstream key not listed
232    /// here reaches the client.
233    pub fn with_denied_response_headers(
234        mut self,
235        names: impl IntoIterator<Item = HeaderName>,
236    ) -> Self {
237        self.denied_response_headers = names.into_iter().collect();
238        self
239    }
240}
241
242/// Build transcoded REST→gRPC routes from a descriptor pool.
243///
244/// Takes a `DescriptorPool` and optional path aliases from config.
245/// Returns an axum Router that transcodes REST requests to gRPC calls. Error
246/// bodies carry `google.rpc.Status` details on every route; use
247/// [`routes_with_options`] to choose per route or to frame NDJSON streams.
248pub fn routes<S: TranscodeState>(pool: &DescriptorPool, aliases: &[AliasConfig]) -> Router<S> {
249    routes_with_options(pool, aliases, &TranscodeOptions::default())
250}
251
252/// [`routes`], built as `options` describe.
253///
254/// A binding answers the requests whose path matches its template, custom verb
255/// included (`/v1/{name}:cancel`, google/api/http.proto `Verb`), and whose
256/// method is its own; a GET binding answers HEAD too. A request's verb is its
257/// last segment from the first unencoded `:`: a verb some binding of its path
258/// binds owns the URL, answered by that verb's bindings alone; a verb none
259/// binds stays part of the last variable. A URL that bindings answer, but none
260/// with the request's method, is `405` with their methods in `Allow`; a path
261/// that matches a template but that no binding answers is `404`.
262///
263/// A second binding for a method, path and verb already taken (or any binding
264/// on a path and verb a `custom` `*` rule takes, which answers every method),
265/// and a template the router cannot match (text around a variable within one
266/// segment, a path that does not start with `/`), are skipped with an error in
267/// the log.
268pub fn routes_with_options<S: TranscodeState>(
269    pool: &DescriptorPool,
270    aliases: &[AliasConfig],
271    options: &TranscodeOptions,
272) -> Router<S> {
273    routes_and_choices(pool, aliases, options).0
274}
275
276/// The routes of [`routes_with_options`], with the [`Choices`] among their
277/// bindings that the caller makes before its own layers.
278pub(crate) fn routes_and_choices<S: TranscodeState>(
279    pool: &DescriptorPool,
280    aliases: &[AliasConfig],
281    options: &TranscodeOptions,
282) -> (Router<S>, Option<Choices>) {
283    let tables = path_tables(pool, aliases, options);
284    if tables.is_empty() {
285        tracing::warn!("No HTTP-annotated RPCs found in proto descriptors");
286        return (Router::new(), None);
287    }
288
289    let routes = Arc::new(Routes::new(tables));
290    let choices = Choices {
291        routes: routes.clone(),
292        ranked: None,
293    };
294    let mut router: Router<S> = Router::new();
295    for (table, mounted) in routes.tables.iter().enumerate() {
296        let routes = routes.clone();
297        // Every method goes to the routes, which answer 405 themselves: the
298        // URL, not the path alone, decides which methods a path answers. A
299        // HEAD a GET binding serves still goes out without a body: axum's
300        // route future empties the body of every HEAD response, `any` routes
301        // included.
302        let handler =
303            move |State(state): State<S>, request: Request| dispatch(routes, table, state, request);
304        let mut route = axum::routing::any(handler.clone());
305        // A binding answering HEAD itself is the path's HEAD route, before
306        // the GET of another route merged at the path answers HEAD.
307        if mounted.binds_head() {
308            route = route.head(handler);
309        }
310        router = router.route(&mounted.path, route);
311    }
312    (router, Some(choices))
313}
314
315/// The choice of the binding a request reaches among the transcoded routes,
316/// made before any layer of the routes runs. A URL no binding answers (its
317/// field template, its custom verb) is no transcoded request: it goes to the
318/// proxy's other routes instead.
319pub(crate) struct Choices {
320    routes: Arc<Routes>,
321    /// The transcoded paths among the proxy's other routes, when known.
322    ranked: Option<Arc<table::Ranked>>,
323}
324
325impl Choices {
326    /// These choices among `others`: the router paths of the proxy's other
327    /// routes, each with the method it answers (`None`: every one). A table
328    /// answers a request a better-ranked one refused only when no other
329    /// route ranks before it. Ranked among none of them when an other path
330    /// is one the proxy cannot rank as the router does.
331    pub(crate) fn among(mut self, others: &[(Option<Method>, String)]) -> Self {
332        self.ranked = table::Ranked::new(&self.routes, others).map(Arc::new);
333        self
334    }
335
336    /// These choices, handing a URL no binding answers to `elsewhere`: a
337    /// router of the proxy's other routes.
338    pub(crate) fn with_elsewhere(self, elsewhere: Router) -> Chooser {
339        Chooser(Arc::new(Choosing {
340            routes: self.routes,
341            ranked: self.ranked,
342            elsewhere,
343        }))
344    }
345
346    /// The transcoded routes served past any router, calling with `state`;
347    /// `layered` puts the layers of the transcoded routes around them.
348    /// `None` without a ranking among the other routes.
349    pub(crate) fn direct<S: TranscodeState>(
350        &self,
351        state: S,
352        layered: impl FnOnce(BoxedService) -> BoxedService,
353    ) -> Option<Direct> {
354        let ranked = self.ranked.clone()?;
355        let dispatcher = BoxedService::new(Dispatcher {
356            routes: self.routes.clone(),
357            state,
358        });
359        Some(Direct {
360            ranking: Arc::new(Ranking {
361                ranked,
362                routes: self.routes.clone(),
363            }),
364            service: layered(dispatcher),
365        })
366    }
367}
368
369/// The transcoded routes served straight from the proxy's own match of a
370/// request's path, past any router.
371#[derive(Clone)]
372pub(crate) struct Direct {
373    ranking: Arc<Ranking>,
374    /// The routes behind their layers.
375    service: BoxedService,
376}
377
378struct Ranking {
379    ranked: Arc<table::Ranked>,
380    routes: Arc<Routes>,
381}
382
383impl Direct {
384    /// Whether `request` is one of the transcoded routes', whose binding
385    /// then rides on it: its path's best match among every route of the
386    /// proxy is a transcoded path, the method not another route's there, and
387    /// a binding answers the URL. A request for none goes to the other
388    /// routes, which a fallback follows, as if the transcoded ones were not
389    /// there.
390    pub(crate) fn takes<B>(&self, request: &mut http::Request<B>) -> bool {
391        let ranking = &*self.ranking;
392        match ranking.ranked.table(request.method(), request.uri().path()) {
393            Some(table) => choose(&ranking.routes, Some(&ranking.ranked), table, request),
394            None => false,
395        }
396    }
397
398    /// The routes behind their layers, for a request [`takes`](Self::takes)
399    /// took.
400    pub(crate) fn service(&self) -> &BoxedService {
401        &self.service
402    }
403}
404
405/// The transcoded routes as one service, for requests whose binding rides on
406/// them.
407#[derive(Clone)]
408struct Dispatcher<S> {
409    routes: Arc<Routes>,
410    state: S,
411}
412
413impl<S: TranscodeState> tower::Service<Request> for Dispatcher<S> {
414    type Response = Response;
415    type Error = std::convert::Infallible;
416    type Future = std::pin::Pin<
417        Box<dyn std::future::Future<Output = Result<Response, std::convert::Infallible>> + Send>,
418    >;
419
420    fn poll_ready(
421        &mut self,
422        _cx: &mut std::task::Context<'_>,
423    ) -> std::task::Poll<Result<(), Self::Error>> {
424        std::task::Poll::Ready(Ok(()))
425    }
426
427    fn call(&mut self, request: Request) -> Self::Future {
428        let routes = self.routes.clone();
429        let state = self.state.clone();
430        Box::pin(async move { Ok(serve(routes, state, request).await) })
431    }
432}
433
434/// Choose among `routes` for `request`, which the router matches (or
435/// matched) to the table at `table`, ranked among the proxy's other routes
436/// by `ranked` when known: `false` when no binding answers it, otherwise the
437/// choice rides on the request to [`dispatch`].
438fn choose<B>(
439    routes: &Routes,
440    ranked: Option<&table::Ranked>,
441    table: usize,
442    request: &mut http::Request<B>,
443) -> bool {
444    let (method, path) = (request.method(), request.uri().path());
445    let choice = match ranked {
446        Some(ranked) => match routes.choose_ranked(table, method, path, &|other| {
447            ranked.ranks_first(other, method, path)
448        }) {
449            // The other routes at the path answer methods too.
450            // The table that answered, not the one the path matched first:
451            // a refused binding passes the request on.
452            Choice::MethodNotAllowed { table, allow } => Choice::MethodNotAllowed {
453                table,
454                allow: ranked.allow(table, allow),
455            },
456            choice => choice,
457        },
458        None => routes.choose(table, method, path),
459    };
460    match choice {
461        Choice::NotFound => false,
462        choice => {
463            request.extensions_mut().insert(Chosen(choice));
464            true
465        }
466    }
467}
468
469/// [`Choices`] with the routes a URL no binding answers goes to. One
470/// reference count: the router clones its routes' layers for every request.
471#[derive(Clone)]
472pub(crate) struct Chooser(Arc<Choosing>);
473
474struct Choosing {
475    routes: Arc<Routes>,
476    ranked: Option<Arc<table::Ranked>>,
477    elsewhere: Router,
478}
479
480impl Chooser {
481    /// The other routes, with `fallback` answering what they do not.
482    pub(crate) fn with_fallback(self, fallback: BoxedService) -> Self {
483        Self(Arc::new(Choosing {
484            routes: self.0.routes.clone(),
485            ranked: self.0.ranked.clone(),
486            elsewhere: self.0.elsewhere.clone().fallback_service(fallback),
487        }))
488    }
489
490    /// `router`, holding the transcoded routes, choosing outside its layers
491    /// for a router that is served on its own, merged or nested: the request
492    /// is past the routing then, and the router reports the route it matched
493    /// with the paths it is nested at in front.
494    pub(crate) fn layer(self, router: Router) -> Router {
495        router.layer(self)
496    }
497}
498
499impl<S> tower::Layer<S> for Chooser {
500    type Service = ChooseRouted<S>;
501
502    fn layer(&self, route: S) -> ChooseRouted<S> {
503        ChooseRouted {
504            chooser: self.clone(),
505            route,
506        }
507    }
508}
509
510/// A transcoded route, with the choice among its bindings made before its
511/// layers: a request no binding answers goes to the other routes.
512#[derive(Clone)]
513pub(crate) struct ChooseRouted<S> {
514    chooser: Chooser,
515    route: S,
516}
517
518impl<S> tower::Service<Request> for ChooseRouted<S>
519where
520    S: tower::Service<Request, Response = Response, Error = std::convert::Infallible>,
521{
522    type Response = Response;
523    type Error = std::convert::Infallible;
524    type Future = ChooseFuture<S::Future>;
525
526    fn poll_ready(
527        &mut self,
528        cx: &mut std::task::Context<'_>,
529    ) -> std::task::Poll<Result<(), Self::Error>> {
530        self.route.poll_ready(cx)
531    }
532
533    fn call(&mut self, mut request: Request) -> Self::Future {
534        let choosing = &*self.chooser.0;
535        let extensions = request.extensions();
536        let matched = extensions
537            .get::<axum::extract::MatchedPath>()
538            .and_then(|matched| {
539                let nested = extensions.get::<axum::extract::NestedPath>();
540                local_path(matched.as_str(), nested.map(|nested| nested.as_str()))
541            })
542            .and_then(|path| choosing.routes.table_at(path));
543        let ranked = choosing.ranked.as_deref();
544        if matched.is_none_or(|matched| choose(&choosing.routes, ranked, matched, &mut request)) {
545            return ChooseFuture::Route {
546                future: self.route.call(request),
547            };
548        }
549        // The other routes route it again: the route the first routing
550        // matched goes, so theirs is reported. Its captures stay, unread: no
551        // other route of the proxy takes path parameters, and an extra
552        // route's handler sees no extensions. Routes an application merged
553        // after the proxy's are not among them: the router has routed the
554        // request to this route and tries no other, so they are reached only
555        // as extra routes, or behind a proxy service's fallback.
556        request
557            .extensions_mut()
558            .remove::<axum::extract::MatchedPath>();
559        ChooseFuture::Elsewhere {
560            future: tower::ServiceExt::oneshot(choosing.elsewhere.clone(), request),
561        }
562    }
563}
564
565pin_project_lite::pin_project! {
566    /// The response future of [`ChooseRouted`].
567    #[project = ChooseProjection]
568    pub(crate) enum ChooseFuture<F> {
569        Route { #[pin] future: F },
570        Elsewhere { #[pin] future: tower::util::Oneshot<Router, Request> },
571    }
572}
573
574impl<F> std::future::Future for ChooseFuture<F>
575where
576    F: std::future::Future<Output = Result<Response, std::convert::Infallible>>,
577{
578    type Output = Result<Response, std::convert::Infallible>;
579
580    fn poll(
581        self: std::pin::Pin<&mut Self>,
582        cx: &mut std::task::Context<'_>,
583    ) -> std::task::Poll<Self::Output> {
584        match self.project() {
585            ChooseProjection::Route { future } => future.poll(cx),
586            ChooseProjection::Elsewhere { future } => future.poll(cx),
587        }
588    }
589}
590
591/// The binding chosen for a request before the layers of the routes, for
592/// [`dispatch`].
593#[derive(Clone)]
594struct Chosen(Choice);
595
596/// The router path `matched` names in the router nested at `nested`, which
597/// writes the paths it is nested at in front of it.
598fn local_path<'p>(matched: &'p str, nested: Option<&str>) -> Option<&'p str> {
599    let Some(nested) = nested else {
600        return Some(matched);
601    };
602    // A prefix ending in `/` is joined to the path without its leading `/`,
603    // and a path of `/` alone is the prefix itself (axum's nesting).
604    let local = matched.strip_prefix(nested.trim_end_matches('/'))?;
605    Some(if local.is_empty() { "/" } else { local })
606}
607
608/// The tables [`routes_with_options`] mounts, one per router path, each
609/// holding every binding of that path in binding order.
610fn path_tables(
611    pool: &DescriptorPool,
612    aliases: &[AliasConfig],
613    options: &TranscodeOptions,
614) -> Vec<PathTable> {
615    let bindings = route_bindings(pool, aliases, &options.selection, true);
616    tracing::info!("Registering {} transcoded REST→gRPC routes", bindings.len());
617
618    // At most two renderers, without and with the opaque-detail extension,
619    // each shared by every route that uses it and built only when one does.
620    // The second is a copy of the first: the descriptor pool inside is shared.
621    let mut status_details: [Option<Arc<StatusDetails>>; 2] = [None, None];
622    // The router tells paths apart by shape only, so every binding of one
623    // shape goes into one table, whatever its capture names.
624    let mut tables: Vec<PathTable> = Vec::new();
625    let mut by_shape: HashMap<String, usize> = HashMap::new();
626    for mut binding in bindings {
627        let policy = &options.error_details;
628        let route = binding.mount.display();
629        if policy.enabled_for(&route) {
630            let opaque = policy.opaque_for(&route);
631            let slot = usize::from(opaque);
632            if status_details[slot].is_none() {
633                let base = status_details
634                    .iter()
635                    .flatten()
636                    .next()
637                    .map(|existing| existing.as_ref().clone())
638                    .unwrap_or_else(|| StatusDetails::new(pool));
639                status_details[slot] = Some(Arc::new(base.with_opaque_details(opaque)));
640            }
641            binding.entry.error_details = status_details[slot].clone();
642        }
643        binding.entry.ndjson_envelope = options.ndjson_envelope;
644        binding.entry.denied_headers = options.denied_response_headers.clone();
645        match by_shape.get(&binding.mount.shape) {
646            Some(&index) => tables[index].add(binding.mount, binding.entry),
647            None => {
648                by_shape.insert(binding.mount.shape.clone(), tables.len());
649                tables.push(PathTable::new(binding.mount, binding.entry));
650            }
651        }
652    }
653
654    retain_mountable(
655        &mut tables,
656        |table| table.path.as_str(),
657        |path, error| {
658            tracing::error!(
659                path = %path,
660                %error,
661                "transcoded route conflicts with another; skipping its bindings"
662            );
663        },
664    );
665    tables
666}
667
668/// Keep the items whose router path a router holding the ones kept before
669/// accepts, in order; `refused` hears of each left out. axum would panic on
670/// such a path, and matchit is the router axum matches with.
671fn retain_mountable<T>(
672    items: &mut Vec<T>,
673    path: impl Fn(&T) -> &str,
674    mut refused: impl FnMut(&str, matchit::InsertError),
675) {
676    let mut router = matchit::Router::new();
677    items.retain(|item| match router.insert(path(item), ()) {
678        Ok(()) => true,
679        Err(error) => {
680            refused(path(item), error);
681            false
682        }
683    });
684}
685
686/// The shapes of the router paths [`routes_with_options`] mounts for
687/// `selection`: what the OpenAPI document may promise.
688pub(crate) fn mounted_shapes(
689    pool: &DescriptorPool,
690    aliases: &[AliasConfig],
691    selection: &RpcSelection,
692) -> std::collections::HashSet<String> {
693    // One path per shape, the first binding's, as the tables are built.
694    let mut seen = std::collections::HashSet::new();
695    let mut paths: Vec<MountedPath> = route_bindings(pool, aliases, selection, false)
696        .into_iter()
697        .filter_map(|binding| {
698            seen.insert(binding.mount.shape.clone())
699                .then_some(binding.mount)
700        })
701        .collect();
702    retain_mountable(&mut paths, |mount| mount.axum.as_str(), |_, _| {});
703    paths.into_iter().map(|mount| mount.shape).collect()
704}
705
706/// Serve `request`, which the router matched to the table at `matched`:
707/// choose the binding before anything is extracted, so a request no binding
708/// answers gets its 404 or 405 without its body being read.
709async fn dispatch<S: TranscodeState>(
710    routes: Arc<Routes>,
711    matched: usize,
712    state: S,
713    request: Request,
714) -> Response {
715    let (mut parts, body) = request.into_parts();
716    let choice = match parts.extensions.remove::<Chosen>() {
717        Some(Chosen(choice)) => choice,
718        None => routes.choose(matched, &parts.method, parts.uri.path()),
719    };
720    let (table, index) = match choice {
721        Choice::Route { table, index } => (table, index),
722        Choice::MethodNotAllowed { allow, .. } => {
723            return (StatusCode::METHOD_NOT_ALLOWED, [(ALLOW, allow)]).into_response()
724        }
725        // Only routes served without a `Chooser` get here.
726        Choice::NotFound => return StatusCode::NOT_FOUND.into_response(),
727    };
728    // The router's captures, decoded, shared with it rather than copied.
729    let captured = match RawPathParams::from_request_parts(&mut parts, &state).await {
730        Ok(captured) => captured,
731        Err(rejection) => return rejection.into_response(),
732    };
733    let mut path_params: PathParams<'_> = captured
734        .iter()
735        .map(|(name, value)| (name, Cow::Borrowed(value)))
736        .collect();
737    let multi_segment = routes.tables[table].multi_segment(index);
738    // The path the router matched, kept past the parts going on with the
739    // body when the captures are read from it.
740    let local;
741    if table != matched || !multi_segment.is_empty() {
742        // Another path the request matches answers it (a verb bound there, or
743        // the only bindings without a verb), or a variable over several
744        // segments keeps `%2F` the router decoded: the captures come from the
745        // request's path, the router's prefix parameters stay.
746        local = parts.uri.clone();
747        // The matched path's captures are the router's last parameters, after
748        // those of the prefixes it is nested under, which may share their
749        // names: they go by position, the prefixes' stay.
750        let prefixes = path_params
751            .len()
752            .checked_sub(routes.tables[matched].captures.len())
753            .expect("the router captures every variable of the path it matched");
754        path_params.truncate(prefixes);
755        if let Err(rejection) = capture(
756            &routes,
757            table,
758            multi_segment,
759            local.path(),
760            &mut path_params,
761        ) {
762            return rejection.into_response(routes.tables[table].entry(index));
763        }
764    }
765    // The target as received, before a router the proxy is nested in strips
766    // its prefix; the router records it on every request it routes.
767    let uri = match parts.extensions.remove::<OriginalUri>() {
768        Some(OriginalUri(uri)) => uri,
769        None => parts.uri.clone(),
770    };
771    let begun = begin(
772        &routes,
773        (table, index),
774        &state,
775        (parts, body),
776        path_params,
777        uri,
778    )
779    .await;
780    serve_begun(begun, routes, table, index).await
781}
782
783/// Serve `request`, which the proxy matched to a transcoded binding itself,
784/// with no router: the choice rides on it.
785async fn serve<S: TranscodeState>(routes: Arc<Routes>, state: S, request: Request) -> Response {
786    let (mut parts, body) = request.into_parts();
787    let head = parts.method == Method::HEAD;
788    let connect = parts.method == Method::CONNECT;
789    let choice = match parts.extensions.remove::<Chosen>() {
790        Some(Chosen(choice)) => choice,
791        None => Choice::NotFound,
792    };
793    let mut response = match choice {
794        Choice::Route { table, index } => {
795            // The path the proxy matched, what the captures are read from.
796            let local = parts.uri.clone();
797            // The target as received, before a router the proxy is nested in
798            // strips its prefix.
799            let uri = match parts.extensions.remove::<OriginalUri>() {
800                Some(OriginalUri(uri)) => uri,
801                None => local.clone(),
802            };
803            let multi_segment = routes.tables[table].multi_segment(index);
804            let mut path_params = Vec::with_capacity(routes.tables[table].captures.len());
805            match capture(
806                &routes,
807                table,
808                multi_segment,
809                local.path(),
810                &mut path_params,
811            ) {
812                Ok(()) => {
813                    let begun = begin(
814                        &routes,
815                        (table, index),
816                        &state,
817                        (parts, body),
818                        path_params,
819                        uri,
820                    )
821                    .await;
822                    serve_begun(begun, routes, table, index).await
823                }
824                Err(rejection) => rejection.into_response(routes.tables[table].entry(index)),
825            }
826        }
827        Choice::MethodNotAllowed { allow, .. } => {
828            (StatusCode::METHOD_NOT_ALLOWED, [(ALLOW, allow)]).into_response()
829        }
830        Choice::NotFound => StatusCode::NOT_FOUND.into_response(),
831    };
832    let status = response.status();
833    // A 2xx answer to CONNECT has no content and no Content-Length (RFC 9110
834    // §9.3.6, §8.6), as a router's route answers it too.
835    if connect && status.is_success() {
836        response.headers_mut().remove(http::header::CONTENT_LENGTH);
837        *response.body_mut() = axum::body::Body::empty();
838        return response;
839    }
840    // As a router's route answers: the length of a body whose size is known,
841    // and none sent for a HEAD. Not on a 204, which has no Content-Length, nor
842    // on a 304, whose length could only describe the selected representation
843    // (RFC 9110 §8.6), never the empty body sent.
844    if status != StatusCode::NO_CONTENT
845        && status != StatusCode::NOT_MODIFIED
846        && !response
847            .headers()
848            .contains_key(http::header::CONTENT_LENGTH)
849    {
850        if let Some(length) = http_body::Body::size_hint(response.body()).exact() {
851            response
852                .headers_mut()
853                .insert(http::header::CONTENT_LENGTH, HeaderValue::from(length));
854        }
855    }
856    if head {
857        *response.body_mut() = axum::body::Body::empty();
858    }
859    response
860}
861
862/// How far [`begin`] got: the upstream call to make, or the answer already.
863enum Begun<U> {
864    Call {
865        call: Call<U>,
866        sse: bool,
867        keep_alive_secs: u64,
868    },
869    Answered(Response),
870}
871
872/// Bind the request of `parts` and `body` to the binding at `binding` (table,
873/// index) with its path parameters `path_params`, its target as received
874/// `uri`: read its body and map it onto the RPC.
875async fn begin<S: TranscodeState>(
876    routes: &Routes,
877    (table, index): (usize, usize),
878    state: &S,
879    (mut parts, body): (http::request::Parts, axum::body::Body),
880    mut path_params: PathParams<'_>,
881    uri: Uri,
882) -> Begun<S::Upstream> {
883    routes.tables[table].bind_params(index, &mut path_params);
884    // Taken rather than copied: reading the body needs only its limit, which
885    // stays in the extensions.
886    let client = Client {
887        headers: std::mem::take(&mut parts.headers),
888        origin: Origin {
889            connection: parts.extensions.remove::<ConnectionInfo>(),
890            address: parts.extensions.remove::<ClientAddress>(),
891        },
892        line: RequestLine {
893            method: std::mem::replace(&mut parts.method, Method::GET),
894            uri,
895        },
896    };
897    let body = match Bytes::from_request(Request::from_parts(parts, body), state).await {
898        Ok(body) => body,
899        Err(rejection) => return Begun::Answered(rejection.into_response()),
900    };
901    let entry = routes.tables[table].entry(index);
902    match start(state.clone(), client, &path_params, body, entry) {
903        Ok((call, sse, keep_alive_secs)) => Begun::Call {
904            call,
905            sse,
906            keep_alive_secs,
907        },
908        Err(rejection) => Begun::Answered(rejection.into_response(entry)),
909    }
910}
911
912/// Make the call [`begin`] got to, the parameters bound: the routes go on
913/// with it.
914async fn serve_begun<U: crate::upstream::Upstream>(
915    begun: Begun<U>,
916    routes: Arc<Routes>,
917    table: usize,
918    index: usize,
919) -> Response {
920    let (call, sse, keep_alive_secs) = match begun {
921        Begun::Call {
922            call,
923            sse,
924            keep_alive_secs,
925        } => (call, sse, keep_alive_secs),
926        Begun::Answered(response) => return response,
927    };
928    let entry = RouteRef {
929        routes,
930        table,
931        index,
932    };
933    if entry.streaming {
934        streaming_call(call, entry, sse, keep_alive_secs).await
935    } else {
936        unary_call(call, &entry).await
937    }
938}
939
940/// Add the captures of the table at `table` matching `path` to `params`,
941/// percent-decoded as the router decodes its own except at the positions of
942/// `multi_segment`, which keep `%2F`; borrowed from `path` where nothing is
943/// decoded.
944fn capture<'p>(
945    routes: &'p Routes,
946    table: usize,
947    multi_segment: &[usize],
948    path: &'p str,
949    params: &mut PathParams<'p>,
950) -> Result<(), Unmappable> {
951    let found = routes
952        .params_of(table, path)
953        .expect("the chosen table was found by matching this path");
954    for (position, (name, raw)) in found.iter().enumerate() {
955        let decoded: Cow<'p, [u8]> = if multi_segment.contains(&position) {
956            path::decode_multi_segment(raw)
957        } else {
958            percent_encoding::percent_decode_str(raw).into()
959        };
960        let not_utf8 = || Unmappable(format!("path parameter {name} is not valid UTF-8"));
961        let value = match decoded {
962            Cow::Borrowed(bytes) => {
963                Cow::Borrowed(std::str::from_utf8(bytes).map_err(|_| not_utf8())?)
964            }
965            Cow::Owned(bytes) => Cow::Owned(String::from_utf8(bytes).map_err(|_| not_utf8())?),
966        };
967        params.push((name, value));
968    }
969    Ok(())
970}
971
972/// The binding serving a request, held through the routes: they are already
973/// shared with the request, so naming the binding takes no refcount of its
974/// own.
975#[derive(Clone)]
976struct RouteRef {
977    routes: Arc<Routes>,
978    table: usize,
979    index: usize,
980}
981
982impl std::ops::Deref for RouteRef {
983    type Target = RouteEntry;
984
985    fn deref(&self) -> &RouteEntry {
986        self.routes.tables[self.table].entry(self.index)
987    }
988}
989
990/// One transcode route to mount: the RPC entry that serves it and where it is
991/// mounted.
992struct RouteBinding {
993    entry: RouteEntry,
994    mount: MountedPath,
995}
996
997impl RouteBinding {
998    fn claim(&self) -> table::Claim<'_> {
999        table::Claim {
1000            method: &self.entry.http_method,
1001            verb: self.mount.verb.as_deref(),
1002            template: self.mount.last_template.as_deref(),
1003            literals: &self.mount.literals,
1004        }
1005    }
1006}
1007
1008/// The single source of truth for what [`routes`] mounts: every binding of
1009/// every unary and server-streaming RPC, plus its config aliases, less the
1010/// templates the router cannot match. Both [`routes`] (to build handlers) and
1011/// [`route_paths`] (to enumerate paths for collision checks) consume this, so
1012/// the mounted set and the enumerated set cannot drift apart. A template left
1013/// out is logged when `report` is set.
1014fn route_bindings(
1015    pool: &DescriptorPool,
1016    aliases: &[AliasConfig],
1017    selection: &RpcSelection,
1018    report: bool,
1019) -> Vec<RouteBinding> {
1020    let mut bindings = Vec::new();
1021    let mut push = |mount: MountedPath, entry: RouteEntry| match mount.routable() {
1022        Ok(()) => bindings.push(RouteBinding { entry, mount }),
1023        Err(_) if !report => {}
1024        Err(error) => tracing::error!(
1025            path = %mount.display(),
1026            rpc = %entry.grpc_path,
1027            %error,
1028            "google.api.http path template cannot be routed; skipping this binding"
1029        ),
1030    };
1031    for entry in extract_routes(pool, selection) {
1032        for alias in aliases {
1033            if let Some(suffix) = entry.http_path.strip_prefix(&alias.to) {
1034                if alias.from.ends_with("/{path}") {
1035                    let prefix = alias.from.trim_end_matches("/{path}");
1036                    push(
1037                        MountedPath::new(&format!("{prefix}{suffix}")),
1038                        entry.clone(),
1039                    );
1040                }
1041            }
1042        }
1043        push(MountedPath::new(&entry.http_path), entry);
1044    }
1045    bindings
1046}
1047
1048/// The axum paths [`routes_with_options`] would register for this pool,
1049/// aliases and selection.
1050///
1051/// Mirrors the registration (every binding of every selected unary and
1052/// server-streaming RPC, and their config aliases) without building handlers,
1053/// so callers can detect route collisions before mounting additional routes
1054/// (e.g. a forward-auth endpoint).
1055///
1056/// Each entry is `(method, path)` where `method` is the uppercase HTTP token,
1057/// or `*` for a route that answers every method, so callers can distinguish
1058/// same-path/different-method routes from real conflicts. The bindings of one
1059/// path are listed as the route they share: `*` once when a `custom` `*` rule
1060/// or a custom verb after a variable is among them (the transcoded routes
1061/// match the verb, and answer a bound verb's other methods with 405), else
1062/// each method once. Two bindings that cannot both serve (one method or a `*`
1063/// rule, one verb) are listed both, so the caller sees the collision.
1064pub fn route_paths(
1065    pool: &DescriptorPool,
1066    aliases: &[AliasConfig],
1067    selection: &RpcSelection,
1068) -> Vec<(String, String)> {
1069    let mut paths = Vec::new();
1070    for bindings in &shape_bindings(pool, aliases, selection) {
1071        // The route the shape shares, under the spelling it is mounted with:
1072        // that of its first binding, whatever names the others capture under.
1073        let path = bindings[0].mount.axum.as_str();
1074        let listed = |method: &str| (method.to_owned(), path.to_owned());
1075        // A path with a verb after its variable answers every method: one
1076        // whose verb is bound only for others is told so with 405.
1077        if bindings
1078            .iter()
1079            .any(|b| b.entry.http_method == RouteMethod::Any || b.mount.verb.is_some())
1080        {
1081            paths.push(listed("*"));
1082        } else {
1083            let mut methods: Vec<&str> = Vec::new();
1084            for binding in bindings {
1085                let method = binding.entry.http_method.as_str();
1086                if !methods.contains(&method) {
1087                    methods.push(method);
1088                    paths.push(listed(method));
1089                }
1090            }
1091        }
1092        for (at, binding) in bindings.iter().enumerate() {
1093            let clashes = bindings[..at]
1094                .iter()
1095                .any(|earlier| table::clash(&earlier.claim(), &binding.claim()));
1096            if clashes {
1097                paths.push(listed(binding.entry.http_method.as_str()));
1098            }
1099        }
1100    }
1101    paths
1102}
1103
1104/// The bindings of each route shape the router mounts, in first-binding
1105/// order: a shape it refuses beside the ones before it is skipped, as the
1106/// tables are, and answers nothing. The routes themselves report a template
1107/// left out.
1108fn shape_bindings(
1109    pool: &DescriptorPool,
1110    aliases: &[AliasConfig],
1111    selection: &RpcSelection,
1112) -> Vec<Vec<RouteBinding>> {
1113    let mut shapes: Vec<Vec<RouteBinding>> = Vec::new();
1114    let mut by_shape: HashMap<String, usize> = HashMap::new();
1115    for binding in route_bindings(pool, aliases, selection, false) {
1116        match by_shape.get(&binding.mount.shape) {
1117            Some(&index) => shapes[index].push(binding),
1118            None => {
1119                by_shape.insert(binding.mount.shape.clone(), shapes.len());
1120                shapes.push(vec![binding]);
1121            }
1122        }
1123    }
1124    retain_mountable(
1125        &mut shapes,
1126        |bindings| bindings[0].mount.axum.as_str(),
1127        |_, _| {},
1128    );
1129    shapes
1130}
1131
1132/// The methods the transcoded bindings for one pool, aliases and selection
1133/// answer: what [`route_paths`] lists under `*` still names methods a route
1134/// answers, or every one for a `custom` `*` rule.
1135pub(crate) struct BoundMethods {
1136    /// The methods the bindings name, each once.
1137    pub(crate) methods: Vec<Method>,
1138    /// Whether a `custom` `*` rule answers every method.
1139    pub(crate) every: bool,
1140}
1141
1142/// The [`BoundMethods`] of the transcoded bindings for this pool, aliases
1143/// and selection that the router mounts: a skipped route answers none.
1144pub(crate) fn bound_methods(
1145    pool: &DescriptorPool,
1146    aliases: &[AliasConfig],
1147    selection: &RpcSelection,
1148) -> BoundMethods {
1149    let mut bound = BoundMethods {
1150        methods: Vec::new(),
1151        every: false,
1152    };
1153    for binding in shape_bindings(pool, aliases, selection)
1154        .into_iter()
1155        .flatten()
1156    {
1157        match binding.entry.http_method {
1158            RouteMethod::One(method) if !bound.methods.contains(&method) => {
1159                bound.methods.push(method);
1160            }
1161            RouteMethod::One(_) => {}
1162            RouteMethod::Any => bound.every = true,
1163        }
1164    }
1165    bound
1166}
1167
1168/// JSON serialization options shared by the unary and streaming response paths,
1169/// so a given message serializes identically regardless of RPC kind.
1170fn response_serialize_options() -> SerializeOptions {
1171    SerializeOptions::new()
1172        .skip_default_fields(false)
1173        .stringify_64_bit_integers(true)
1174}
1175
1176/// Serialize a message straight to JSON bytes, without an intermediate tree.
1177fn message_to_json_bytes(
1178    msg: &DynamicMessage,
1179    opts: &SerializeOptions,
1180) -> Result<Vec<u8>, serde_json::Error> {
1181    let mut buf = Vec::with_capacity(128);
1182    msg.serialize_with_options(&mut serde_json::Serializer::new(&mut buf), opts)?;
1183    Ok(buf)
1184}
1185
1186/// Serialize one streamed gRPC message to a compact JSON string.
1187fn message_to_json_string(msg: &DynamicMessage, opts: &SerializeOptions) -> Result<String, String> {
1188    let buf = message_to_json_bytes(msg, opts).map_err(|e| e.to_string())?;
1189    // SAFETY: serde_json's serializer writes only valid UTF-8, the same
1190    // guarantee `serde_json::to_string` relies on.
1191    Ok(unsafe { String::from_utf8_unchecked(buf) })
1192}
1193
1194/// The unary response body as JSON: the whole message, or the subfield
1195/// `response_body` names (JSON `null` when the path does not exist).
1196pub(crate) fn json_body(
1197    msg: &DynamicMessage,
1198    response_body: Option<&str>,
1199) -> Result<Vec<u8>, serde_json::Error> {
1200    let opts = response_serialize_options();
1201    let Some(path) = response_body else {
1202        return message_to_json_bytes(msg, &opts);
1203    };
1204    // Walk the tree by moving each subtree out, so nothing is copied.
1205    // `response_body` names proto fields, while the tree is keyed by their
1206    // JSON names, so each segment is resolved through the descriptor.
1207    let mut value = Some(msg.serialize_with_options(serde_json::value::Serializer, &opts)?);
1208    let mut desc = Some(prost_reflect::ReflectMessage::descriptor(msg));
1209    for segment in path.split('.') {
1210        let field = desc.as_ref().and_then(|d| d.get_field_by_name(segment));
1211        desc = match field.as_ref().map(FieldDescriptor::kind) {
1212            Some(prost_reflect::Kind::Message(inner)) => Some(inner),
1213            _ => None,
1214        };
1215        value = match (value, field) {
1216            (Some(serde_json::Value::Object(mut fields)), Some(field)) => {
1217                fields.remove(field.json_name())
1218            }
1219            _ => None,
1220        };
1221    }
1222    let value = value.unwrap_or_else(|| {
1223        tracing::warn!(
1224            response_body = %path,
1225            "configured response_body path not found in response; returning null"
1226        );
1227        serde_json::Value::Null
1228    });
1229    serde_json::to_vec(&value)
1230}
1231
1232/// Whether the client negotiated a Server-Sent Events response via `Accept`.
1233///
1234/// Considers every `Accept` header line (a client may send more than one) and
1235/// every comma-separated media range within each. Matches `text/event-stream`
1236/// case-insensitively and honors the quality factor: per RFC 7231 §5.3.1 a
1237/// `q=0` weight means the type is explicitly not acceptable, so it does not
1238/// select the SSE path.
1239fn wants_sse(headers: &HeaderMap) -> bool {
1240    headers
1241        .get_all(axum::http::header::ACCEPT)
1242        .iter()
1243        .filter_map(|v| v.to_str().ok())
1244        .flat_map(|accept| accept.split(','))
1245        .any(accept_range_selects_sse)
1246}
1247
1248/// Whether a single `Accept` media range selects `text/event-stream` with a
1249/// non-zero quality factor.
1250fn accept_range_selects_sse(range: &str) -> bool {
1251    let mut parts = range.split(';');
1252    let media = parts.next().unwrap_or("").trim();
1253    if !media.eq_ignore_ascii_case("text/event-stream") {
1254        return false;
1255    }
1256    // Default weight is 1.0; only an explicit `q=0` (or unparseable-as-positive)
1257    // disqualifies the match. A malformed weight falls back to acceptable.
1258    for param in parts {
1259        let mut kv = param.splitn(2, '=');
1260        if kv.next().unwrap_or("").trim().eq_ignore_ascii_case("q") {
1261            let q: f32 = kv.next().unwrap_or("").trim().parse().unwrap_or(1.0);
1262            return q > 0.0;
1263        }
1264    }
1265    true
1266}
1267
1268/// Who sent a transcoded request: its headers, where it came from and its
1269/// request line.
1270struct Client {
1271    headers: HeaderMap,
1272    origin: Origin,
1273    line: RequestLine,
1274}
1275
1276/// The method and target of a transcoded request as it was received: the
1277/// target before a router the proxy is nested in strips its prefix.
1278struct RequestLine {
1279    method: Method,
1280    uri: Uri,
1281}
1282
1283impl RequestLine {
1284    /// What an upstream in process gets of the request line. An authority-form
1285    /// target has no path to record (RFC 9110 §7.1); it never matches a route,
1286    /// whose paths all start with `/`, and is refused rather than recorded as
1287    /// something it was not.
1288    fn received(self, entry: &RouteEntry) -> Result<ReceivedRequest, Unmappable> {
1289        let path_and_query = self
1290            .uri
1291            .into_parts()
1292            .path_and_query
1293            .ok_or_else(|| Unmappable("the request target has no path".to_string()))?;
1294        Ok(ReceivedRequest::new(
1295            self.method,
1296            path_and_query,
1297            entry.grpc_path.clone(),
1298        ))
1299    }
1300}
1301
1302/// Where a transcoded request came from: the connection, when the server
1303/// recorded one, and the client address the proxy resolved.
1304struct Origin {
1305    connection: Option<ConnectionInfo>,
1306    address: Option<ClientAddress>,
1307}
1308
1309/// The upstream call one request on a transcoded route makes, with whether a
1310/// streamed answer goes out as SSE and its keep-alive; or why the request
1311/// cannot be mapped onto the RPC.
1312fn start<S: TranscodeState>(
1313    proxy_state: S,
1314    client: Client,
1315    path_params: &[(&str, Cow<'_, str>)],
1316    body: Bytes,
1317    entry: &RouteEntry,
1318) -> Result<(Call<S::Upstream>, bool, u64), Unmappable> {
1319    let keep_alive_secs = proxy_state.sse_keep_alive_secs();
1320    let Client {
1321        headers,
1322        origin,
1323        line,
1324    } = client;
1325    // The query is read where it was received, before the request line is
1326    // handed on.
1327    let prepared = prepare(
1328        proxy_state,
1329        &headers,
1330        origin,
1331        path_params,
1332        line.uri.query(),
1333        body,
1334        entry,
1335    )
1336    .and_then(|mut call| {
1337        // Next to the extensions `prepare` sets, for an upstream in process.
1338        call.request.extensions_mut().insert(line.received(entry)?);
1339        Ok(call)
1340    });
1341    let call = prepared?;
1342    Ok((
1343        call,
1344        entry.streaming && wants_sse(&headers),
1345        keep_alive_secs,
1346    ))
1347}
1348
1349/// Why a request ends before the upstream is called: it cannot be mapped onto
1350/// the RPC (`INVALID_ARGUMENT`, 400).
1351struct Unmappable(String);
1352
1353impl Unmappable {
1354    /// The answer, in the error body the upstream's own errors get on the route.
1355    fn into_response(self, entry: &RouteEntry) -> Response {
1356        let status = tonic::Status::invalid_argument(self.0);
1357        error::status_to_response_with_details(&status, entry.error_details.as_deref())
1358    }
1359}
1360
1361/// A call ready to be made: the upstream, the request, and how long the
1362/// upstream may take to answer it.
1363struct Call<U> {
1364    upstream: U,
1365    request: tonic::Request<DynamicMessage>,
1366    deadline: Duration,
1367}
1368
1369impl<U: Upstream> Call<U> {
1370    /// Wait for the upstream to take the call, start it, and wait for its
1371    /// response headers, all within the one deadline: an upstream under
1372    /// backpressure that never frees a slot answers `DEADLINE_EXCEEDED` like
1373    /// one that never answers. Every upstream gets the same deadline here, in
1374    /// process or remote, rather than whatever its transport enforces.
1375    async fn open(
1376        self,
1377        entry: &RouteEntry,
1378    ) -> Result<tonic::Response<tonic::Streaming<DynamicMessage>>, tonic::Status> {
1379        let Self {
1380            upstream,
1381            request,
1382            deadline,
1383        } = self;
1384        let call = async move {
1385            let mut client = Grpc::new(upstream);
1386            if let Err(e) = client.ready().await {
1387                let e: crate::upstream::BoxError = e.into();
1388                return Err(tonic::Status::unavailable(format!(
1389                    "gRPC upstream not ready: {e}"
1390                )));
1391            }
1392            client
1393                .server_streaming(request, entry.grpc_path.clone(), entry.codec())
1394                .await
1395        };
1396        match tokio::time::timeout(deadline, call).await {
1397            Ok(result) => result,
1398            Err(_) => Err(tonic::Status::deadline_exceeded(
1399                "upstream did not answer within the deadline",
1400            )),
1401        }
1402    }
1403}
1404
1405/// Map the request onto the RPC's input message.
1406fn prepare<S: TranscodeState>(
1407    proxy_state: S,
1408    headers: &HeaderMap,
1409    origin: Origin,
1410    path_params: &[(&str, Cow<'_, str>)],
1411    raw_query: Option<&str>,
1412    body: Bytes,
1413    entry: &RouteEntry,
1414) -> Result<Call<S::Upstream>, Unmappable> {
1415    let Origin {
1416        connection,
1417        address,
1418    } = origin;
1419    // A request the proxy resolved carries the client-address headers its
1420    // forwarding policy wrote; one routed here without that resolution
1421    // carries only what its client asserted, which is never forwarded.
1422    let forwarded = match proxy_state.forwarded_header_names() {
1423        Some(names) => metadata::Forwarded::Named(names),
1424        None => metadata::Forwarded::Listed(proxy_state.forwarded_headers()),
1425    };
1426    let request_metadata = metadata::forwarded_metadata(headers, forwarded, address.is_some())
1427        .map_err(|e| Unmappable(e.to_string()))?;
1428    let message =
1429        decode_request(entry, headers, path_params, raw_query, body).map_err(Unmappable)?;
1430    let mut request = tonic::Request::new(message);
1431    *request.metadata_mut() = request_metadata;
1432    // An upstream in process reads the HTTP client's address and TLS
1433    // certificates with `Request::remote_addr` / `peer_certs`, the resolved
1434    // client address as a `ClientAddress` and the request line as a
1435    // `ReceivedRequest`; a remote one never sees request extensions.
1436    if let Some(connection) = connection {
1437        connection.into_tonic_extensions(request.extensions_mut());
1438    }
1439    if let Some(address) = address {
1440        request.extensions_mut().insert(address);
1441    }
1442    // Only the client's own deadline travels upstream: a default one would
1443    // cut a long server stream short on an upstream that applies
1444    // `grpc-timeout` to the whole call.
1445    let deadline = metadata::apply_request_deadline(&mut request, headers)
1446        .map_or(UPSTREAM_DEADLINE, |client| client.min(UPSTREAM_DEADLINE));
1447
1448    Ok(Call {
1449        upstream: proxy_state.into_upstream(),
1450        request,
1451        deadline,
1452    })
1453}
1454
1455/// A successful unary answer with its initial metadata and trailers kept
1456/// apart.
1457struct UnaryAnswer {
1458    initial: MetadataMap,
1459    message: DynamicMessage,
1460    trailers: Option<MetadataMap>,
1461}
1462
1463/// Call a unary RPC. tonic's `Grpc::unary` merges the trailers over the
1464/// initial metadata, so a key sent in both keeps only its trailer value; the
1465/// HTTP response carries both, so the call is made as a one-message stream
1466/// instead, reading exactly what `Grpc::unary` reads.
1467async fn call_unary<U: Upstream>(
1468    call: Call<U>,
1469    entry: &RouteEntry,
1470) -> Result<UnaryAnswer, tonic::Status> {
1471    let (initial, mut stream, _) = call.open(entry).await?.into_parts();
1472    let message = match stream.message().await {
1473        Ok(Some(message)) => message,
1474        Ok(None) => {
1475            let status = tonic::Status::internal("Missing response message.");
1476            return Err(with_initial(status, initial));
1477        }
1478        Err(status) => return Err(with_initial(status, initial)),
1479    };
1480    let trailers = match stream.trailers().await {
1481        Ok(trailers) => trailers,
1482        Err(status) => return Err(with_initial(status, initial)),
1483    };
1484    Ok(UnaryAnswer {
1485        initial,
1486        message,
1487        trailers,
1488    })
1489}
1490
1491/// A failure that came after the upstream's response headers, with their
1492/// metadata put ahead of the failure's own: both belong to the error
1493/// response, as tonic's `Grpc::unary` keeps them.
1494fn with_initial(mut status: tonic::Status, initial: MetadataMap) -> tonic::Status {
1495    let own = std::mem::take(status.metadata_mut()).into_headers();
1496    let mut merged = initial.into_headers();
1497    let mut last: Option<HeaderName> = None;
1498    // A header map yields a name only with the first value of each key.
1499    for (name, value) in own {
1500        if let Some(name) = name {
1501            merged.append(&name, value);
1502            last = Some(name);
1503        } else if let Some(name) = &last {
1504            merged.append(name, value);
1505        }
1506    }
1507    *status.metadata_mut() = MetadataMap::from_headers(merged);
1508    status
1509}
1510
1511/// Serve a unary RPC.
1512async fn unary_call<U: Upstream>(call: Call<U>, entry: &RouteEntry) -> Response {
1513    match call_unary(call, entry).await {
1514        Ok(answer) => unary_success(entry, answer),
1515        Err(status) => upstream_error(status, entry),
1516    }
1517}
1518
1519/// The HTTP response to a successful unary call: the status `x-http-code`
1520/// sets (200 otherwise), the forwarded response metadata, and the body.
1521fn unary_success(entry: &RouteEntry, answer: UnaryAnswer) -> Response {
1522    let mut upstream = UpstreamHeaders::default();
1523    upstream.absorb(answer.initial, &entry.denied_headers);
1524    if let Some(trailers) = answer.trailers {
1525        upstream.absorb(trailers, &entry.denied_headers);
1526    }
1527    let status = match upstream.status() {
1528        Ok(status) => status.unwrap_or(StatusCode::OK),
1529        Err(response::InvalidHttpCode) => {
1530            tracing::error!(
1531                rpc = %entry.grpc_path,
1532                "upstream set x-http-code to something other than one integer in 200-599"
1533            );
1534            return error::malformed_response(entry.error_details.as_deref());
1535        }
1536    };
1537    let (content_type, body) = match &entry.response {
1538        ResponseShape::HttpBody(path) => {
1539            let raw = httpbody::take(answer.message, path);
1540            match content_type_header(&raw.content_type) {
1541                Ok(content_type) => (content_type, Body::from(raw.data)),
1542                Err(()) => return invalid_content_type(entry),
1543            }
1544        }
1545        ResponseShape::Json(response_body) => {
1546            match json_body(&answer.message, response_body.as_deref()) {
1547                Ok(json) => (
1548                    Some(HeaderValue::from_static("application/json")),
1549                    Body::from(json),
1550                ),
1551                Err(e) => {
1552                    tracing::error!("Failed to serialize gRPC response: {e}");
1553                    return error::status_to_response_with_details(
1554                        &tonic::Status::internal("failed to serialize response"),
1555                        entry.error_details.as_deref(),
1556                    );
1557                }
1558            }
1559        }
1560    };
1561    response::build(status, upstream.into_headers(), content_type, body)
1562}
1563
1564/// The `Content-Type` an HttpBody asks for: none when it left the field empty.
1565fn content_type_header(content_type: &str) -> Result<Option<HeaderValue>, ()> {
1566    if content_type.is_empty() {
1567        return Ok(None);
1568    }
1569    HeaderValue::from_str(content_type)
1570        .map(Some)
1571        .map_err(|_| ())
1572}
1573
1574/// The answer to an HttpBody whose content type cannot be a header value.
1575fn invalid_content_type(entry: &RouteEntry) -> Response {
1576    tracing::error!(
1577        rpc = %entry.grpc_path,
1578        "upstream HttpBody content_type is not a valid header value"
1579    );
1580    error::malformed_response(entry.error_details.as_deref())
1581}
1582
1583/// The HTTP response to a failed call, carrying the failure's metadata as
1584/// headers (a trailers-only response, or the response headers and trailers of
1585/// a call that failed after them, see [`with_initial`]) unless its details
1586/// were malformed and the answer is the generic `INTERNAL`, which then carries
1587/// nothing of the upstream's answer.
1588fn upstream_error(mut status: tonic::Status, entry: &RouteEntry) -> Response {
1589    let (response, faithful) = error::render_response(&status, entry.error_details.as_deref());
1590    let metadata = std::mem::take(status.metadata_mut());
1591    if !faithful || metadata.is_empty() {
1592        return response;
1593    }
1594    let mut upstream = UpstreamHeaders::default();
1595    upstream.absorb(metadata, &entry.denied_headers);
1596    response::with_upstream_headers(response, upstream.into_headers())
1597}
1598
1599/// Serve a server-streaming RPC.
1600///
1601/// A JSON stream is Server-Sent Events when the client sends
1602/// `Accept: text/event-stream`, otherwise newline-delimited JSON (NDJSON); a
1603/// gRPC error mid-stream is delivered as an explicit terminal frame before the
1604/// stream is closed cleanly, rather than truncating the HTTP body. An HttpBody
1605/// stream is the concatenated `data` of its messages. The upstream's initial
1606/// metadata becomes response headers; trailers arrive after the headers are
1607/// sent and are not forwarded.
1608async fn streaming_call<U: Upstream>(
1609    call: Call<U>,
1610    entry: RouteRef,
1611    use_sse: bool,
1612    keep_alive_secs: u64,
1613) -> Response {
1614    let response = match call.open(&entry).await {
1615        Ok(response) => response,
1616        // A trailers-only rejection, or no answer within the deadline.
1617        Err(status) => return upstream_error(status, &entry),
1618    };
1619    let (initial, stream, _) = response.into_parts();
1620    if matches!(entry.response, ResponseShape::HttpBody(_)) {
1621        return http_body_stream(stream, entry, initial).await;
1622    }
1623    let mut upstream = UpstreamHeaders::default();
1624    upstream.absorb(initial, &entry.denied_headers);
1625    let headers = upstream.into_headers();
1626    // Once the upstream accepted the call, the response starts at once rather
1627    // than waiting for the first item, so headers and SSE keep-alives are not
1628    // held back; an error that comes before the first message is a terminal
1629    // frame. The terminal frame renders like the unary error body. The
1630    // closure takes over this request's route handle, so the stream keeps it
1631    // alive without another refcount.
1632    let envelope = entry.ndjson_envelope;
1633    let render_error =
1634        move |status: &tonic::Status| error::error_body(status, entry.error_details.as_deref());
1635    let response = if use_sse {
1636        sse_response(stream, render_error, keep_alive_secs)
1637    } else {
1638        ndjson_response(stream, render_error, envelope)
1639    };
1640    response::with_upstream_headers(response, headers)
1641}
1642
1643/// A server-streaming HttpBody response: `Content-Type` from the first
1644/// message, so the headers wait for it, then every message's `data` as it
1645/// arrives. An error before the first message is an ordinary error response,
1646/// with the initial metadata the upstream sent before it; after the first
1647/// message, the raw body has no in-band error frame, so the body is aborted
1648/// and the client sees a truncated transfer instead of a clean end.
1649async fn http_body_stream(
1650    mut stream: tonic::Streaming<DynamicMessage>,
1651    entry: RouteRef,
1652    initial: MetadataMap,
1653) -> Response {
1654    let first = match stream.message().await {
1655        Ok(Some(message)) => entry.raw_body(message).unwrap_or_default(),
1656        Ok(None) => httpbody::RawBody::default(),
1657        Err(status) => return upstream_error(with_initial(status, initial), &entry),
1658    };
1659    let mut upstream = UpstreamHeaders::default();
1660    upstream.absorb(initial, &entry.denied_headers);
1661    let headers = upstream.into_headers();
1662    let content_type = match content_type_header(&first.content_type) {
1663        Ok(content_type) => content_type,
1664        Err(()) => return invalid_content_type(&entry),
1665    };
1666    let rest = stream.map(move |item| match item {
1667        Ok(message) => Ok(entry.raw_body(message).unwrap_or_default().data),
1668        Err(status) => {
1669            tracing::error!(
1670                rpc = %entry.grpc_path,
1671                code = ?status.code(),
1672                "server-streaming HttpBody failed after the response started; aborting the body"
1673            );
1674            Err(std::io::Error::other(status))
1675        }
1676    });
1677    let chunks = futures::stream::once(futures::future::ready(Ok(first.data)))
1678        .chain(rest)
1679        .try_filter(|chunk| futures::future::ready(!chunk.is_empty()));
1680    response::build(
1681        StatusCode::OK,
1682        headers,
1683        content_type,
1684        Body::from_stream(chunks),
1685    )
1686}
1687
1688/// One frame of a streaming response: a serialized message, or the error body
1689/// (see [`error::error_body`]) that ends the stream.
1690///
1691/// `Error` is terminal: [`json_frames`] stops the stream right after yielding
1692/// it, so an error frame is always the last thing a client sees regardless of
1693/// whether it came from a gRPC status or a serialization failure. It stays a
1694/// JSON value so each format can add its own framing before serializing it.
1695enum StreamFrame {
1696    Data(String),
1697    Error(serde_json::Value),
1698}
1699
1700/// Type URL marking the terminal error line of an unenveloped NDJSON stream. A
1701/// data line is the ProtoJSON of a response message, which carries a top-level
1702/// `@type` only when the RPC streams `google.protobuf.Any`; `Struct`, `Value`
1703/// and `ListValue` can carry any key at all. For those RPCs no in-band marker
1704/// is collision-free, which is what the opt-in envelope
1705/// ([`TranscodeOptions::with_ndjson_envelope`]) is for; the unenveloped shape
1706/// stays the default so existing NDJSON readers keep working.
1707const STATUS_TYPE_URL: &str = "type.googleapis.com/google.rpc.Status";
1708
1709/// Turn a gRPC message stream into a stream of serialized JSON frames, stopping
1710/// after the first error so error frames are unambiguously terminal.
1711///
1712/// Both a gRPC `Status` (rendered by `render_error`) and a per-message
1713/// serialization failure become a terminal [`StreamFrame::Error`]; downstream
1714/// messages the upstream might still emit are dropped rather than streamed past
1715/// the error. The terminal frame drops the upstream along with the stream
1716/// state, so the body ends at once instead of polling an upstream that may
1717/// stay open.
1718fn json_frames<St, R>(
1719    stream: St,
1720    render_error: R,
1721) -> impl futures::Stream<Item = StreamFrame> + Send + 'static
1722where
1723    St: futures::Stream<Item = Result<DynamicMessage, tonic::Status>> + Send + Unpin + 'static,
1724    R: Fn(&tonic::Status) -> serde_json::Value + Send + 'static,
1725{
1726    let state = Some((stream, render_error, response_serialize_options()));
1727    futures::stream::unfold(state, |state| async move {
1728        let (mut stream, render_error, opts) = state?;
1729        let error = match stream.next().await? {
1730            Ok(msg) => match message_to_json_string(&msg, &opts) {
1731                Ok(s) => return Some((StreamFrame::Data(s), Some((stream, render_error, opts)))),
1732                Err(e) => tonic::Status::internal(format!("serialization error: {e}")),
1733            },
1734            Err(status) => status,
1735        };
1736        Some((StreamFrame::Error(render_error(&error)), None))
1737    })
1738}
1739
1740/// Build an NDJSON (`application/x-ndjson`) streaming response.
1741///
1742/// With `envelope`, every line is wrapped: `{"result": <message>}` for data and
1743/// `{"error": <error body>}` for the terminal error. Without it, a data line is
1744/// the bare message and the error line is the error body plus an
1745/// `@type: google.rpc.Status` marker (see [`STATUS_TYPE_URL`]).
1746fn ndjson_response<St, R>(stream: St, render_error: R, envelope: bool) -> Response
1747where
1748    St: futures::Stream<Item = Result<DynamicMessage, tonic::Status>> + Send + Unpin + 'static,
1749    R: Fn(&tonic::Status) -> serde_json::Value + Send + 'static,
1750{
1751    let byte_stream = json_frames(stream, render_error).map(move |frame| {
1752        let mut line = match frame {
1753            // The message is already serialized; wrap the text instead of
1754            // parsing it back into a value.
1755            StreamFrame::Data(s) if envelope => {
1756                let mut wrapped = String::with_capacity(s.len() + 12);
1757                wrapped.push_str("{\"result\":");
1758                wrapped.push_str(&s);
1759                wrapped.push('}');
1760                wrapped
1761            }
1762            StreamFrame::Data(s) => s,
1763            StreamFrame::Error(body) if envelope => {
1764                serde_json::json!({ "error": body }).to_string()
1765            }
1766            StreamFrame::Error(mut body) => {
1767                if let Some(fields) = body.as_object_mut() {
1768                    fields.insert("@type".into(), STATUS_TYPE_URL.into());
1769                }
1770                body.to_string()
1771            }
1772        };
1773        line.push('\n');
1774        Ok::<Bytes, std::io::Error>(Bytes::from(line))
1775    });
1776
1777    let body = Body::from_stream(byte_stream);
1778    // Body framing (chunked on HTTP/1.1, DATA frames on HTTP/2) is chosen by
1779    // hyper from the protocol version; setting transfer-encoding by hand would
1780    // be redundant on HTTP/1.1 and illegal on HTTP/2.
1781    Response::builder()
1782        .status(StatusCode::OK)
1783        .header(CONTENT_TYPE, "application/x-ndjson")
1784        .body(body)
1785        .unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response())
1786}
1787
1788/// Build a Server-Sent Events (`text/event-stream`) streaming response.
1789fn sse_response<St, R>(stream: St, render_error: R, keep_alive_secs: u64) -> Response
1790where
1791    St: futures::Stream<Item = Result<DynamicMessage, tonic::Status>> + Send + Unpin + 'static,
1792    R: Fn(&tonic::Status) -> serde_json::Value + Send + 'static,
1793{
1794    // Terminal errors use the `stream-error` event type, not the reserved
1795    // `error` type that the browser EventSource dispatches for transport
1796    // failures — clients listen for it via addEventListener("stream-error").
1797    let event_stream = json_frames(stream, render_error).map(|frame| {
1798        let event = match frame {
1799            StreamFrame::Data(s) => Event::default().data(s),
1800            StreamFrame::Error(body) => Event::default()
1801                .event("stream-error")
1802                .data(body.to_string()),
1803        };
1804        Ok::<Event, std::convert::Infallible>(event)
1805    });
1806
1807    Sse::new(event_stream)
1808        .keep_alive(KeepAlive::new().interval(std::time::Duration::from_secs(keep_alive_secs)))
1809        .into_response()
1810}
1811
1812/// Request body mapping of the raw-body routes: nothing parsed.
1813static NO_PARSED_BODY: request::BodyMapping = request::BodyMapping::None;
1814
1815/// Map the HTTP request onto the RPC's input message: path parameters, query
1816/// parameters and the route's `body` rule, or the raw body for an HttpBody.
1817/// Unary and server-streaming routes share it, so both bind a request the same
1818/// way. The error is the message of the 400 the caller answers with, before
1819/// the upstream is called.
1820fn decode_request(
1821    entry: &RouteEntry,
1822    headers: &HeaderMap,
1823    path_params: &[(&str, Cow<'_, str>)],
1824    raw_query: Option<&str>,
1825    body_bytes: Bytes,
1826) -> Result<DynamicMessage, String> {
1827    // A raw field keeps its `body` mapping with no body read, so query binding
1828    // leaves that field to the raw body. An HttpBody input with `body: "*"`
1829    // takes every field from the raw body, so its query binds nothing
1830    // (google/api/http.proto: with `*` there are no HTTP parameters).
1831    let (mapping, body, raw_query) = match &entry.request_body {
1832        RequestBody::Parsed(mapping @ request::BodyMapping::None) => {
1833            (mapping, request::Body::Absent, raw_query)
1834        }
1835        RequestBody::Parsed(mapping) => (
1836            mapping,
1837            request::Body::new(body::content_type(headers), &body_bytes),
1838            raw_query,
1839        ),
1840        RequestBody::RawRoot => (&NO_PARSED_BODY, request::Body::Absent, None),
1841        RequestBody::RawField { mapping, .. } => (mapping, request::Body::Absent, raw_query),
1842    };
1843
1844    let mut message =
1845        request::build_message(&entry.method.input(), mapping, body, path_params, raw_query)?;
1846    match &entry.request_body {
1847        RequestBody::Parsed(_) => {}
1848        RequestBody::RawRoot => {
1849            httpbody::fill(&mut message, request_content_type(headers)?, body_bytes);
1850        }
1851        RequestBody::RawField {
1852            field, http_body, ..
1853        } => {
1854            let mut inner = DynamicMessage::new(http_body.clone());
1855            httpbody::fill(&mut inner, request_content_type(headers)?, body_bytes);
1856            message.set_field(field, prost_reflect::Value::Message(inner));
1857        }
1858    }
1859    Ok(message)
1860}
1861
1862/// The request's full `Content-Type` value (parameters included) for an
1863/// HttpBody, empty when absent. `HttpBody.content_type` is a proto string, so
1864/// a value that is not visible ASCII is rejected rather than altered.
1865fn request_content_type(headers: &HeaderMap) -> Result<String, String> {
1866    match headers.get(CONTENT_TYPE) {
1867        None => Ok(String::new()),
1868        Some(value) => value
1869            .to_str()
1870            .map(str::to_owned)
1871            .map_err(|_| "request Content-Type is not a visible ASCII string".to_string()),
1872    }
1873}
1874
1875/// Extract the route entries of every HTTP binding of every selected unary
1876/// and server-streaming RPC. Client-streaming RPCs have no HTTP mapping.
1877fn extract_routes(pool: &DescriptorPool, selection: &RpcSelection) -> Vec<RouteEntry> {
1878    let http_ext = match pool.get_extension_by_name("google.api.http") {
1879        Some(ext) => ext,
1880        None => {
1881            tracing::warn!("google.api.http extension not found in descriptor pool");
1882            return Vec::new();
1883        }
1884    };
1885
1886    let mut entries = Vec::new();
1887
1888    for service in pool.services() {
1889        for method in service.methods() {
1890            if method.is_client_streaming() || !selection.selects(&method) {
1891                continue;
1892            }
1893
1894            let grpc_path = format!("/{}/{}", service.full_name(), method.name());
1895            let grpc_path: axum::http::uri::PathAndQuery = match grpc_path.parse() {
1896                Ok(p) => p,
1897                Err(e) => {
1898                    tracing::error!("skipping route with invalid gRPC path '{grpc_path}': {e}");
1899                    continue;
1900                }
1901            };
1902
1903            let input = method.input();
1904            let output = method.output();
1905            let streaming = method.is_server_streaming();
1906            let response_has_required = codec::has_required_fields(&output);
1907            for binding in rule::http_bindings(&method, &http_ext) {
1908                if streaming {
1909                    tracing::info!(
1910                        "Registering streaming route: {} {} → {}",
1911                        binding.method.as_str(),
1912                        binding.path,
1913                        grpc_path
1914                    );
1915                }
1916                entries.push(RouteEntry {
1917                    http_path: binding.path,
1918                    http_method: binding.method,
1919                    grpc_path: grpc_path.clone(),
1920                    method: method.clone(),
1921                    streaming,
1922                    request_body: request_body(&input, binding.body),
1923                    response: response_shape(&output, binding.response_body),
1924                    // Decided per mounted path in `routes_with_options`.
1925                    error_details: None,
1926                    ndjson_envelope: false,
1927                    response_has_required,
1928                    denied_headers: Arc::default(),
1929                });
1930            }
1931        }
1932    }
1933
1934    entries
1935}
1936
1937/// How a binding's `body` rule reaches `input`: raw into an HttpBody (the
1938/// input itself with `body: "*"`, or the HttpBody field `body` names), parsed
1939/// otherwise. `body` names a field of the input itself, never a dotted path:
1940/// `google/api/http.proto` requires "the referred field must not be a repeated
1941/// field and must be present at the top-level of request message type".
1942fn request_body(input: &MessageDescriptor, mapping: request::BodyMapping) -> RequestBody {
1943    let raw_field = match &mapping {
1944        request::BodyMapping::Root if httpbody::is_http_body(input) => return RequestBody::RawRoot,
1945        request::BodyMapping::Field(name) => httpbody::http_body_field(input, name),
1946        _ => None,
1947    };
1948    match raw_field {
1949        Some((field, http_body)) => RequestBody::RawField {
1950            mapping,
1951            field,
1952            http_body,
1953        },
1954        None => RequestBody::Parsed(mapping),
1955    }
1956}
1957
1958/// What a binding answers with: the raw body of an HttpBody (the output itself,
1959/// or the HttpBody `response_body` names), JSON otherwise.
1960fn response_shape(output: &MessageDescriptor, response_body: Option<String>) -> ResponseShape {
1961    match response_body {
1962        None if httpbody::is_http_body(output) => ResponseShape::HttpBody(Vec::new()),
1963        None => ResponseShape::Json(None),
1964        Some(path) => match httpbody::http_body_path(output, &path) {
1965            Some(fields) => ResponseShape::HttpBody(fields),
1966            None => ResponseShape::Json(Some(path)),
1967        },
1968    }
1969}
1970
1971#[cfg(test)]
1972mod tests;