relay_event_schema/protocol/contexts/
mod.rs

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