sentry_tunnel/
lib.rs

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))]
26/// Create Sentry tunnel route handler
27pub 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")]
35/// Extension methods for Router
36pub trait SentryTunnelExt {
37    /// Add Sentry tunnel route to Router
38    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")]
59/// Create a standalone Sentry tunnel service
60pub fn create_sentry_tunnel_service(config: SentryTunnelConfig) -> Router {
61    Router::new().sentry_tunnel(config)
62}
63
64#[cfg(feature = "utoipa")]
65/// Implementation of SentryTunnelExt for utoipa-axum OpenApiRouter
66impl<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        // Add the route using the standard router method
75        // The utoipa::path annotation is already applied to the handler
76        self.route(&path, post(sentry_tunnel_handler).with_state(config_arc))
77    }
78}
79
80/// Builder pattern for creating configuration
81pub struct SentryTunnelBuilder {
82    config: SentryTunnelConfig,
83}
84
85impl SentryTunnelBuilder {
86    /// Create a new builder
87    pub fn new(sentry_host: impl Into<String>) -> Self {
88        Self {
89            config: SentryTunnelConfig::new(sentry_host, vec![]),
90        }
91    }
92
93    /// Add an allowed project ID
94    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    /// Add multiple allowed project IDs
100    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    /// Set the path
112    pub fn path(mut self, path: impl Into<String>) -> Self {
113        self.config.path = path.into();
114        self
115    }
116
117    /// Set the timeout
118    pub fn timeout_secs(mut self, timeout: u64) -> Self {
119        self.config.timeout_secs = timeout;
120        self
121    }
122
123    /// Build the configuration
124    pub fn build(self) -> SentryTunnelConfig {
125        self.config
126    }
127}