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 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
47/// A span's operation type.
48///
49/// Tries to follow OpenCensus/OpenTracing's span types. Examples are `db.statement` for database
50/// queries or `http` for external HTTP calls.
51pub type OperationType = String;
52
53/// Origin type such as `auto.http`.
54/// Follows the pattern described in the [develop docs](https://develop.sentry.dev/sdk/performance/trace-origin/).
55pub type OriginType = String;
56
57/// A context describes environment info (e.g. device, os or browser).
58#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
59#[metastructure(process_func = "process_context")]
60pub enum Context {
61    /// Device information.
62    Device(Box<DeviceContext>),
63    /// Operating system information.
64    Os(Box<OsContext>),
65    /// Runtime information.
66    Runtime(Box<RuntimeContext>),
67    /// Application information.
68    App(Box<AppContext>),
69    /// Web browser information.
70    Browser(Box<BrowserContext>),
71    /// Information about device's GPU.
72    Gpu(Box<GpuContext>),
73    /// Information related to Tracing.
74    Trace(Box<TraceContext>),
75    /// Information related to Profiling.
76    Profile(Box<ProfileContext>),
77    /// Information related to Replay.
78    Replay(Box<ReplayContext>),
79    /// Information related to Feature flags.
80    Flags(Box<flags::FlagsContext>),
81    /// Information related to User Report V2. TODO:(jferg): rename to UserFeedbackContext
82    #[metastructure(tag = "feedback")]
83    UserReportV2(Box<UserReportV2Context>),
84    /// Information related to Monitors feature.
85    Monitor(Box<MonitorContext>),
86    /// Auxilliary information for reprocessing.
87    #[metastructure(omit_from_schema)]
88    Reprocessing(Box<ReprocessingContext>),
89    /// Response information.
90    Response(Box<ResponseContext>),
91    /// OpenTelemetry information.
92    Otel(Box<OtelContext>),
93    /// Cloud resource information.
94    CloudResource(Box<CloudResourceContext>),
95    /// Nel information.
96    Nel(Box<NelContext>),
97    /// Performance score information.
98    PerformanceScore(Box<PerformanceScoreContext>),
99    /// Spring / Spring Boot information.
100    Spring(Box<SpringContext>),
101    /// OTA Updates information.
102    OTAUpdates(Box<OTAUpdatesContext>),
103    /// Chromium Stability Report from minidump.
104    ChromiumStabilityReport(Box<StabilityReportContext>),
105    /// Additional arbitrary fields for forwards compatibility.
106    #[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/// The Contexts interface provides additional context data. Typically, this is data related to the
120/// current user and the environment. For example, the device or application version. Its canonical
121/// name is `contexts`.
122///
123/// The `contexts` type can be used to define arbitrary contextual data on the event. It accepts an
124/// object of key/value pairs. The key is the “alias” of the context and can be freely chosen.
125/// However, as per policy, it should match the type of the context unless there are two values for
126/// a type. You can omit `type` if the key name is the type.
127///
128/// Unknown data for the contexts is rendered as a key/value list.
129///
130/// For more details about sending additional data with your event, see the [full documentation on
131/// Additional Data](https://docs.sentry.io/enriching-error-data/additional-data/).
132#[derive(Clone, Debug, PartialEq, Empty, IntoValue, ProcessValue, Default)]
133#[metastructure(process_func = "process_contexts")]
134pub struct Contexts(pub Object<ContextInner>);
135
136impl Contexts {
137    /// Creates an empty contexts map.
138    pub fn new() -> Contexts {
139        Contexts(Object::new())
140    }
141
142    /// Inserts a context under the default key for the context.
143    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    /// Inserts a context under a custom given key.
151    ///
152    /// By convention, every typed context has a default key. Use [`add`](Self::add) to insert such
153    /// contexts, instead.
154    pub fn insert(&mut self, key: String, context: Context) {
155        self.0.insert(key, Annotated::new(ContextInner(context)));
156    }
157
158    /// Returns `true` if a matching context resides in the map at its default key.
159    pub fn contains<C>(&self) -> bool
160    where
161        C: DefaultContext,
162    {
163        // Use `get` to perform a type check.
164        self.get::<C>().is_some()
165    }
166
167    /// Returns `true` if a context with the provided key is present in the map.
168    ///
169    /// By convention, every typed context has a default key. Use [`contains`](Self::contains) to
170    /// check such contexts, instead.
171    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    /// Returns the context at its default key or constructs it if not present.
179    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    /// Returns the context at the specified key or constructs it if not present.
191    ///
192    /// By convention, every typed context has a default key. Use
193    /// [`get_or_default`](Self::get_or_default) to insert such contexts, instead.
194    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    /// Returns a reference to the default context by type.
209    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    /// Returns a mutable reference to the default context by type.
217    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    /// Returns a reference to the context specified by `key`.
225    ///
226    /// By convention, every typed context has a default key. Use [`get`](Self::get) to retrieve
227    /// such contexts, instead.
228    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    /// Returns a mutable reference to the context specified by `key`.
236    ///
237    /// By convention, every typed context has a default key. Use [`get_mut`](Self::get_mut) to
238    /// retrieve such contexts, instead.
239    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    /// Removes a context from the map, returning the context it was previously in the map.
247    ///
248    /// Returns `Some` if a matching context was removed from the default key. If the context at the
249    /// default key does not have a matching type, it is removed but `None` is returned.
250    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    /// Removes a context from the map, returning the context it was previously in the map.
259    ///
260    /// By convention, every typed context has a default key. Use [`remove`](Self::remove) to
261    /// retrieve such contexts, instead.
262    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                    // Set the `"type"` if it's empty and overwrite it if it's an empty object or array.
277                    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
290/// Returns `true` if `value` is a non-empty string, which is the only valid value
291/// for the `"type"` field of a context.
292fn 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
296/// A well-known context in the [`Contexts`] interface.
297///
298/// These contexts have a [default key](Self::default_key) in the contexts map and can be
299/// constructed as an empty default value.
300pub trait DefaultContext: Default {
301    /// The default key at which this context resides in [`Contexts`].
302    fn default_key() -> &'static str;
303
304    /// Converts this context type from a generic context type.
305    ///
306    /// Returns `Some` if the context is of this type. Otherwise, returns `None`.
307    fn from_context(context: Context) -> Option<Self>;
308
309    /// Casts a reference to this context type from a generic context type.
310    ///
311    /// Returns `Some` if the context is of this type. Otherwise, returns `None`.
312    fn cast(context: &Context) -> Option<&Self>;
313
314    /// Casts a mutable reference to this context type from a generic context type.
315    ///
316    /// Returns `Some` if the context is of this type. Otherwise, returns `None`.
317    fn cast_mut(context: &mut Context) -> Option<&mut Self>;
318
319    /// Boxes this context type in the generic context wrapper.
320    ///
321    /// Returns `Some` if the context is of this type. Otherwise, returns `None`.
322    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}