1mod config;
2mod error;
3mod handler;
4
5pub use config::SentryTunnelConfig;
6pub use error::SentryTunnelError;
7pub use handler::handle_sentry_tunnel_inner;
8
9#[cfg(feature = "extension")]
10use axum::{Router, body::Bytes, extract::State, http::StatusCode, routing::post};
11#[cfg(feature = "extension")]
12use std::sync::Arc;
13
14#[cfg(feature = "extension")]
15#[cfg_attr(feature = "utoipa", utoipa::path(
16 post,
17 path = "/tunnel",
18 request_body = Vec<u8>,
19 responses(
20 (status = 200, description = "Successfully tunneled to Sentry"),
21 (status = 400, description = "Bad request - invalid envelope or DSN"),
22 (status = 500, description = "Internal server error - failed to tunnel")
23 ),
24 tag = "sentry"
25))]
26pub async fn sentry_tunnel_handler(
28 State(config): State<Arc<SentryTunnelConfig>>,
29 body: Bytes,
30) -> Result<StatusCode, SentryTunnelError> {
31 handler::handle_sentry_tunnel(config, body).await
32}
33
34#[cfg(feature = "extension")]
35pub trait SentryTunnelExt {
37 fn sentry_tunnel(self, config: SentryTunnelConfig) -> Self;
39}
40
41#[cfg(feature = "extension")]
42impl<S> SentryTunnelExt for Router<S>
43where
44 S: Clone + Send + Sync + 'static,
45{
46 fn sentry_tunnel(self, config: SentryTunnelConfig) -> Self {
47 let path = config.path.clone();
48 let config = Arc::new(config);
49
50 let tunnel_router = Router::new()
51 .route(&path, post(sentry_tunnel_handler))
52 .with_state(config);
53
54 self.merge(tunnel_router)
55 }
56}
57
58#[cfg(feature = "standalone")]
59pub fn create_sentry_tunnel_service(config: SentryTunnelConfig) -> Router {
61 Router::new().sentry_tunnel(config)
62}
63
64#[cfg(feature = "utoipa")]
65impl<S> SentryTunnelExt for utoipa_axum::router::OpenApiRouter<S>
67where
68 S: Clone + Send + Sync + 'static,
69{
70 fn sentry_tunnel(self, config: SentryTunnelConfig) -> Self {
71 let path = config.path.clone();
72 let config_arc = Arc::new(config);
73
74 self.route(&path, post(sentry_tunnel_handler).with_state(config_arc))
77 }
78}
79
80pub struct SentryTunnelBuilder {
82 config: SentryTunnelConfig,
83}
84
85impl SentryTunnelBuilder {
86 pub fn new(sentry_host: impl Into<String>) -> Self {
88 Self {
89 config: SentryTunnelConfig::new(sentry_host, vec![]),
90 }
91 }
92
93 pub fn allow_project_id(mut self, project_id: impl Into<String>) -> Self {
95 self.config.allowed_project_ids.push(project_id.into());
96 self
97 }
98
99 pub fn allow_project_ids<I, S>(mut self, project_ids: I) -> Self
101 where
102 I: IntoIterator<Item = S>,
103 S: Into<String>,
104 {
105 self.config
106 .allowed_project_ids
107 .extend(project_ids.into_iter().map(|s| s.into()));
108 self
109 }
110
111 pub fn path(mut self, path: impl Into<String>) -> Self {
113 self.config.path = path.into();
114 self
115 }
116
117 pub fn timeout_secs(mut self, timeout: u64) -> Self {
119 self.config.timeout_secs = timeout;
120 self
121 }
122
123 pub fn build(self) -> SentryTunnelConfig {
125 self.config
126 }
127}