relay_event_schema/protocol/contexts/
mod.rs1mod app;
2mod browser;
3mod chromium_stability_report;
4mod cloud_resource;
5mod culture;
6mod device;
7mod flags;
8mod gpu;
9mod memory_info;
10mod monitor;
11mod nel;
12mod os;
13mod ota_updates;
14mod otel;
15mod performance_score;
16mod profile;
17mod replay;
18mod reprocessing;
19mod response;
20mod runtime;
21mod spring;
22mod threadpool_info;
23mod trace;
24mod unity;
25mod user_report_v2;
26pub use app::*;
27pub use browser::*;
28pub use chromium_stability_report::*;
29pub use cloud_resource::*;
30pub use culture::*;
31pub use device::*;
32pub use gpu::*;
33pub use memory_info::*;
34pub use monitor::*;
35pub use nel::*;
36pub use os::*;
37pub use ota_updates::*;
38pub use otel::*;
39pub use performance_score::*;
40pub use profile::*;
41pub use replay::*;
42pub use reprocessing::*;
43pub use response::*;
44pub use runtime::*;
45pub use spring::*;
46pub use threadpool_info::*;
47pub use trace::*;
48pub use unity::*;
49pub use user_report_v2::*;
50
51use relay_protocol::{Annotated, Empty, FromValue, IntoValue, Object, Value};
52
53use crate::processor::ProcessValue;
54
55pub type OperationType = String;
60
61pub type OriginType = String;
64
65#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
67#[metastructure(process_func = "process_context")]
68pub enum Context {
69 Device(Box<DeviceContext>),
71 Os(Box<OsContext>),
73 Runtime(Box<RuntimeContext>),
75 App(Box<AppContext>),
77 Browser(Box<BrowserContext>),
79 Gpu(Box<GpuContext>),
81 Trace(Box<TraceContext>),
83 Profile(Box<ProfileContext>),
85 Replay(Box<ReplayContext>),
87 Flags(Box<flags::FlagsContext>),
89 #[metastructure(tag = "feedback")]
91 UserReportV2(Box<UserReportV2Context>),
92 #[metastructure(tag = "memory_info")]
94 MemoryInfo(Box<MemoryInfoContext>),
95 Monitor(Box<MonitorContext>),
97 #[metastructure(omit_from_schema)]
99 Reprocessing(Box<ReprocessingContext>),
100 Response(Box<ResponseContext>),
102 Otel(Box<OtelContext>),
104 CloudResource(Box<CloudResourceContext>),
106 Culture(Box<CultureContext>),
108 Nel(Box<NelContext>),
110 PerformanceScore(Box<PerformanceScoreContext>),
112 Spring(Box<SpringContext>),
114 #[metastructure(tag = "threadpool_info")]
116 ThreadPoolInfo(Box<ThreadPoolInfoContext>),
117 OTAUpdates(Box<OTAUpdatesContext>),
119 ChromiumStabilityReport(Box<StabilityReportContext>),
121 Unity(Box<UnityContext>),
123 #[metastructure(fallback_variant)]
125 Other(#[metastructure(pii = "true")] Object<Value>),
126}
127
128#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
129pub struct ContextInner(#[metastructure(max_depth = 7, max_bytes = 8192)] pub Context);
130
131impl From<Context> for ContextInner {
132 fn from(c: Context) -> ContextInner {
133 ContextInner(c)
134 }
135}
136
137#[derive(Clone, Debug, PartialEq, Empty, IntoValue, ProcessValue, Default)]
151#[metastructure(process_func = "process_contexts")]
152pub struct Contexts(pub Object<ContextInner>);
153
154impl Contexts {
155 pub fn new() -> Contexts {
157 Contexts(Object::new())
158 }
159
160 pub fn add<C>(&mut self, context: C)
162 where
163 C: DefaultContext,
164 {
165 self.insert(C::default_key().to_owned(), context.into_context());
166 }
167
168 pub fn insert(&mut self, key: String, context: Context) {
173 self.0.insert(key, Annotated::new(ContextInner(context)));
174 }
175
176 pub fn contains<C>(&self) -> bool
178 where
179 C: DefaultContext,
180 {
181 self.get::<C>().is_some()
183 }
184
185 pub fn contains_key<S>(&self, key: S) -> bool
190 where
191 S: AsRef<str>,
192 {
193 self.0.contains_key(key.as_ref())
194 }
195
196 pub fn get_or_default<C>(&mut self) -> &mut C
198 where
199 C: DefaultContext,
200 {
201 if !self.contains::<C>() {
202 self.add(C::default());
203 }
204
205 self.get_mut().unwrap()
206 }
207
208 pub fn get_or_insert_with<F, S>(&mut self, key: S, context_builder: F) -> &mut Context
213 where
214 F: FnOnce() -> Context,
215 S: Into<String>,
216 {
217 &mut self
218 .0
219 .entry(key.into())
220 .or_insert_with(Annotated::empty)
221 .value_mut()
222 .get_or_insert_with(|| ContextInner(context_builder()))
223 .0
224 }
225
226 pub fn get<C>(&self) -> Option<&C>
228 where
229 C: DefaultContext,
230 {
231 C::cast(self.get_key(C::default_key())?)
232 }
233
234 pub fn get_mut<C>(&mut self) -> Option<&mut C>
236 where
237 C: DefaultContext,
238 {
239 C::cast_mut(self.get_key_mut(C::default_key())?)
240 }
241
242 pub fn get_key<S>(&self, key: S) -> Option<&Context>
247 where
248 S: AsRef<str>,
249 {
250 Some(&self.0.get(key.as_ref())?.value().as_ref()?.0)
251 }
252
253 pub fn get_key_mut<S>(&mut self, key: S) -> Option<&mut Context>
258 where
259 S: AsRef<str>,
260 {
261 Some(&mut self.0.get_mut(key.as_ref())?.value_mut().as_mut()?.0)
262 }
263
264 pub fn remove<C>(&mut self) -> Option<C>
269 where
270 C: DefaultContext,
271 {
272 let context = self.remove_key(C::default_key())?;
273 C::from_context(context)
274 }
275
276 pub fn remove_key<S>(&mut self, key: S) -> Option<Context>
281 where
282 S: AsRef<str>,
283 {
284 let inner = self.0.remove(key.as_ref())?;
285 Some(inner.into_value()?.0)
286 }
287}
288
289impl FromValue for Contexts {
290 fn from_value(mut annotated: Annotated<Value>) -> Annotated<Self> {
291 if let Annotated(Some(Value::Object(ref mut items)), _) = annotated {
292 for (key, value) in items.iter_mut() {
293 if let Annotated(Some(Value::Object(items)), _) = value {
294 if !is_valid_context_type(items.get("type")) {
296 items.insert(
297 "type".to_owned(),
298 Annotated::new(Value::String(key.to_string())),
299 );
300 }
301 }
302 }
303 }
304 FromValue::from_value(annotated).map_value(Contexts)
305 }
306}
307
308fn is_valid_context_type(value: Option<&Annotated<Value>>) -> bool {
311 matches!(value.and_then(|v| v.value()), Some(Value::String(s)) if !s.is_empty())
312}
313
314pub trait DefaultContext: Default {
319 fn default_key() -> &'static str;
321
322 fn from_context(context: Context) -> Option<Self>;
326
327 fn cast(context: &Context) -> Option<&Self>;
331
332 fn cast_mut(context: &mut Context) -> Option<&mut Self>;
336
337 fn into_context(self) -> Context;
341}
342
343#[cfg(test)]
344mod tests {
345 use relay_protocol::{Map, Meta};
346
347 use super::*;
348 use crate::processor::{ProcessingResult, ProcessingState, Processor};
349 use crate::protocol::Event;
350
351 #[test]
352 fn test_other_context_roundtrip() {
353 let json = r#"{"other":"value","type":"mytype"}"#;
354 let context = Annotated::new(Context::Other({
355 let mut map = Map::new();
356 map.insert(
357 "other".to_owned(),
358 Annotated::new(Value::String("value".to_owned())),
359 );
360 map.insert(
361 "type".to_owned(),
362 Annotated::new(Value::String("mytype".to_owned())),
363 );
364 map
365 }));
366
367 assert_eq!(context, Annotated::from_json(json).unwrap());
368 assert_eq!(json, context.to_json().unwrap());
369 }
370
371 #[test]
372 fn test_untagged_context_deserialize() {
373 let json = r#"{"os": {"name": "Linux"}}"#;
374
375 let mut map = Contexts::new();
376 map.add(OsContext {
377 name: Annotated::new("Linux".to_owned()),
378 ..Default::default()
379 });
380
381 assert_eq!(Annotated::new(map), Annotated::from_json(json).unwrap());
382 }
383
384 #[test]
385 fn test_context_invalid_type_deserialize() {
386 let json = r#"{
387 "monitor":{"name":"Foobar","type":17},
388 "os":{"name":"Linux","type":{}},
389 "profile":{"profile_id":"52df9022835246eeb317dbd739ccd059","type":""},
390 "runtime":{"name":"rustc","type":["invalid"]}
391 }"#;
392
393 let mut map = Contexts::new();
394 map.add(MonitorContext(
395 [("name".to_owned(), Value::String("Foobar".to_owned()).into())]
396 .into_iter()
397 .collect(),
398 ));
399 map.add(OsContext {
400 name: Annotated::new("Linux".to_owned()),
401 ..Default::default()
402 });
403 map.add(ProfileContext {
404 profile_id: Annotated::new("52df9022835246eeb317dbd739ccd059".parse().unwrap()),
405 ..Default::default()
406 });
407 map.add(RuntimeContext {
408 name: Annotated::new("rustc".to_owned()),
409 ..Default::default()
410 });
411
412 assert_eq!(Annotated::new(map), Annotated::from_json(json).unwrap());
413 }
414
415 #[test]
416 fn test_multiple_contexts_roundtrip() {
417 let json =
418 r#"{"os":{"name":"Linux","type":"os"},"runtime":{"name":"rustc","type":"runtime"}}"#;
419
420 let mut map = Contexts::new();
421 map.add(OsContext {
422 name: Annotated::new("Linux".to_owned()),
423 ..Default::default()
424 });
425 map.add(RuntimeContext {
426 name: Annotated::new("rustc".to_owned()),
427 ..Default::default()
428 });
429
430 let contexts = Annotated::new(map);
431 assert_eq!(contexts, Annotated::from_json(json).unwrap());
432 assert_eq!(json, contexts.to_json().unwrap());
433 }
434
435 #[test]
436 fn test_context_processing() {
437 let mut event = Annotated::new(Event {
438 contexts: {
439 let mut contexts = Contexts::new();
440 contexts.add(RuntimeContext {
441 name: Annotated::new("php".to_owned()),
442 version: Annotated::new("7.1.20-1+ubuntu16.04.1+deb.sury.org+1".to_owned()),
443 ..Default::default()
444 });
445 Annotated::new(contexts)
446 },
447 ..Default::default()
448 });
449
450 struct FooProcessor {
451 called: bool,
452 }
453
454 impl Processor for FooProcessor {
455 #[inline]
456 fn process_context(
457 &mut self,
458 _value: &mut Context,
459 _meta: &mut Meta,
460 _state: &ProcessingState<'_>,
461 ) -> ProcessingResult {
462 self.called = true;
463 Ok(())
464 }
465 }
466
467 let mut processor = FooProcessor { called: false };
468 crate::processor::process_value(&mut event, &mut processor, ProcessingState::root())
469 .unwrap();
470 assert!(processor.called);
471 }
472}