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::extract::DefaultBodyLimit;
29 use axum::http::header::HeaderName;
30 use axum::middleware::{self, Next};
31 use axum::routing::{get, post};
32 use tower::util::option_layer;
33 use tower::ServiceBuilder;
34 use tower_http::request_id::{MakeRequestUuid, PropagateRequestIdLayer, SetRequestIdLayer};
35 use tower_http::trace::TraceLayer;
36
37 use crate::cache::cache_control_layer;
38 use crate::handlers::{
39 conversion::{options_conversion, post_conversion},
40 fallback::{conversion_method_not_allowed, health_method_not_allowed, not_found},
41 health::{get_health, head_health, options_health},
42 };
43 use crate::telemetry;
44
45 let attach_sentry_tags = |request: Request<Body>, next: Next| async move {
46 if sentry::Hub::current().client().is_some() {
47 let request_id = request
48 .extensions()
49 .get::<RequestId>()
50 .and_then(|req_id| req_id.header_value().to_str().ok())
51 .unwrap_or_default()
52 .to_owned();
53 let trace_id = request
54 .headers()
55 .get("x-trace-id")
56 .and_then(|head| head.to_str().ok())
57 .unwrap_or_default()
58 .to_owned();
59 sentry::configure_scope(|scope| {
60 if !request_id.is_empty() {
61 scope.set_tag("request_id", &request_id);
62 }
63 if !trace_id.is_empty() {
64 scope.set_tag("trace_id", &trace_id);
65 }
66 });
67 }
68 next.run(request).await
69 };
70
71 let x_request_id = HeaderName::from_static("x-request-id");
72 let x_trace_id = HeaderName::from_static("x-trace-id");
73
74 let conversion_route = post(post_conversion)
75 .options(options_conversion)
76 .fallback(conversion_method_not_allowed);
77
78 let health_route = get(get_health)
79 .head(head_health)
80 .options(options_health)
81 .fallback(health_method_not_allowed);
82
83 Router::new()
84 .route("/conversion", conversion_route)
85 .route("/health", health_route)
86 .fallback(not_found)
87 .layer(
88 ServiceBuilder::new()
89 .layer(DefaultBodyLimit::disable())
95 .layer(SetRequestIdLayer::new(
96 x_request_id.clone(),
97 MakeRequestUuid,
98 ))
99 .layer(SetRequestIdLayer::new(x_trace_id.clone(), EchoOnly))
100 .layer(option_layer(telemetry::tower_new_layer()))
101 .layer(option_layer(telemetry::tower_http_layer()))
102 .layer(middleware::from_fn(attach_sentry_tags))
103 .layer(TraceLayer::new_for_http())
104 .layer(middleware::from_fn(record_http_metrics))
105 .layer(PropagateRequestIdLayer::new(x_request_id))
106 .layer(PropagateRequestIdLayer::new(x_trace_id))
107 .layer(cache_control_layer()),
108 )
109}
110
111#[inline]
125pub fn router_with_metrics(handle: PrometheusHandle) -> Router {
126 router().route("/metrics", axum::routing::get(metrics_handler(handle)))
127}