relay_event_schema/protocol/contexts/
mod.rs1mod app;
2mod browser;
3mod cloud_resource;
4mod device;
5mod flags;
6mod gpu;
7mod monitor;
8mod nel;
9mod os;
10mod otel;
11mod performance_score;
12mod profile;
13mod replay;
14mod reprocessing;
15mod response;
16mod runtime;
17mod spring;
18mod trace;
19mod user_report_v2;
20pub use app::*;
21pub use browser::*;
22pub use cloud_resource::*;
23pub use device::*;
24pub use gpu::*;
25pub use monitor::*;
26pub use nel::*;
27pub use os::*;
28pub use otel::*;
29pub use performance_score::*;
30pub use profile::*;
31pub use replay::*;
32pub use reprocessing::*;
33pub use response::*;
34pub use runtime::*;
35pub use spring::*;
36pub use trace::*;
37pub use user_report_v2::*;
38
39use relay_protocol::{Annotated, Empty, FromValue, IntoValue, Object, Value};
40
41use crate::processor::ProcessValue;
42
43pub type OperationType = String;
48
49pub type OriginType = String;
52
53#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
55#[metastructure(process_func = "process_context")]
56pub enum Context {
57 Device(Box<DeviceContext>),
59 Os(Box<OsContext>),
61 Runtime(Box<RuntimeContext>),
63 App(Box<AppContext>),
65 Browser(Box<BrowserContext>),
67 Gpu(Box<GpuContext>),
69 Trace(Box<TraceContext>),
71 Profile(Box<ProfileContext>),
73 Replay(Box<ReplayContext>),
75 Flags(Box<flags::FlagsContext>),
77 #[metastructure(tag = "feedback")]
79 UserReportV2(Box<UserReportV2Context>),
80 Monitor(Box<MonitorContext>),
82 #[metastructure(omit_from_schema)]
84 Reprocessing(Box<ReprocessingContext>),
85 Response(Box<ResponseContext>),
87 Otel(Box<OtelContext>),
89 CloudResource(Box<CloudResourceContext>),
91 Nel(Box<NelContext>),
93 PerformanceScore(Box<PerformanceScoreContext>),
95 Spring(Box<SpringContext>),
97 #[metastructure(fallback_variant)]
99 Other(#[metastructure(pii = "true")] Object<Value>),
100}
101
102#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
103pub struct ContextInner(#[metastructure(max_depth = 7, max_bytes = 8192)] pub Context);
104
105impl From<Context> for ContextInner {
106 fn from(c: Context) -> ContextInner {
107 ContextInner(c)
108 }
109}
110
111#[derive(Clone, Debug, PartialEq, Empty, IntoValue, ProcessValue, Default)]
125#[metastructure(process_func = "process_contexts")]
126pub struct Contexts(pub Object<ContextInner>);
127
128impl Contexts {
129 pub fn new() -> Contexts {
131 Contexts(Object::new())
132 }
133
134 pub fn add<C>(&mut self, context: C)
136 where
137 C: DefaultContext,
138 {
139 self.insert(C::default_key().to_owned(), context.into_context());
140 }
141
142 pub fn insert(&mut self, key: String, context: Context) {
147 self.0.insert(key, Annotated::new(ContextInner(context)));
148 }
149
150 pub fn contains<C>(&self) -> bool
152 where
153 C: DefaultContext,
154 {
155 self.get::<C>().is_some()
157 }
158
159 pub fn contains_key<S>(&self, key: S) -> bool
164 where
165 S: AsRef<str>,
166 {
167 self.0.contains_key(key.as_ref())
168 }
169
170 pub fn get_or_default<C>(&mut self) -> &mut C
172 where
173 C: DefaultContext,
174 {
175 if !self.contains::<C>() {
176 self.add(C::default());
177 }
178
179 self.get_mut().unwrap()
180 }
181
182 pub fn get_or_insert_with<F, S>(&mut self, key: S, context_builder: F) -> &mut Context
187 where
188 F: FnOnce() -> Context,
189 S: Into<String>,
190 {
191 &mut self
192 .0
193 .entry(key.into())
194 .or_insert_with(Annotated::empty)
195 .value_mut()
196 .get_or_insert_with(|| ContextInner(context_builder()))
197 .0
198 }
199
200 pub fn get<C>(&self) -> Option<&C>
202 where
203 C: DefaultContext,
204 {
205 C::cast(self.get_key(C::default_key())?)
206 }
207
208 pub fn get_mut<C>(&mut self) -> Option<&mut C>
210 where
211 C: DefaultContext,
212 {
213 C::cast_mut(self.get_key_mut(C::default_key())?)
214 }
215
216 pub fn get_key<S>(&self, key: S) -> Option<&Context>
221 where
222 S: AsRef<str>,
223 {
224 Some(&self.0.get(key.as_ref())?.value().as_ref()?.0)
225 }
226
227 pub fn get_key_mut<S>(&mut self, key: S) -> Option<&mut Context>
232 where
233 S: AsRef<str>,
234 {
235 Some(&mut self.0.get_mut(key.as_ref())?.value_mut().as_mut()?.0)
236 }
237
238 pub fn remove<C>(&mut self) -> Option<C>
243 where
244 C: DefaultContext,
245 {
246 let context = self.remove_key(C::default_key())?;
247 C::from_context(context)
248 }
249
250 pub fn remove_key<S>(&mut self, key: S) -> Option<Context>
255 where
256 S: AsRef<str>,
257 {
258 let inner = self.0.remove(key.as_ref())?;
259 Some(inner.into_value()?.0)
260 }
261}
262
263impl FromValue for Contexts {
264 fn from_value(mut annotated: Annotated<Value>) -> Annotated<Self> {
265 if let Annotated(Some(Value::Object(ref mut items)), _) = annotated {
266 for (key, value) in items.iter_mut() {
267 if let Annotated(Some(Value::Object(ref mut items)), _) = value {
268 if !items.contains_key("type") {
269 items.insert(
270 "type".to_string(),
271 Annotated::new(Value::String(key.to_string())),
272 );
273 }
274 }
275 }
276 }
277 FromValue::from_value(annotated).map_value(Contexts)
278 }
279}
280
281pub trait DefaultContext: Default {
286 fn default_key() -> &'static str;
288
289 fn from_context(context: Context) -> Option<Self>;
293
294 fn cast(context: &Context) -> Option<&Self>;
298
299 fn cast_mut(context: &mut Context) -> Option<&mut Self>;
303
304 fn into_context(self) -> Context;
308}
309
310#[cfg(test)]
311mod tests {
312 use relay_protocol::{Map, Meta};
313
314 use super::*;
315 use crate::processor::{ProcessingResult, ProcessingState, Processor};
316 use crate::protocol::Event;
317
318 #[test]
319 fn test_other_context_roundtrip() {
320 let json = r#"{"other":"value","type":"mytype"}"#;
321 let context = Annotated::new(Context::Other({
322 let mut map = Map::new();
323 map.insert(
324 "other".to_string(),
325 Annotated::new(Value::String("value".to_string())),
326 );
327 map.insert(
328 "type".to_string(),
329 Annotated::new(Value::String("mytype".to_string())),
330 );
331 map
332 }));
333
334 assert_eq!(context, Annotated::from_json(json).unwrap());
335 assert_eq!(json, context.to_json().unwrap());
336 }
337
338 #[test]
339 fn test_untagged_context_deserialize() {
340 let json = r#"{"os": {"name": "Linux"}}"#;
341
342 let mut map = Contexts::new();
343 map.add(OsContext {
344 name: Annotated::new("Linux".to_string()),
345 ..Default::default()
346 });
347
348 assert_eq!(Annotated::new(map), Annotated::from_json(json).unwrap());
349 }
350
351 #[test]
352 fn test_multiple_contexts_roundtrip() {
353 let json =
354 r#"{"os":{"name":"Linux","type":"os"},"runtime":{"name":"rustc","type":"runtime"}}"#;
355
356 let mut map = Contexts::new();
357 map.add(OsContext {
358 name: Annotated::new("Linux".to_string()),
359 ..Default::default()
360 });
361 map.add(RuntimeContext {
362 name: Annotated::new("rustc".to_string()),
363 ..Default::default()
364 });
365
366 let contexts = Annotated::new(map);
367 assert_eq!(contexts, Annotated::from_json(json).unwrap());
368 assert_eq!(json, contexts.to_json().unwrap());
369 }
370
371 #[test]
372 fn test_context_processing() {
373 let mut event = Annotated::new(Event {
374 contexts: {
375 let mut contexts = Contexts::new();
376 contexts.add(RuntimeContext {
377 name: Annotated::new("php".to_owned()),
378 version: Annotated::new("7.1.20-1+ubuntu16.04.1+deb.sury.org+1".to_owned()),
379 ..Default::default()
380 });
381 Annotated::new(contexts)
382 },
383 ..Default::default()
384 });
385
386 struct FooProcessor {
387 called: bool,
388 }
389
390 impl Processor for FooProcessor {
391 #[inline]
392 fn process_context(
393 &mut self,
394 _value: &mut Context,
395 _meta: &mut Meta,
396 _state: &ProcessingState<'_>,
397 ) -> ProcessingResult {
398 self.called = true;
399 Ok(())
400 }
401 }
402
403 let mut processor = FooProcessor { called: false };
404 crate::processor::process_value(&mut event, &mut processor, ProcessingState::root())
405 .unwrap();
406 assert!(processor.called);
407 }
408}