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