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 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 culture::*;
30pub use device::*;
31pub use flags::*;
32pub use gpu::*;
33pub use memory_info::*;
34pub use monitor::*;
35pub use os::*;
36pub use ota_updates::*;
37pub use otel::*;
38pub use performance_score::*;
39pub use profile::*;
40pub use replay::*;
41pub use reprocessing::*;
42pub use response::*;
43pub use runtime::*;
44pub use spring::*;
45pub use threadpool_info::*;
46pub use trace::*;
47pub use unity::*;
48pub use user_report_v2::*;
49
50use relay_protocol::{Annotated, Empty, FromValue, IntoValue, Object, Value};
51
52use crate::processor::ProcessValue;
53
54pub type OperationType = String;
59
60pub type OriginType = String;
63
64#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
66#[metastructure(process_func = "process_context")]
67pub enum Context {
68 Device(#[metastructure(max_bytes = 8192)] Box<DeviceContext>),
70 Os(#[metastructure(max_bytes = 8192)] Box<OsContext>),
72 Runtime(#[metastructure(max_bytes = 8192)] Box<RuntimeContext>),
74 App(#[metastructure(max_bytes = 8192)] Box<AppContext>),
76 Browser(#[metastructure(max_bytes = 8192)] Box<BrowserContext>),
78 Gpu(#[metastructure(max_bytes = 8192)] Box<GpuContext>),
80 Trace(#[metastructure(max_bytes = 8192)] Box<TraceContext>),
82 Profile(#[metastructure(max_bytes = 8192)] Box<ProfileContext>),
84 Replay(#[metastructure(max_bytes = 8192)] Box<ReplayContext>),
86 Flags(#[metastructure(max_bytes = 65_536)] Box<flags::FlagsContext>),
88 #[metastructure(tag = "feedback")]
90 UserReportV2(#[metastructure(max_bytes = 8192)] Box<UserReportV2Context>),
91 #[metastructure(tag = "memory_info")]
93 MemoryInfo(#[metastructure(max_bytes = 8192)] Box<MemoryInfoContext>),
94 Monitor(#[metastructure(max_bytes = 8192)] Box<MonitorContext>),
96 #[metastructure(omit_from_schema)]
98 Reprocessing(#[metastructure(max_bytes = 8192)] Box<ReprocessingContext>),
99 Response(#[metastructure(max_bytes = 8192)] Box<ResponseContext>),
101 Otel(#[metastructure(max_bytes = 8192)] Box<OtelContext>),
103 CloudResource(#[metastructure(max_bytes = 8192)] Box<CloudResourceContext>),
105 Culture(#[metastructure(max_bytes = 8192)] Box<CultureContext>),
107 PerformanceScore(#[metastructure(max_bytes = 8192)] Box<PerformanceScoreContext>),
109 Spring(#[metastructure(max_bytes = 8192)] Box<SpringContext>),
111 #[metastructure(tag = "threadpool_info")]
113 ThreadPoolInfo(#[metastructure(max_bytes = 8192)] Box<ThreadPoolInfoContext>),
114 OTAUpdates(#[metastructure(max_bytes = 8192)] Box<OTAUpdatesContext>),
116 ChromiumStabilityReport(#[metastructure(max_bytes = 8192)] Box<StabilityReportContext>),
118 Unity(#[metastructure(max_bytes = 8192)] Box<UnityContext>),
120 #[metastructure(fallback_variant, retain = true)]
122 Other(#[metastructure(pii = "true", max_bytes = 8192)] Object<Value>),
123}
124
125#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
126pub struct ContextInner(#[metastructure(max_depth = 7)] pub Context);
127
128impl From<Context> for ContextInner {
129 fn from(c: Context) -> ContextInner {
130 ContextInner(c)
131 }
132}
133
134#[derive(Clone, Debug, PartialEq, Empty, IntoValue, ProcessValue, Default)]
148#[metastructure(process_func = "process_contexts")]
149pub struct Contexts(pub Object<ContextInner>);
150
151impl Contexts {
152 pub fn new() -> Contexts {
154 Contexts(Object::new())
155 }
156
157 pub fn add<C>(&mut self, context: C)
159 where
160 C: DefaultContext,
161 {
162 self.insert(C::default_key().to_owned(), context.into_context());
163 }
164
165 pub fn insert(&mut self, key: String, context: Context) {
170 self.0.insert(key, Annotated::new(ContextInner(context)));
171 }
172
173 pub fn contains<C>(&self) -> bool
175 where
176 C: DefaultContext,
177 {
178 self.get::<C>().is_some()
180 }
181
182 pub fn contains_key<S>(&self, key: S) -> bool
187 where
188 S: AsRef<str>,
189 {
190 self.0.contains_key(key.as_ref())
191 }
192
193 pub fn get_or_default<C>(&mut self) -> &mut C
195 where
196 C: DefaultContext,
197 {
198 if !self.contains::<C>() {
199 self.add(C::default());
200 }
201
202 self.get_mut().unwrap()
203 }
204
205 pub fn get_or_insert_with<F, S>(&mut self, key: S, context_builder: F) -> &mut Context
210 where
211 F: FnOnce() -> Context,
212 S: Into<String>,
213 {
214 &mut self
215 .0
216 .entry(key.into())
217 .or_insert_with(Annotated::empty)
218 .value_mut()
219 .get_or_insert_with(|| ContextInner(context_builder()))
220 .0
221 }
222
223 pub fn get<C>(&self) -> Option<&C>
225 where
226 C: DefaultContext,
227 {
228 C::cast(self.get_key(C::default_key())?)
229 }
230
231 pub fn get_mut<C>(&mut self) -> Option<&mut C>
233 where
234 C: DefaultContext,
235 {
236 C::cast_mut(self.get_key_mut(C::default_key())?)
237 }
238
239 pub fn get_key<S>(&self, key: S) -> Option<&Context>
244 where
245 S: AsRef<str>,
246 {
247 Some(&self.0.get(key.as_ref())?.value().as_ref()?.0)
248 }
249
250 pub fn get_key_mut<S>(&mut self, key: S) -> Option<&mut Context>
255 where
256 S: AsRef<str>,
257 {
258 Some(&mut self.0.get_mut(key.as_ref())?.value_mut().as_mut()?.0)
259 }
260
261 pub fn remove<C>(&mut self) -> Option<C>
266 where
267 C: DefaultContext,
268 {
269 let context = self.remove_key(C::default_key())?;
270 C::from_context(context)
271 }
272
273 pub fn remove_key<S>(&mut self, key: S) -> Option<Context>
278 where
279 S: AsRef<str>,
280 {
281 let inner = self.0.remove(key.as_ref())?;
282 Some(inner.into_value()?.0)
283 }
284}
285
286impl FromValue for Contexts {
287 fn from_value(mut annotated: Annotated<Value>) -> Annotated<Self> {
288 if let Annotated(Some(Value::Object(ref mut items)), _) = annotated {
289 for (key, value) in items.iter_mut() {
290 if let Annotated(Some(Value::Object(items)), _) = value {
291 if !is_valid_context_type(items.get("type")) {
293 items.insert(
294 "type".to_owned(),
295 Annotated::new(Value::String(key.to_string())),
296 );
297 }
298 }
299 }
300 }
301 FromValue::from_value(annotated).map_value(Contexts)
302 }
303}
304
305fn is_valid_context_type(value: Option<&Annotated<Value>>) -> bool {
308 matches!(value.and_then(|v| v.value()), Some(Value::String(s)) if !s.is_empty())
309}
310
311pub trait DefaultContext: Default {
316 fn default_key() -> &'static str;
318
319 fn from_context(context: Context) -> Option<Self>;
323
324 fn cast(context: &Context) -> Option<&Self>;
328
329 fn cast_mut(context: &mut Context) -> Option<&mut Self>;
333
334 fn into_context(self) -> Context;
338}
339
340#[cfg(test)]
341mod tests {
342 use relay_protocol::{Map, Meta};
343
344 use super::*;
345 use crate::processor::{ProcessingResult, ProcessingState, Processor};
346 use crate::protocol::Event;
347
348 #[test]
349 fn test_other_context_roundtrip() {
350 let json = r#"{"other":"value","type":"mytype"}"#;
351 let context = Annotated::new(Context::Other({
352 let mut map = Map::new();
353 map.insert(
354 "other".to_owned(),
355 Annotated::new(Value::String("value".to_owned())),
356 );
357 map.insert(
358 "type".to_owned(),
359 Annotated::new(Value::String("mytype".to_owned())),
360 );
361 map
362 }));
363
364 assert_eq!(context, Annotated::from_json(json).unwrap());
365 assert_eq!(json, context.to_json().unwrap());
366 }
367
368 #[test]
369 fn test_untagged_context_deserialize() {
370 let json = r#"{"os": {"name": "Linux"}}"#;
371
372 let mut map = Contexts::new();
373 map.add(OsContext {
374 name: Annotated::new("Linux".to_owned()),
375 ..Default::default()
376 });
377
378 assert_eq!(Annotated::new(map), Annotated::from_json(json).unwrap());
379 }
380
381 #[test]
382 fn test_context_invalid_type_deserialize() {
383 let json = r#"{
384 "monitor":{"name":"Foobar","type":17},
385 "os":{"name":"Linux","type":{}},
386 "profile":{"profile_id":"52df9022835246eeb317dbd739ccd059","type":""},
387 "runtime":{"name":"rustc","type":["invalid"]}
388 }"#;
389
390 let mut map = Contexts::new();
391 map.add(MonitorContext(
392 [("name".to_owned(), Value::String("Foobar".to_owned()).into())]
393 .into_iter()
394 .collect(),
395 ));
396 map.add(OsContext {
397 name: Annotated::new("Linux".to_owned()),
398 ..Default::default()
399 });
400 map.add(ProfileContext {
401 profile_id: Annotated::new("52df9022835246eeb317dbd739ccd059".parse().unwrap()),
402 ..Default::default()
403 });
404 map.add(RuntimeContext {
405 name: Annotated::new("rustc".to_owned()),
406 ..Default::default()
407 });
408
409 assert_eq!(Annotated::new(map), Annotated::from_json(json).unwrap());
410 }
411
412 #[test]
413 fn test_multiple_contexts_roundtrip() {
414 let json =
415 r#"{"os":{"name":"Linux","type":"os"},"runtime":{"name":"rustc","type":"runtime"}}"#;
416
417 let mut map = Contexts::new();
418 map.add(OsContext {
419 name: Annotated::new("Linux".to_owned()),
420 ..Default::default()
421 });
422 map.add(RuntimeContext {
423 name: Annotated::new("rustc".to_owned()),
424 ..Default::default()
425 });
426
427 let contexts = Annotated::new(map);
428 assert_eq!(contexts, Annotated::from_json(json).unwrap());
429 assert_eq!(json, contexts.to_json().unwrap());
430 }
431
432 #[test]
433 fn test_context_processing() {
434 let mut event = Annotated::new(Event {
435 contexts: {
436 let mut contexts = Contexts::new();
437 contexts.add(RuntimeContext {
438 name: Annotated::new("php".to_owned()),
439 version: Annotated::new("7.1.20-1+ubuntu16.04.1+deb.sury.org+1".to_owned()),
440 ..Default::default()
441 });
442 Annotated::new(contexts)
443 },
444 ..Default::default()
445 });
446
447 struct FooProcessor {
448 called: bool,
449 }
450
451 impl Processor for FooProcessor {
452 #[inline]
453 fn process_context(
454 &mut self,
455 _value: &mut Context,
456 _meta: &mut Meta,
457 _state: &ProcessingState<'_>,
458 ) -> ProcessingResult {
459 self.called = true;
460 Ok(())
461 }
462 }
463
464 let mut processor = FooProcessor { called: false };
465 crate::processor::process_value(&mut event, &mut processor, ProcessingState::root())
466 .unwrap();
467 assert!(processor.called);
468 }
469}