1use axum::{http::Request, Router};
4use metrics_exporter_prometheus::PrometheusHandle;
5use tower_http::request_id::{MakeRequestId, RequestId};
6
7use crate::metrics::{metrics_handler, middleware::record_http_metrics};
8
9#[derive(Clone, Copy)]
15struct EchoOnly;
16
17impl MakeRequestId for EchoOnly {
18 #[inline]
19 fn make_request_id<B>(&mut self, _request: &Request<B>) -> Option<RequestId> {
20 None
21 }
22}
23
24#[inline]
26pub fn router() -> Router {
27 use axum::body::Body;
28 use axum::http::header::HeaderName;
29 use axum::middleware::{self, Next};
30 use axum::routing::{get, post};
31 use tower::util::option_layer;
32 use tower::ServiceBuilder;
33 use tower_http::request_id::{MakeRequestUuid, PropagateRequestIdLayer, SetRequestIdLayer};
34 use tower_http::trace::TraceLayer;
35
36 use crate::cache::cache_control_layer;
37 use crate::handlers::{
38 conversion::{options_conversion, post_conversion},
39 fallback::{conversion_method_not_allowed, health_method_not_allowed, not_found},
40 health::{get_health, head_health, options_health},
41 };
42 use crate::telemetry;
43
44 let attach_sentry_tags = |request: Request<Body>, next: Next| async move {
45 if sentry::Hub::current().client().is_some() {
46 let request_id = request
47 .extensions()
48 .get::<RequestId>()
49 .and_then(|req_id| req_id.header_value().to_str().ok())
50 .unwrap_or_default()
51 .to_owned();
52 let trace_id = request
53 .headers()
54 .get("x-trace-id")
55 .and_then(|head| head.to_str().ok())
56 .unwrap_or_default()
57 .to_owned();
58 sentry::configure_scope(|scope| {
59 if !request_id.is_empty() {
60 scope.set_tag("request_id", &request_id);
61 }
62 if !trace_id.is_empty() {
63 scope.set_tag("trace_id", &trace_id);
64 }
65 });
66 }
67 next.run(request).await
68 };
69
70 let x_request_id = HeaderName::from_static("x-request-id");
71 let x_trace_id = HeaderName::from_static("x-trace-id");
72
73 let conversion_route = post(post_conversion)
74 .options(options_conversion)
75 .fallback(conversion_method_not_allowed);
76
77 let health_route = get(get_health)
78 .head(head_health)
79 .options(options_health)
80 .fallback(health_method_not_allowed);
81
82 Router::new()
83 .route("/conversion", conversion_route)
84 .route("/health", health_route)
85 .fallback(not_found)
86 .layer(
87 ServiceBuilder::new()
88 .layer(SetRequestIdLayer::new(
89 x_request_id.clone(),
90 MakeRequestUuid,
91 ))
92 .layer(SetRequestIdLayer::new(x_trace_id.clone(), EchoOnly))
93 .layer(option_layer(telemetry::tower_new_layer()))
94 .layer(option_layer(telemetry::tower_http_layer()))
95 .layer(middleware::from_fn(attach_sentry_tags))
96 .layer(TraceLayer::new_for_http())
97 .layer(middleware::from_fn(record_http_metrics))
98 .layer(PropagateRequestIdLayer::new(x_request_id))
99 .layer(PropagateRequestIdLayer::new(x_trace_id))
100 .layer(cache_control_layer()),
101 )
102}
103
104#[inline]
118pub fn router_with_metrics(handle: PrometheusHandle) -> Router {
119 router().route("/metrics", axum::routing::get(metrics_handler(handle)))
120}