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