relay_event_schema/protocol/contexts/
mod.rs1mod app;
2mod browser;
3mod cloud_resource;
4mod device;
5mod flags;
6mod gpu;
7mod monitor;
8mod nel;
9mod os;
10mod ota_updates;
11mod otel;
12mod performance_score;
13mod profile;
14mod replay;
15mod reprocessing;
16mod response;
17mod runtime;
18mod spring;
19mod trace;
20mod user_report_v2;
21pub use app::*;
22pub use browser::*;
23pub use cloud_resource::*;
24pub use device::*;
25pub use gpu::*;
26pub use monitor::*;
27pub use nel::*;
28pub use os::*;
29pub use ota_updates::*;
30pub use otel::*;
31pub use performance_score::*;
32pub use profile::*;
33pub use replay::*;
34pub use reprocessing::*;
35pub use response::*;
36pub use runtime::*;
37pub use spring::*;
38pub use trace::*;
39pub use user_report_v2::*;
40
41use relay_protocol::{Annotated, Empty, FromValue, IntoValue, Object, Value};
42
43use crate::processor::ProcessValue;
44
45pub type OperationType = String;
50
51pub type OriginType = String;
54
55#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
57#[metastructure(process_func = "process_context")]
58pub enum Context {
59 Device(Box<DeviceContext>),
61 Os(Box<OsContext>),
63 Runtime(Box<RuntimeContext>),
65 App(Box<AppContext>),
67 Browser(Box<BrowserContext>),
69 Gpu(Box<GpuContext>),
71 Trace(Box<TraceContext>),
73 Profile(Box<ProfileContext>),
75 Replay(Box<ReplayContext>),
77 Flags(Box<flags::FlagsContext>),
79 #[metastructure(tag = "feedback")]
81 UserReportV2(Box<UserReportV2Context>),
82 Monitor(Box<MonitorContext>),
84 #[metastructure(omit_from_schema)]
86 Reprocessing(Box<ReprocessingContext>),
87 Response(Box<ResponseContext>),
89 Otel(Box<OtelContext>),
91 CloudResource(Box<CloudResourceContext>),
93 Nel(Box<NelContext>),
95 PerformanceScore(Box<PerformanceScoreContext>),
97 Spring(Box<SpringContext>),
99 OTAUpdates(Box<OTAUpdatesContext>),
101 #[metastructure(fallback_variant)]
103 Other(#[metastructure(pii = "true")] Object<Value>),
104}
105
106#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
107pub struct ContextInner(#[metastructure(max_depth = 7, max_bytes = 8192)] pub Context);
108
109impl From<Context> for ContextInner {
110 fn from(c: Context) -> ContextInner {
111 ContextInner(c)
112 }
113}
114
115#[derive(Clone, Debug, PartialEq, Empty, IntoValue, ProcessValue, Default)]
129#[metastructure(process_func = "process_contexts")]
130pub struct Contexts(pub Object<ContextInner>);
131
132impl Contexts {
133 pub fn new() -> Contexts {
135 Contexts(Object::new())
136 }
137
138 pub fn add<C>(&mut self, context: C)
140 where
141 C: DefaultContext,
142 {
143 self.insert(C::default_key().to_owned(), context.into_context());
144 }
145
146 pub fn insert(&mut self, key: String, context: Context) {
151 self.0.insert(key, Annotated::new(ContextInner(context)));
152 }
153
154 pub fn contains<C>(&self) -> bool
156 where
157 C: DefaultContext,
158 {
159 self.get::<C>().is_some()
161 }
162
163 pub fn contains_key<S>(&self, key: S) -> bool
168 where
169 S: AsRef<str>,
170 {
171 self.0.contains_key(key.as_ref())
172 }
173
174 pub fn get_or_default<C>(&mut self) -> &mut C
176 where
177 C: DefaultContext,
178 {
179 if !self.contains::<C>() {
180 self.add(C::default());
181 }
182
183 self.get_mut().unwrap()
184 }
185
186 pub fn get_or_insert_with<F, S>(&mut self, key: S, context_builder: F) -> &mut Context
191 where
192 F: FnOnce() -> Context,
193 S: Into<String>,
194 {
195 &mut self
196 .0
197 .entry(key.into())
198 .or_insert_with(Annotated::empty)
199 .value_mut()
200 .get_or_insert_with(|| ContextInner(context_builder()))
201 .0
202 }
203
204 pub fn get<C>(&self) -> Option<&C>
206 where
207 C: DefaultContext,
208 {
209 C::cast(self.get_key(C::default_key())?)
210 }
211
212 pub fn get_mut<C>(&mut self) -> Option<&mut C>
214 where
215 C: DefaultContext,
216 {
217 C::cast_mut(self.get_key_mut(C::default_key())?)
218 }
219
220 pub fn get_key<S>(&self, key: S) -> Option<&Context>
225 where
226 S: AsRef<str>,
227 {
228 Some(&self.0.get(key.as_ref())?.value().as_ref()?.0)
229 }
230
231 pub fn get_key_mut<S>(&mut self, key: S) -> Option<&mut Context>
236 where
237 S: AsRef<str>,
238 {
239 Some(&mut self.0.get_mut(key.as_ref())?.value_mut().as_mut()?.0)
240 }
241
242 pub fn remove<C>(&mut self) -> Option<C>
247 where
248 C: DefaultContext,
249 {
250 let context = self.remove_key(C::default_key())?;
251 C::from_context(context)
252 }
253
254 pub fn remove_key<S>(&mut self, key: S) -> Option<Context>
259 where
260 S: AsRef<str>,
261 {
262 let inner = self.0.remove(key.as_ref())?;
263 Some(inner.into_value()?.0)
264 }
265}
266
267impl FromValue for Contexts {
268 fn from_value(mut annotated: Annotated<Value>) -> Annotated<Self> {
269 if let Annotated(Some(Value::Object(ref mut items)), _) = annotated {
270 for (key, value) in items.iter_mut() {
271 if let Annotated(Some(Value::Object(items)), _) = value {
272 if !items.contains_key("type") {
273 items.insert(
274 "type".to_string(),
275 Annotated::new(Value::String(key.to_string())),
276 );
277 }
278 }
279 }
280 }
281 FromValue::from_value(annotated).map_value(Contexts)
282 }
283}
284
285pub trait DefaultContext: Default {
290 fn default_key() -> &'static str;
292
293 fn from_context(context: Context) -> Option<Self>;
297
298 fn cast(context: &Context) -> Option<&Self>;
302
303 fn cast_mut(context: &mut Context) -> Option<&mut Self>;
307
308 fn into_context(self) -> Context;
312}
313
314#[cfg(test)]
315mod tests {
316 use relay_protocol::{Map, Meta};
317
318 use super::*;
319 use crate::processor::{ProcessingResult, ProcessingState, Processor};
320 use crate::protocol::Event;
321
322 #[test]
323 fn test_other_context_roundtrip() {
324 let json = r#"{"other":"value","type":"mytype"}"#;
325 let context = Annotated::new(Context::Other({
326 let mut map = Map::new();
327 map.insert(
328 "other".to_string(),
329 Annotated::new(Value::String("value".to_string())),
330 );
331 map.insert(
332 "type".to_string(),
333 Annotated::new(Value::String("mytype".to_string())),
334 );
335 map
336 }));
337
338 assert_eq!(context, Annotated::from_json(json).unwrap());
339 assert_eq!(json, context.to_json().unwrap());
340 }
341
342 #[test]
343 fn test_untagged_context_deserialize() {
344 let json = r#"{"os": {"name": "Linux"}}"#;
345
346 let mut map = Contexts::new();
347 map.add(OsContext {
348 name: Annotated::new("Linux".to_string()),
349 ..Default::default()
350 });
351
352 assert_eq!(Annotated::new(map), Annotated::from_json(json).unwrap());
353 }
354
355 #[test]
356 fn test_multiple_contexts_roundtrip() {
357 let json =
358 r#"{"os":{"name":"Linux","type":"os"},"runtime":{"name":"rustc","type":"runtime"}}"#;
359
360 let mut map = Contexts::new();
361 map.add(OsContext {
362 name: Annotated::new("Linux".to_string()),
363 ..Default::default()
364 });
365 map.add(RuntimeContext {
366 name: Annotated::new("rustc".to_string()),
367 ..Default::default()
368 });
369
370 let contexts = Annotated::new(map);
371 assert_eq!(contexts, Annotated::from_json(json).unwrap());
372 assert_eq!(json, contexts.to_json().unwrap());
373 }
374
375 #[test]
376 fn test_context_processing() {
377 let mut event = Annotated::new(Event {
378 contexts: {
379 let mut contexts = Contexts::new();
380 contexts.add(RuntimeContext {
381 name: Annotated::new("php".to_owned()),
382 version: Annotated::new("7.1.20-1+ubuntu16.04.1+deb.sury.org+1".to_owned()),
383 ..Default::default()
384 });
385 Annotated::new(contexts)
386 },
387 ..Default::default()
388 });
389
390 struct FooProcessor {
391 called: bool,
392 }
393
394 impl Processor for FooProcessor {
395 #[inline]
396 fn process_context(
397 &mut self,
398 _value: &mut Context,
399 _meta: &mut Meta,
400 _state: &ProcessingState<'_>,
401 ) -> ProcessingResult {
402 self.called = true;
403 Ok(())
404 }
405 }
406
407 let mut processor = FooProcessor { called: false };
408 crate::processor::process_value(&mut event, &mut processor, ProcessingState::root())
409 .unwrap();
410 assert!(processor.called);
411 }
412}