relay_filter/
interface.rs

1//! This module contains the trait for items that can be filtered by Inbound Filters, plus
2//! the implementation for [`Event`].
3use url::Url;
4
5use relay_event_schema::protocol::{
6    Csp, Event, EventType, Exception, LogEntry, OurLog, Replay, SessionAggregates, SessionUpdate,
7    Span, Values,
8};
9
10/// A user agent returned from [`Filterable::user_agent`].
11#[derive(Clone, Debug, Default)]
12pub struct UserAgent<'a> {
13    /// The raw, unparsed user agent string, if available.
14    pub raw: Option<&'a str>,
15    /// A already fully parsed user agent, if available.
16    pub parsed: Option<relay_ua::UserAgent<'a>>,
17}
18
19impl<'a> UserAgent<'a> {
20    /// Parses the raw user agent, if it is not already parsed and returns it.
21    pub fn parsed(&mut self) -> Option<&'_ relay_ua::UserAgent<'a>> {
22        match &mut self.parsed {
23            Some(parsed) => Some(parsed),
24            parsed @ None => {
25                *parsed = Some(relay_ua::parse_user_agent(self.raw?));
26                parsed.as_ref()
27            }
28        }
29    }
30}
31
32/// A data item to which filters can be applied.
33pub trait Filterable {
34    /// The CSP report contained in the item. Only for CSP reports.
35    fn csp(&self) -> Option<&Csp> {
36        None
37    }
38
39    /// The exception values of the item. Only for error events.
40    fn exceptions(&self) -> Option<&Values<Exception>> {
41        None
42    }
43
44    /// The IP address of the client that sent the data.
45    fn ip_addr(&self) -> Option<&str> {
46        None
47    }
48
49    /// The logentry message. Only for error events.
50    fn logentry(&self) -> Option<&LogEntry> {
51        None
52    }
53
54    /// The release string of the data item.
55    fn release(&self) -> Option<&str> {
56        None
57    }
58
59    /// The transaction name. Only for transaction events.
60    fn transaction(&self) -> Option<&str> {
61        None
62    }
63
64    /// The URL from which the request originates. Used for localhost filtering.
65    fn url(&self) -> Option<Url> {
66        None
67    }
68
69    /// The user agent of the client that sent the data.
70    fn user_agent(&self) -> UserAgent<'_> {
71        Default::default()
72    }
73
74    /// Retrieves a request headers from the item.
75    ///
76    /// For example this is used for localhost filtering by inspecting relevant headers that may
77    /// be included in the `request.headers` of an error.
78    ///
79    /// This **does not** return header information from the request that reached relay.
80    fn header(&self, _header_name: &str) -> Option<&str> {
81        None
82    }
83}
84
85impl Filterable for Event {
86    fn csp(&self) -> Option<&Csp> {
87        if self.ty.value() != Some(&EventType::Csp) {
88            return None;
89        }
90        self.csp.value()
91    }
92
93    fn exceptions(&self) -> Option<&Values<Exception>> {
94        self.exceptions.value()
95    }
96
97    fn ip_addr(&self) -> Option<&str> {
98        let user = self.user.value()?;
99        Some(user.ip_address.value()?.as_ref())
100    }
101
102    fn logentry(&self) -> Option<&LogEntry> {
103        self.logentry.value()
104    }
105
106    fn release(&self) -> Option<&str> {
107        self.release.as_str()
108    }
109
110    fn transaction(&self) -> Option<&str> {
111        if self.ty.value() != Some(&EventType::Transaction) {
112            return None;
113        }
114        self.transaction.as_str()
115    }
116
117    fn url(&self) -> Option<Url> {
118        let url_str = self.request.value()?.url.value()?;
119        Url::parse(url_str).ok()
120    }
121
122    fn user_agent(&self) -> UserAgent<'_> {
123        UserAgent {
124            raw: self.user_agent(),
125            parsed: None,
126        }
127    }
128
129    fn header(&self, header_name: &str) -> Option<&str> {
130        self.request
131            .value()?
132            .headers
133            .value()?
134            .get_header(header_name)
135    }
136}
137
138impl Filterable for Replay {
139    fn ip_addr(&self) -> Option<&str> {
140        let user = self.user.value()?;
141        Some(user.ip_address.value()?.as_ref())
142    }
143
144    fn release(&self) -> Option<&str> {
145        self.release.as_str()
146    }
147
148    fn url(&self) -> Option<Url> {
149        let url_str = self.request.value()?.url.value()?;
150        Url::parse(url_str).ok()
151    }
152
153    fn user_agent(&self) -> UserAgent<'_> {
154        UserAgent {
155            raw: self.user_agent(),
156            parsed: None,
157        }
158    }
159
160    fn header(&self, header_name: &str) -> Option<&str> {
161        self.request
162            .value()?
163            .headers
164            .value()?
165            .get_header(header_name)
166    }
167}
168
169impl Filterable for Span {
170    fn ip_addr(&self) -> Option<&str> {
171        self.data.value()?.client_address.as_str()
172    }
173
174    fn release(&self) -> Option<&str> {
175        self.data.value()?.release.as_str()
176    }
177
178    fn transaction(&self) -> Option<&str> {
179        self.data.value()?.segment_name.as_str()
180    }
181
182    fn url(&self) -> Option<Url> {
183        let url_str = self.data.value()?.url_full.as_str()?;
184        Url::parse(url_str).ok()
185    }
186
187    fn user_agent(&self) -> UserAgent<'_> {
188        let raw = self
189            .data
190            .value()
191            .and_then(|data| data.user_agent_original.as_str());
192        UserAgent { raw, parsed: None }
193    }
194}
195
196impl Filterable for SessionUpdate {
197    fn ip_addr(&self) -> Option<&str> {
198        self.attributes
199            .ip_address
200            .as_ref()
201            .map(|addr| addr.as_str())
202    }
203
204    fn release(&self) -> Option<&str> {
205        Some(&self.attributes.release)
206    }
207
208    fn user_agent(&self) -> UserAgent<'_> {
209        UserAgent {
210            raw: self.attributes.user_agent.as_deref(),
211            parsed: None,
212        }
213    }
214}
215
216impl Filterable for SessionAggregates {
217    fn ip_addr(&self) -> Option<&str> {
218        self.attributes
219            .ip_address
220            .as_ref()
221            .map(|addr| addr.as_str())
222    }
223
224    fn release(&self) -> Option<&str> {
225        Some(&self.attributes.release)
226    }
227
228    fn user_agent(&self) -> UserAgent<'_> {
229        UserAgent {
230            raw: self.attributes.user_agent.as_deref(),
231            parsed: None,
232        }
233    }
234}
235
236impl Filterable for OurLog {
237    fn release(&self) -> Option<&str> {
238        self.attributes
239            .value()?
240            .get_value("sentry.release")?
241            .as_str()
242    }
243
244    fn user_agent(&self) -> UserAgent<'_> {
245        let parsed = (|| {
246            let attributes = self.attributes.value()?;
247
248            let family = attributes.get_value("sentry.browser.name")?.as_str()?;
249            let version = attributes.get_value("sentry.browser.version")?.as_str()?;
250            let mut parts = version.splitn(3, '.');
251
252            Some(relay_ua::UserAgent {
253                family: family.into(),
254                major: parts.next().map(Into::into),
255                minor: parts.next().map(Into::into),
256                patch: parts.next().map(Into::into),
257            })
258        })();
259
260        UserAgent { raw: None, parsed }
261    }
262}