1pub 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
58pub const UPSTREAM_DEADLINE: Duration = Duration::from_secs(5);
61
62pub trait TranscodeState: Clone + Send + Sync + 'static {
68 type Upstream: Upstream;
70 fn into_upstream(self) -> Self::Upstream;
72 fn forwarded_headers(&self) -> &[String];
74 fn forwarded_header_names(&self) -> Option<&[HeaderName]> {
78 None
79 }
80 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
100type PathParams<'a> = Vec<(&'a str, Cow<'a, str>)>;
104
105#[derive(Debug, Clone)]
107enum RequestBody {
108 Parsed(request::BodyMapping),
110 RawRoot,
112 RawField {
115 mapping: request::BodyMapping,
117 field: FieldDescriptor,
118 http_body: MessageDescriptor,
120 },
121}
122
123#[derive(Debug, Clone)]
125enum ResponseShape {
126 Json(Option<String>),
128 HttpBody(Vec<FieldDescriptor>),
131}
132
133#[derive(Debug, Clone)]
135struct RouteEntry {
136 http_path: String,
138 http_method: RouteMethod,
140 grpc_path: axum::http::uri::PathAndQuery,
143 method: MethodDescriptor,
145 streaming: bool,
147 request_body: RequestBody,
149 response: ResponseShape,
151 error_details: Option<Arc<StatusDetails>>,
154 ndjson_envelope: bool,
156 response_has_required: bool,
159 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 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#[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 pub fn with_selection(mut self, selection: RpcSelection) -> Self {
205 self.selection = selection;
206 self
207 }
208
209 pub fn with_error_details(mut self, policy: ErrorDetailsPolicy) -> Self {
212 self.error_details = policy;
213 self
214 }
215
216 pub fn with_ndjson_envelope(mut self, enabled: bool) -> Self {
224 self.ndjson_envelope = enabled;
225 self
226 }
227
228 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
242pub fn routes<S: TranscodeState>(pool: &DescriptorPool, aliases: &[AliasConfig]) -> Router<S> {
249 routes_with_options(pool, aliases, &TranscodeOptions::default())
250}
251
252pub 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
276pub(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 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 if mounted.binds_head() {
308 route = route.head(handler);
309 }
310 router = router.route(&mounted.path, route);
311 }
312 (router, Some(choices))
313}
314
315pub(crate) struct Choices {
320 routes: Arc<Routes>,
321 ranked: Option<Arc<table::Ranked>>,
323}
324
325impl Choices {
326 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 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 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#[derive(Clone)]
372pub(crate) struct Direct {
373 ranking: Arc<Ranking>,
374 service: BoxedService,
376}
377
378struct Ranking {
379 ranked: Arc<table::Ranked>,
380 routes: Arc<Routes>,
381}
382
383impl Direct {
384 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 pub(crate) fn service(&self) -> &BoxedService {
401 &self.service
402 }
403}
404
405#[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
434fn 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 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#[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 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 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#[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 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 #[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#[derive(Clone)]
594struct Chosen(Choice);
595
596fn local_path<'p>(matched: &'p str, nested: Option<&str>) -> Option<&'p str> {
599 let Some(nested) = nested else {
600 return Some(matched);
601 };
602 let local = matched.strip_prefix(nested.trim_end_matches('/'))?;
605 Some(if local.is_empty() { "/" } else { local })
606}
607
608fn 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 let mut status_details: [Option<Arc<StatusDetails>>; 2] = [None, None];
622 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
668fn 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
686pub(crate) fn mounted_shapes(
689 pool: &DescriptorPool,
690 aliases: &[AliasConfig],
691 selection: &RpcSelection,
692) -> std::collections::HashSet<String> {
693 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
706async 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 Choice::NotFound => return StatusCode::NOT_FOUND.into_response(),
727 };
728 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 let local;
741 if table != matched || !multi_segment.is_empty() {
742 local = parts.uri.clone();
747 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 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
783async 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 let local = parts.uri.clone();
797 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 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 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
862enum Begun<U> {
864 Call {
865 call: Call<U>,
866 sse: bool,
867 keep_alive_secs: u64,
868 },
869 Answered(Response),
870}
871
872async 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 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
912async 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
940fn 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#[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
990struct 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
1008fn 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
1048pub 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 let path = bindings[0].mount.axum.as_str();
1074 let listed = |method: &str| (method.to_owned(), path.to_owned());
1075 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
1104fn 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
1132pub(crate) struct BoundMethods {
1136 pub(crate) methods: Vec<Method>,
1138 pub(crate) every: bool,
1140}
1141
1142pub(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
1168fn response_serialize_options() -> SerializeOptions {
1171 SerializeOptions::new()
1172 .skip_default_fields(false)
1173 .stringify_64_bit_integers(true)
1174}
1175
1176fn 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
1186fn 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 Ok(unsafe { String::from_utf8_unchecked(buf) })
1192}
1193
1194fn 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 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
1232fn 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
1248fn 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 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
1268struct Client {
1271 headers: HeaderMap,
1272 origin: Origin,
1273 line: RequestLine,
1274}
1275
1276struct RequestLine {
1279 method: Method,
1280 uri: Uri,
1281}
1282
1283impl RequestLine {
1284 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
1302struct Origin {
1305 connection: Option<ConnectionInfo>,
1306 address: Option<ClientAddress>,
1307}
1308
1309fn 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 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 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
1349struct Unmappable(String);
1352
1353impl Unmappable {
1354 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
1361struct Call<U> {
1364 upstream: U,
1365 request: tonic::Request<DynamicMessage>,
1366 deadline: Duration,
1367}
1368
1369impl<U: Upstream> Call<U> {
1370 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
1405fn 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 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 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 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
1455struct UnaryAnswer {
1458 initial: MetadataMap,
1459 message: DynamicMessage,
1460 trailers: Option<MetadataMap>,
1461}
1462
1463async 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
1491fn 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 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
1511async 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
1519fn 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
1564fn 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
1574fn 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
1583fn 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
1599async 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 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 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
1643async 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
1688enum StreamFrame {
1696 Data(String),
1697 Error(serde_json::Value),
1698}
1699
1700const STATUS_TYPE_URL: &str = "type.googleapis.com/google.rpc.Status";
1708
1709fn 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
1740fn 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 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 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
1788fn 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 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
1812static NO_PARSED_BODY: request::BodyMapping = request::BodyMapping::None;
1814
1815fn 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 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
1862fn 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
1875fn 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 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
1937fn 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
1958fn 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;