relay_filter/
interface.rs1use relay_conventions::{
4    BROWSER_NAME, BROWSER_VERSION, RELEASE, SEGMENT_NAME, USER_AGENT_ORIGINAL,
5};
6use url::Url;
7
8use relay_event_schema::protocol::{
9    Attributes, Csp, Event, EventType, Exception, LogEntry, OurLog, Replay, SessionAggregates,
10    SessionUpdate, Span, SpanV2, TraceMetric, Values,
11};
12
13#[derive(Clone, Debug, Default)]
15pub struct UserAgent<'a> {
16    pub raw: Option<&'a str>,
18    pub parsed: Option<relay_ua::UserAgent<'a>>,
20}
21
22impl<'a> UserAgent<'a> {
23    pub fn parsed(&mut self) -> Option<&'_ relay_ua::UserAgent<'a>> {
25        match &mut self.parsed {
26            Some(parsed) => Some(parsed),
27            parsed @ None => {
28                *parsed = Some(relay_ua::parse_user_agent(self.raw?));
29                parsed.as_ref()
30            }
31        }
32    }
33}
34
35pub trait Filterable {
37    fn csp(&self) -> Option<&Csp> {
39        None
40    }
41
42    fn exceptions(&self) -> Option<&Values<Exception>> {
44        None
45    }
46
47    fn ip_addr(&self) -> Option<&str> {
49        None
50    }
51
52    fn logentry(&self) -> Option<&LogEntry> {
54        None
55    }
56
57    fn release(&self) -> Option<&str> {
59        None
60    }
61
62    fn transaction(&self) -> Option<&str> {
64        None
65    }
66
67    fn url(&self) -> Option<Url> {
69        None
70    }
71
72    fn user_agent(&self) -> UserAgent<'_> {
74        Default::default()
75    }
76
77    fn header(&self, _header_name: &str) -> Option<&str> {
84        None
85    }
86}
87
88impl Filterable for Event {
89    fn csp(&self) -> Option<&Csp> {
90        if self.ty.value() != Some(&EventType::Csp) {
91            return None;
92        }
93        self.csp.value()
94    }
95
96    fn exceptions(&self) -> Option<&Values<Exception>> {
97        self.exceptions.value()
98    }
99
100    fn ip_addr(&self) -> Option<&str> {
101        let user = self.user.value()?;
102        Some(user.ip_address.value()?.as_ref())
103    }
104
105    fn logentry(&self) -> Option<&LogEntry> {
106        self.logentry.value()
107    }
108
109    fn release(&self) -> Option<&str> {
110        self.release.as_str()
111    }
112
113    fn transaction(&self) -> Option<&str> {
114        if self.ty.value() != Some(&EventType::Transaction) {
115            return None;
116        }
117        self.transaction.as_str()
118    }
119
120    fn url(&self) -> Option<Url> {
121        let url_str = self.request.value()?.url.value()?;
122        Url::parse(url_str).ok()
123    }
124
125    fn user_agent(&self) -> UserAgent<'_> {
126        UserAgent {
127            raw: self.user_agent(),
128            parsed: None,
129        }
130    }
131
132    fn header(&self, header_name: &str) -> Option<&str> {
133        self.request
134            .value()?
135            .headers
136            .value()?
137            .get_header(header_name)
138    }
139}
140
141impl Filterable for Replay {
142    fn ip_addr(&self) -> Option<&str> {
143        let user = self.user.value()?;
144        Some(user.ip_address.value()?.as_ref())
145    }
146
147    fn release(&self) -> Option<&str> {
148        self.release.as_str()
149    }
150
151    fn url(&self) -> Option<Url> {
152        let url_str = self.request.value()?.url.value()?;
153        Url::parse(url_str).ok()
154    }
155
156    fn user_agent(&self) -> UserAgent<'_> {
157        UserAgent {
158            raw: self.user_agent(),
159            parsed: None,
160        }
161    }
162
163    fn header(&self, header_name: &str) -> Option<&str> {
164        self.request
165            .value()?
166            .headers
167            .value()?
168            .get_header(header_name)
169    }
170}
171
172impl Filterable for Span {
173    fn ip_addr(&self) -> Option<&str> {
174        self.data.value()?.client_address.as_str()
175    }
176
177    fn release(&self) -> Option<&str> {
178        self.data.value()?.release.as_str()
179    }
180
181    fn transaction(&self) -> Option<&str> {
182        self.data.value()?.segment_name.as_str()
183    }
184
185    fn url(&self) -> Option<Url> {
186        let url_str = self.data.value()?.url_full.as_str()?;
187        Url::parse(url_str).ok()
188    }
189
190    fn user_agent(&self) -> UserAgent<'_> {
191        let raw = self
192            .data
193            .value()
194            .and_then(|data| data.user_agent_original.as_str());
195        UserAgent { raw, parsed: None }
196    }
197}
198
199impl Filterable for SpanV2 {
200    fn release(&self) -> Option<&str> {
201        self.attributes.value()?.get_value(RELEASE)?.as_str()
202    }
203
204    fn transaction(&self) -> Option<&str> {
205        self.attributes.value()?.get_value(SEGMENT_NAME)?.as_str()
206    }
207
208    fn user_agent(&self) -> UserAgent<'_> {
209        user_agent_from_attributes(&self.attributes)
210    }
211}
212
213impl Filterable for SessionUpdate {
214    fn ip_addr(&self) -> Option<&str> {
215        self.attributes
216            .ip_address
217            .as_ref()
218            .map(|addr| addr.as_str())
219    }
220
221    fn release(&self) -> Option<&str> {
222        Some(&self.attributes.release)
223    }
224
225    fn user_agent(&self) -> UserAgent<'_> {
226        UserAgent {
227            raw: self.attributes.user_agent.as_deref(),
228            parsed: None,
229        }
230    }
231}
232
233impl Filterable for SessionAggregates {
234    fn ip_addr(&self) -> Option<&str> {
235        self.attributes
236            .ip_address
237            .as_ref()
238            .map(|addr| addr.as_str())
239    }
240
241    fn release(&self) -> Option<&str> {
242        Some(&self.attributes.release)
243    }
244
245    fn user_agent(&self) -> UserAgent<'_> {
246        UserAgent {
247            raw: self.attributes.user_agent.as_deref(),
248            parsed: None,
249        }
250    }
251}
252
253impl Filterable for OurLog {
254    fn release(&self) -> Option<&str> {
255        self.attributes.value()?.get_value(RELEASE)?.as_str()
256    }
257
258    fn user_agent(&self) -> UserAgent<'_> {
259        user_agent_from_attributes(&self.attributes)
260    }
261}
262
263impl Filterable for TraceMetric {
264    fn release(&self) -> Option<&str> {
265        self.attributes.value()?.get_value(RELEASE)?.as_str()
266    }
267
268    fn user_agent(&self) -> UserAgent<'_> {
269        user_agent_from_attributes(&self.attributes)
270    }
271}
272
273fn user_agent_from_attributes(attributes: &relay_protocol::Annotated<Attributes>) -> UserAgent<'_> {
274    let parsed = (|| {
275        let attributes = attributes.value()?;
276
277        let family = attributes.get_value(BROWSER_NAME)?.as_str()?;
278        let version = attributes.get_value(BROWSER_VERSION)?.as_str()?;
279        let mut parts = version.splitn(3, '.');
280
281        Some(relay_ua::UserAgent {
282            family: family.into(),
283            major: parts.next().map(Into::into),
284            minor: parts.next().map(Into::into),
285            patch: parts.next().map(Into::into),
286        })
287    })();
288
289    let raw = attributes
290        .value()
291        .and_then(|attr| attr.get_value(USER_AGENT_ORIGINAL))
292        .and_then(|ua| ua.as_str());
293
294    UserAgent { raw, parsed }
295}