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 !items.contains_key("type") {
277 items.insert(
278 "type".to_owned(),
279 Annotated::new(Value::String(key.to_string())),
280 );
281 }
282 }
283 }
284 }
285 FromValue::from_value(annotated).map_value(Contexts)
286 }
287}
288
289pub trait DefaultContext: Default {
294 fn default_key() -> &'static str;
296
297 fn from_context(context: Context) -> Option<Self>;
301
302 fn cast(context: &Context) -> Option<&Self>;
306
307 fn cast_mut(context: &mut Context) -> Option<&mut Self>;
311
312 fn into_context(self) -> Context;
316}
317
318#[cfg(test)]
319mod tests {
320 use relay_protocol::{Map, Meta};
321
322 use super::*;
323 use crate::processor::{ProcessingResult, ProcessingState, Processor};
324 use crate::protocol::Event;
325
326 #[test]
327 fn test_other_context_roundtrip() {
328 let json = r#"{"other":"value","type":"mytype"}"#;
329 let context = Annotated::new(Context::Other({
330 let mut map = Map::new();
331 map.insert(
332 "other".to_owned(),
333 Annotated::new(Value::String("value".to_owned())),
334 );
335 map.insert(
336 "type".to_owned(),
337 Annotated::new(Value::String("mytype".to_owned())),
338 );
339 map
340 }));
341
342 assert_eq!(context, Annotated::from_json(json).unwrap());
343 assert_eq!(json, context.to_json().unwrap());
344 }
345
346 #[test]
347 fn test_untagged_context_deserialize() {
348 let json = r#"{"os": {"name": "Linux"}}"#;
349
350 let mut map = Contexts::new();
351 map.add(OsContext {
352 name: Annotated::new("Linux".to_owned()),
353 ..Default::default()
354 });
355
356 assert_eq!(Annotated::new(map), Annotated::from_json(json).unwrap());
357 }
358
359 #[test]
360 fn test_multiple_contexts_roundtrip() {
361 let json =
362 r#"{"os":{"name":"Linux","type":"os"},"runtime":{"name":"rustc","type":"runtime"}}"#;
363
364 let mut map = Contexts::new();
365 map.add(OsContext {
366 name: Annotated::new("Linux".to_owned()),
367 ..Default::default()
368 });
369 map.add(RuntimeContext {
370 name: Annotated::new("rustc".to_owned()),
371 ..Default::default()
372 });
373
374 let contexts = Annotated::new(map);
375 assert_eq!(contexts, Annotated::from_json(json).unwrap());
376 assert_eq!(json, contexts.to_json().unwrap());
377 }
378
379 #[test]
380 fn test_context_processing() {
381 let mut event = Annotated::new(Event {
382 contexts: {
383 let mut contexts = Contexts::new();
384 contexts.add(RuntimeContext {
385 name: Annotated::new("php".to_owned()),
386 version: Annotated::new("7.1.20-1+ubuntu16.04.1+deb.sury.org+1".to_owned()),
387 ..Default::default()
388 });
389 Annotated::new(contexts)
390 },
391 ..Default::default()
392 });
393
394 struct FooProcessor {
395 called: bool,
396 }
397
398 impl Processor for FooProcessor {
399 #[inline]
400 fn process_context(
401 &mut self,
402 _value: &mut Context,
403 _meta: &mut Meta,
404 _state: &ProcessingState<'_>,
405 ) -> ProcessingResult {
406 self.called = true;
407 Ok(())
408 }
409 }
410
411 let mut processor = FooProcessor { called: false };
412 crate::processor::process_value(&mut event, &mut processor, ProcessingState::root())
413 .unwrap();
414 assert!(processor.called);
415 }
416}