relay_event_schema/protocol/contexts/
mod.rs1mod app;
2mod browser;
3mod chromium_stability_report;
4mod cloud_resource;
5mod device;
6mod flags;
7mod gpu;
8mod monitor;
9mod nel;
10mod os;
11mod ota_updates;
12mod otel;
13mod performance_score;
14mod profile;
15mod replay;
16mod reprocessing;
17mod response;
18mod runtime;
19mod spring;
20mod trace;
21mod user_report_v2;
22pub use app::*;
23pub use browser::*;
24pub use chromium_stability_report::*;
25pub use cloud_resource::*;
26pub use device::*;
27pub use gpu::*;
28pub use monitor::*;
29pub use nel::*;
30pub use os::*;
31pub use ota_updates::*;
32pub use otel::*;
33pub use performance_score::*;
34pub use profile::*;
35pub use replay::*;
36pub use reprocessing::*;
37pub use response::*;
38pub use runtime::*;
39pub use spring::*;
40pub use trace::*;
41pub use user_report_v2::*;
42
43use relay_protocol::{Annotated, Empty, FromValue, IntoValue, Object, Value};
44
45use crate::processor::ProcessValue;
46
47pub type OperationType = String;
52
53pub type OriginType = String;
56
57#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
59#[metastructure(process_func = "process_context")]
60pub enum Context {
61 Device(Box<DeviceContext>),
63 Os(Box<OsContext>),
65 Runtime(Box<RuntimeContext>),
67 App(Box<AppContext>),
69 Browser(Box<BrowserContext>),
71 Gpu(Box<GpuContext>),
73 Trace(Box<TraceContext>),
75 Profile(Box<ProfileContext>),
77 Replay(Box<ReplayContext>),
79 Flags(Box<flags::FlagsContext>),
81 #[metastructure(tag = "feedback")]
83 UserReportV2(Box<UserReportV2Context>),
84 Monitor(Box<MonitorContext>),
86 #[metastructure(omit_from_schema)]
88 Reprocessing(Box<ReprocessingContext>),
89 Response(Box<ResponseContext>),
91 Otel(Box<OtelContext>),
93 CloudResource(Box<CloudResourceContext>),
95 Nel(Box<NelContext>),
97 PerformanceScore(Box<PerformanceScoreContext>),
99 Spring(Box<SpringContext>),
101 OTAUpdates(Box<OTAUpdatesContext>),
103 ChromiumStabilityReport(Box<StabilityReportContext>),
105 #[metastructure(fallback_variant)]
107 Other(#[metastructure(pii = "true")] Object<Value>),
108}
109
110#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
111pub struct ContextInner(#[metastructure(max_depth = 7, max_bytes = 8192)] pub Context);
112
113impl From<Context> for ContextInner {
114 fn from(c: Context) -> ContextInner {
115 ContextInner(c)
116 }
117}
118
119#[derive(Clone, Debug, PartialEq, Empty, IntoValue, ProcessValue, Default)]
133#[metastructure(process_func = "process_contexts")]
134pub struct Contexts(pub Object<ContextInner>);
135
136impl Contexts {
137 pub fn new() -> Contexts {
139 Contexts(Object::new())
140 }
141
142 pub fn add<C>(&mut self, context: C)
144 where
145 C: DefaultContext,
146 {
147 self.insert(C::default_key().to_owned(), context.into_context());
148 }
149
150 pub fn insert(&mut self, key: String, context: Context) {
155 self.0.insert(key, Annotated::new(ContextInner(context)));
156 }
157
158 pub fn contains<C>(&self) -> bool
160 where
161 C: DefaultContext,
162 {
163 self.get::<C>().is_some()
165 }
166
167 pub fn contains_key<S>(&self, key: S) -> bool
172 where
173 S: AsRef<str>,
174 {
175 self.0.contains_key(key.as_ref())
176 }
177
178 pub fn get_or_default<C>(&mut self) -> &mut C
180 where
181 C: DefaultContext,
182 {
183 if !self.contains::<C>() {
184 self.add(C::default());
185 }
186
187 self.get_mut().unwrap()
188 }
189
190 pub fn get_or_insert_with<F, S>(&mut self, key: S, context_builder: F) -> &mut Context
195 where
196 F: FnOnce() -> Context,
197 S: Into<String>,
198 {
199 &mut self
200 .0
201 .entry(key.into())
202 .or_insert_with(Annotated::empty)
203 .value_mut()
204 .get_or_insert_with(|| ContextInner(context_builder()))
205 .0
206 }
207
208 pub fn get<C>(&self) -> Option<&C>
210 where
211 C: DefaultContext,
212 {
213 C::cast(self.get_key(C::default_key())?)
214 }
215
216 pub fn get_mut<C>(&mut self) -> Option<&mut C>
218 where
219 C: DefaultContext,
220 {
221 C::cast_mut(self.get_key_mut(C::default_key())?)
222 }
223
224 pub fn get_key<S>(&self, key: S) -> Option<&Context>
229 where
230 S: AsRef<str>,
231 {
232 Some(&self.0.get(key.as_ref())?.value().as_ref()?.0)
233 }
234
235 pub fn get_key_mut<S>(&mut self, key: S) -> Option<&mut Context>
240 where
241 S: AsRef<str>,
242 {
243 Some(&mut self.0.get_mut(key.as_ref())?.value_mut().as_mut()?.0)
244 }
245
246 pub fn remove<C>(&mut self) -> Option<C>
251 where
252 C: DefaultContext,
253 {
254 let context = self.remove_key(C::default_key())?;
255 C::from_context(context)
256 }
257
258 pub fn remove_key<S>(&mut self, key: S) -> Option<Context>
263 where
264 S: AsRef<str>,
265 {
266 let inner = self.0.remove(key.as_ref())?;
267 Some(inner.into_value()?.0)
268 }
269}
270
271impl FromValue for Contexts {
272 fn from_value(mut annotated: Annotated<Value>) -> Annotated<Self> {
273 if let Annotated(Some(Value::Object(ref mut items)), _) = annotated {
274 for (key, value) in items.iter_mut() {
275 if let Annotated(Some(Value::Object(items)), _) = value {
276 if !is_valid_context_type(items.get("type")) {
278 items.insert(
279 "type".to_owned(),
280 Annotated::new(Value::String(key.to_string())),
281 );
282 }
283 }
284 }
285 }
286 FromValue::from_value(annotated).map_value(Contexts)
287 }
288}
289
290fn is_valid_context_type(value: Option<&Annotated<Value>>) -> bool {
293 matches!(value.and_then(|v| v.value()), Some(Value::String(s)) if !s.is_empty())
294}
295
296pub trait DefaultContext: Default {
301 fn default_key() -> &'static str;
303
304 fn from_context(context: Context) -> Option<Self>;
308
309 fn cast(context: &Context) -> Option<&Self>;
313
314 fn cast_mut(context: &mut Context) -> Option<&mut Self>;
318
319 fn into_context(self) -> Context;
323}
324
325#[cfg(test)]
326mod tests {
327 use relay_protocol::{Map, Meta};
328
329 use super::*;
330 use crate::processor::{ProcessingResult, ProcessingState, Processor};
331 use crate::protocol::Event;
332
333 #[test]
334 fn test_other_context_roundtrip() {
335 let json = r#"{"other":"value","type":"mytype"}"#;
336 let context = Annotated::new(Context::Other({
337 let mut map = Map::new();
338 map.insert(
339 "other".to_owned(),
340 Annotated::new(Value::String("value".to_owned())),
341 );
342 map.insert(
343 "type".to_owned(),
344 Annotated::new(Value::String("mytype".to_owned())),
345 );
346 map
347 }));
348
349 assert_eq!(context, Annotated::from_json(json).unwrap());
350 assert_eq!(json, context.to_json().unwrap());
351 }
352
353 #[test]
354 fn test_untagged_context_deserialize() {
355 let json = r#"{"os": {"name": "Linux"}}"#;
356
357 let mut map = Contexts::new();
358 map.add(OsContext {
359 name: Annotated::new("Linux".to_owned()),
360 ..Default::default()
361 });
362
363 assert_eq!(Annotated::new(map), Annotated::from_json(json).unwrap());
364 }
365
366 #[test]
367 fn test_context_invalid_type_deserialize() {
368 let json = r#"{
369 "monitor":{"name":"Foobar","type":17},
370 "os":{"name":"Linux","type":{}},
371 "profile":{"profile_id":"52df9022835246eeb317dbd739ccd059","type":""},
372 "runtime":{"name":"rustc","type":["invalid"]}
373 }"#;
374
375 let mut map = Contexts::new();
376 map.add(MonitorContext(
377 [("name".to_owned(), Value::String("Foobar".to_owned()).into())]
378 .into_iter()
379 .collect(),
380 ));
381 map.add(OsContext {
382 name: Annotated::new("Linux".to_owned()),
383 ..Default::default()
384 });
385 map.add(ProfileContext {
386 profile_id: Annotated::new("52df9022835246eeb317dbd739ccd059".parse().unwrap()),
387 ..Default::default()
388 });
389 map.add(RuntimeContext {
390 name: Annotated::new("rustc".to_owned()),
391 ..Default::default()
392 });
393
394 assert_eq!(Annotated::new(map), Annotated::from_json(json).unwrap());
395 }
396
397 #[test]
398 fn test_multiple_contexts_roundtrip() {
399 let json =
400 r#"{"os":{"name":"Linux","type":"os"},"runtime":{"name":"rustc","type":"runtime"}}"#;
401
402 let mut map = Contexts::new();
403 map.add(OsContext {
404 name: Annotated::new("Linux".to_owned()),
405 ..Default::default()
406 });
407 map.add(RuntimeContext {
408 name: Annotated::new("rustc".to_owned()),
409 ..Default::default()
410 });
411
412 let contexts = Annotated::new(map);
413 assert_eq!(contexts, Annotated::from_json(json).unwrap());
414 assert_eq!(json, contexts.to_json().unwrap());
415 }
416
417 #[test]
418 fn test_context_processing() {
419 let mut event = Annotated::new(Event {
420 contexts: {
421 let mut contexts = Contexts::new();
422 contexts.add(RuntimeContext {
423 name: Annotated::new("php".to_owned()),
424 version: Annotated::new("7.1.20-1+ubuntu16.04.1+deb.sury.org+1".to_owned()),
425 ..Default::default()
426 });
427 Annotated::new(contexts)
428 },
429 ..Default::default()
430 });
431
432 struct FooProcessor {
433 called: bool,
434 }
435
436 impl Processor for FooProcessor {
437 #[inline]
438 fn process_context(
439 &mut self,
440 _value: &mut Context,
441 _meta: &mut Meta,
442 _state: &ProcessingState<'_>,
443 ) -> ProcessingResult {
444 self.called = true;
445 Ok(())
446 }
447 }
448
449 let mut processor = FooProcessor { called: false };
450 crate::processor::process_value(&mut event, &mut processor, ProcessingState::root())
451 .unwrap();
452 assert!(processor.called);
453 }
454}