relay_event_schema/protocol/contexts/
mod.rs

1mod 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
53/// A span's operation type.
54///
55/// Tries to follow OpenCensus/OpenTracing's span types. Examples are `db.statement` for database
56/// queries or `http` for external HTTP calls.
57pub type OperationType = String;
58
59/// Origin type such as `auto.http`.
60/// Follows the pattern described in the [develop docs](https://develop.sentry.dev/sdk/performance/trace-origin/).
61pub type OriginType = String;
62
63/// A context describes environment info (e.g. device, os or browser).
64#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
65#[metastructure(process_func = "process_context")]
66pub enum Context {
67    /// Device information.
68    Device(Box<DeviceContext>),
69    /// Operating system information.
70    Os(Box<OsContext>),
71    /// Runtime information.
72    Runtime(Box<RuntimeContext>),
73    /// Application information.
74    App(Box<AppContext>),
75    /// Web browser information.
76    Browser(Box<BrowserContext>),
77    /// Information about device's GPU.
78    Gpu(Box<GpuContext>),
79    /// Information related to Tracing.
80    Trace(Box<TraceContext>),
81    /// Information related to Profiling.
82    Profile(Box<ProfileContext>),
83    /// Information related to Replay.
84    Replay(Box<ReplayContext>),
85    /// Information related to Feature flags.
86    Flags(Box<flags::FlagsContext>),
87    /// Information related to User Report V2. TODO:(jferg): rename to UserFeedbackContext
88    #[metastructure(tag = "feedback")]
89    UserReportV2(Box<UserReportV2Context>),
90    /// Information related to Memory usage and garbage collection metrics.
91    #[metastructure(tag = "memory_info")]
92    MemoryInfo(Box<MemoryInfoContext>),
93    /// Information related to Monitors feature.
94    Monitor(Box<MonitorContext>),
95    /// Auxilliary information for reprocessing.
96    #[metastructure(omit_from_schema)]
97    Reprocessing(Box<ReprocessingContext>),
98    /// Response information.
99    Response(Box<ResponseContext>),
100    /// OpenTelemetry information.
101    Otel(Box<OtelContext>),
102    /// Cloud resource information.
103    CloudResource(Box<CloudResourceContext>),
104    /// Nel information.
105    Nel(Box<NelContext>),
106    /// Performance score information.
107    PerformanceScore(Box<PerformanceScoreContext>),
108    /// Spring / Spring Boot information.
109    Spring(Box<SpringContext>),
110    /// Thread pool information.
111    #[metastructure(tag = "threadpool_info")]
112    ThreadPoolInfo(Box<ThreadPoolInfoContext>),
113    /// OTA Updates information.
114    OTAUpdates(Box<OTAUpdatesContext>),
115    /// Chromium Stability Report from minidump.
116    ChromiumStabilityReport(Box<StabilityReportContext>),
117    /// Unity information.
118    Unity(Box<UnityContext>),
119    /// Additional arbitrary fields for forwards compatibility.
120    #[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/// The Contexts interface provides additional context data. Typically, this is data related to the
134/// current user and the environment. For example, the device or application version. Its canonical
135/// name is `contexts`.
136///
137/// The `contexts` type can be used to define arbitrary contextual data on the event. It accepts an
138/// object of key/value pairs. The key is the “alias” of the context and can be freely chosen.
139/// However, as per policy, it should match the type of the context unless there are two values for
140/// a type. You can omit `type` if the key name is the type.
141///
142/// Unknown data for the contexts is rendered as a key/value list.
143///
144/// For more details about sending additional data with your event, see the [full documentation on
145/// Additional Data](https://docs.sentry.io/enriching-error-data/additional-data/).
146#[derive(Clone, Debug, PartialEq, Empty, IntoValue, ProcessValue, Default)]
147#[metastructure(process_func = "process_contexts")]
148pub struct Contexts(pub Object<ContextInner>);
149
150impl Contexts {
151    /// Creates an empty contexts map.
152    pub fn new() -> Contexts {
153        Contexts(Object::new())
154    }
155
156    /// Inserts a context under the default key for the context.
157    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    /// Inserts a context under a custom given key.
165    ///
166    /// By convention, every typed context has a default key. Use [`add`](Self::add) to insert such
167    /// contexts, instead.
168    pub fn insert(&mut self, key: String, context: Context) {
169        self.0.insert(key, Annotated::new(ContextInner(context)));
170    }
171
172    /// Returns `true` if a matching context resides in the map at its default key.
173    pub fn contains<C>(&self) -> bool
174    where
175        C: DefaultContext,
176    {
177        // Use `get` to perform a type check.
178        self.get::<C>().is_some()
179    }
180
181    /// Returns `true` if a context with the provided key is present in the map.
182    ///
183    /// By convention, every typed context has a default key. Use [`contains`](Self::contains) to
184    /// check such contexts, instead.
185    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    /// Returns the context at its default key or constructs it if not present.
193    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    /// Returns the context at the specified key or constructs it if not present.
205    ///
206    /// By convention, every typed context has a default key. Use
207    /// [`get_or_default`](Self::get_or_default) to insert such contexts, instead.
208    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    /// Returns a reference to the default context by type.
223    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    /// Returns a mutable reference to the default context by type.
231    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    /// Returns a reference to the context specified by `key`.
239    ///
240    /// By convention, every typed context has a default key. Use [`get`](Self::get) to retrieve
241    /// such contexts, instead.
242    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    /// Returns a mutable reference to the context specified by `key`.
250    ///
251    /// By convention, every typed context has a default key. Use [`get_mut`](Self::get_mut) to
252    /// retrieve such contexts, instead.
253    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    /// Removes a context from the map, returning the context it was previously in the map.
261    ///
262    /// Returns `Some` if a matching context was removed from the default key. If the context at the
263    /// default key does not have a matching type, it is removed but `None` is returned.
264    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    /// Removes a context from the map, returning the context it was previously in the map.
273    ///
274    /// By convention, every typed context has a default key. Use [`remove`](Self::remove) to
275    /// retrieve such contexts, instead.
276    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                    // Set the `"type"` if it's empty and overwrite it if it's an empty object or array.
291                    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
304/// Returns `true` if `value` is a non-empty string, which is the only valid value
305/// for the `"type"` field of a context.
306fn 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
310/// A well-known context in the [`Contexts`] interface.
311///
312/// These contexts have a [default key](Self::default_key) in the contexts map and can be
313/// constructed as an empty default value.
314pub trait DefaultContext: Default {
315    /// The default key at which this context resides in [`Contexts`].
316    fn default_key() -> &'static str;
317
318    /// Converts this context type from a generic context type.
319    ///
320    /// Returns `Some` if the context is of this type. Otherwise, returns `None`.
321    fn from_context(context: Context) -> Option<Self>;
322
323    /// Casts a reference to this context type from a generic context type.
324    ///
325    /// Returns `Some` if the context is of this type. Otherwise, returns `None`.
326    fn cast(context: &Context) -> Option<&Self>;
327
328    /// Casts a mutable reference to this context type from a generic context type.
329    ///
330    /// Returns `Some` if the context is of this type. Otherwise, returns `None`.
331    fn cast_mut(context: &mut Context) -> Option<&mut Self>;
332
333    /// Boxes this context type in the generic context wrapper.
334    ///
335    /// Returns `Some` if the context is of this type. Otherwise, returns `None`.
336    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}