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 otel;
11mod performance_score;
12mod profile;
13mod replay;
14mod reprocessing;
15mod response;
16mod runtime;
17mod spring;
18mod trace;
19mod user_report_v2;
20pub use app::*;
21pub use browser::*;
22pub use cloud_resource::*;
23pub use device::*;
24pub use gpu::*;
25pub use monitor::*;
26pub use nel::*;
27pub use os::*;
28pub use otel::*;
29pub use performance_score::*;
30pub use profile::*;
31pub use replay::*;
32pub use reprocessing::*;
33pub use response::*;
34pub use runtime::*;
35pub use spring::*;
36pub use trace::*;
37pub use user_report_v2::*;
38
39use relay_protocol::{Annotated, Empty, FromValue, IntoValue, Object, Value};
40
41use crate::processor::ProcessValue;
42
43/// A span's operation type.
44///
45/// Tries to follow OpenCensus/OpenTracing's span types. Examples are `db.statement` for database
46/// queries or `http` for external HTTP calls.
47pub type OperationType = String;
48
49/// Origin type such as `auto.http`.
50/// Follows the pattern described in the [develop docs](https://develop.sentry.dev/sdk/performance/trace-origin/).
51pub type OriginType = String;
52
53/// A context describes environment info (e.g. device, os or browser).
54#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
55#[metastructure(process_func = "process_context")]
56pub enum Context {
57    /// Device information.
58    Device(Box<DeviceContext>),
59    /// Operating system information.
60    Os(Box<OsContext>),
61    /// Runtime information.
62    Runtime(Box<RuntimeContext>),
63    /// Application information.
64    App(Box<AppContext>),
65    /// Web browser information.
66    Browser(Box<BrowserContext>),
67    /// Information about device's GPU.
68    Gpu(Box<GpuContext>),
69    /// Information related to Tracing.
70    Trace(Box<TraceContext>),
71    /// Information related to Profiling.
72    Profile(Box<ProfileContext>),
73    /// Information related to Replay.
74    Replay(Box<ReplayContext>),
75    /// Information related to Feature flags.
76    Flags(Box<flags::FlagsContext>),
77    /// Information related to User Report V2. TODO:(jferg): rename to UserFeedbackContext
78    #[metastructure(tag = "feedback")]
79    UserReportV2(Box<UserReportV2Context>),
80    /// Information related to Monitors feature.
81    Monitor(Box<MonitorContext>),
82    /// Auxilliary information for reprocessing.
83    #[metastructure(omit_from_schema)]
84    Reprocessing(Box<ReprocessingContext>),
85    /// Response information.
86    Response(Box<ResponseContext>),
87    /// OpenTelemetry information.
88    Otel(Box<OtelContext>),
89    /// Cloud resource information.
90    CloudResource(Box<CloudResourceContext>),
91    /// Nel information.
92    Nel(Box<NelContext>),
93    /// Performance score information.
94    PerformanceScore(Box<PerformanceScoreContext>),
95    /// Spring / Spring Boot information.
96    Spring(Box<SpringContext>),
97    /// Additional arbitrary fields for forwards compatibility.
98    #[metastructure(fallback_variant)]
99    Other(#[metastructure(pii = "true")] Object<Value>),
100}
101
102#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
103pub struct ContextInner(#[metastructure(max_depth = 7, max_bytes = 8192)] pub Context);
104
105impl From<Context> for ContextInner {
106    fn from(c: Context) -> ContextInner {
107        ContextInner(c)
108    }
109}
110
111/// The Contexts interface provides additional context data. Typically, this is data related to the
112/// current user and the environment. For example, the device or application version. Its canonical
113/// name is `contexts`.
114///
115/// The `contexts` type can be used to define arbitrary contextual data on the event. It accepts an
116/// object of key/value pairs. The key is the “alias” of the context and can be freely chosen.
117/// However, as per policy, it should match the type of the context unless there are two values for
118/// a type. You can omit `type` if the key name is the type.
119///
120/// Unknown data for the contexts is rendered as a key/value list.
121///
122/// For more details about sending additional data with your event, see the [full documentation on
123/// Additional Data](https://docs.sentry.io/enriching-error-data/additional-data/).
124#[derive(Clone, Debug, PartialEq, Empty, IntoValue, ProcessValue, Default)]
125#[metastructure(process_func = "process_contexts")]
126pub struct Contexts(pub Object<ContextInner>);
127
128impl Contexts {
129    /// Creates an empty contexts map.
130    pub fn new() -> Contexts {
131        Contexts(Object::new())
132    }
133
134    /// Inserts a context under the default key for the context.
135    pub fn add<C>(&mut self, context: C)
136    where
137        C: DefaultContext,
138    {
139        self.insert(C::default_key().to_owned(), context.into_context());
140    }
141
142    /// Inserts a context under a custom given key.
143    ///
144    /// By convention, every typed context has a default key. Use [`add`](Self::add) to insert such
145    /// contexts, instead.
146    pub fn insert(&mut self, key: String, context: Context) {
147        self.0.insert(key, Annotated::new(ContextInner(context)));
148    }
149
150    /// Returns `true` if a matching context resides in the map at its default key.
151    pub fn contains<C>(&self) -> bool
152    where
153        C: DefaultContext,
154    {
155        // Use `get` to perform a type check.
156        self.get::<C>().is_some()
157    }
158
159    /// Returns `true` if a context with the provided key is present in the map.
160    ///
161    /// By convention, every typed context has a default key. Use [`contains`](Self::contains) to
162    /// check such contexts, instead.
163    pub fn contains_key<S>(&self, key: S) -> bool
164    where
165        S: AsRef<str>,
166    {
167        self.0.contains_key(key.as_ref())
168    }
169
170    /// Returns the context at its default key or constructs it if not present.
171    pub fn get_or_default<C>(&mut self) -> &mut C
172    where
173        C: DefaultContext,
174    {
175        if !self.contains::<C>() {
176            self.add(C::default());
177        }
178
179        self.get_mut().unwrap()
180    }
181
182    /// Returns the context at the specified key or constructs it if not present.
183    ///
184    /// By convention, every typed context has a default key. Use
185    /// [`get_or_default`](Self::get_or_default) to insert such contexts, instead.
186    pub fn get_or_insert_with<F, S>(&mut self, key: S, context_builder: F) -> &mut Context
187    where
188        F: FnOnce() -> Context,
189        S: Into<String>,
190    {
191        &mut self
192            .0
193            .entry(key.into())
194            .or_insert_with(Annotated::empty)
195            .value_mut()
196            .get_or_insert_with(|| ContextInner(context_builder()))
197            .0
198    }
199
200    /// Returns a reference to the default context by type.
201    pub fn get<C>(&self) -> Option<&C>
202    where
203        C: DefaultContext,
204    {
205        C::cast(self.get_key(C::default_key())?)
206    }
207
208    /// Returns a mutable reference to the default context by type.
209    pub fn get_mut<C>(&mut self) -> Option<&mut C>
210    where
211        C: DefaultContext,
212    {
213        C::cast_mut(self.get_key_mut(C::default_key())?)
214    }
215
216    /// Returns a reference to the context specified by `key`.
217    ///
218    /// By convention, every typed context has a default key. Use [`get`](Self::get) to retrieve
219    /// such contexts, instead.
220    pub fn get_key<S>(&self, key: S) -> Option<&Context>
221    where
222        S: AsRef<str>,
223    {
224        Some(&self.0.get(key.as_ref())?.value().as_ref()?.0)
225    }
226
227    /// Returns a mutable reference to the context specified by `key`.
228    ///
229    /// By convention, every typed context has a default key. Use [`get_mut`](Self::get_mut) to
230    /// retrieve such contexts, instead.
231    pub fn get_key_mut<S>(&mut self, key: S) -> Option<&mut Context>
232    where
233        S: AsRef<str>,
234    {
235        Some(&mut self.0.get_mut(key.as_ref())?.value_mut().as_mut()?.0)
236    }
237
238    /// Removes a context from the map, returning the context it was previously in the map.
239    ///
240    /// Returns `Some` if a matching context was removed from the default key. If the context at the
241    /// default key does not have a matching type, it is removed but `None` is returned.
242    pub fn remove<C>(&mut self) -> Option<C>
243    where
244        C: DefaultContext,
245    {
246        let context = self.remove_key(C::default_key())?;
247        C::from_context(context)
248    }
249
250    /// Removes a context from the map, returning the context it was previously in the map.
251    ///
252    /// By convention, every typed context has a default key. Use [`remove`](Self::remove) to
253    /// retrieve such contexts, instead.
254    pub fn remove_key<S>(&mut self, key: S) -> Option<Context>
255    where
256        S: AsRef<str>,
257    {
258        let inner = self.0.remove(key.as_ref())?;
259        Some(inner.into_value()?.0)
260    }
261}
262
263impl FromValue for Contexts {
264    fn from_value(mut annotated: Annotated<Value>) -> Annotated<Self> {
265        if let Annotated(Some(Value::Object(ref mut items)), _) = annotated {
266            for (key, value) in items.iter_mut() {
267                if let Annotated(Some(Value::Object(ref mut items)), _) = value {
268                    if !items.contains_key("type") {
269                        items.insert(
270                            "type".to_string(),
271                            Annotated::new(Value::String(key.to_string())),
272                        );
273                    }
274                }
275            }
276        }
277        FromValue::from_value(annotated).map_value(Contexts)
278    }
279}
280
281/// A well-known context in the [`Contexts`] interface.
282///
283/// These contexts have a [default key](Self::default_key) in the contexts map and can be
284/// constructed as an empty default value.
285pub trait DefaultContext: Default {
286    /// The default key at which this context resides in [`Contexts`].
287    fn default_key() -> &'static str;
288
289    /// Converts this context type from a generic context type.
290    ///
291    /// Returns `Some` if the context is of this type. Otherwise, returns `None`.
292    fn from_context(context: Context) -> Option<Self>;
293
294    /// Casts a reference to this context type from a generic context type.
295    ///
296    /// Returns `Some` if the context is of this type. Otherwise, returns `None`.
297    fn cast(context: &Context) -> Option<&Self>;
298
299    /// Casts a mutable reference to this context type from a generic context type.
300    ///
301    /// Returns `Some` if the context is of this type. Otherwise, returns `None`.
302    fn cast_mut(context: &mut Context) -> Option<&mut Self>;
303
304    /// Boxes this context type in the generic context wrapper.
305    ///
306    /// Returns `Some` if the context is of this type. Otherwise, returns `None`.
307    fn into_context(self) -> Context;
308}
309
310#[cfg(test)]
311mod tests {
312    use relay_protocol::{Map, Meta};
313
314    use super::*;
315    use crate::processor::{ProcessingResult, ProcessingState, Processor};
316    use crate::protocol::Event;
317
318    #[test]
319    fn test_other_context_roundtrip() {
320        let json = r#"{"other":"value","type":"mytype"}"#;
321        let context = Annotated::new(Context::Other({
322            let mut map = Map::new();
323            map.insert(
324                "other".to_string(),
325                Annotated::new(Value::String("value".to_string())),
326            );
327            map.insert(
328                "type".to_string(),
329                Annotated::new(Value::String("mytype".to_string())),
330            );
331            map
332        }));
333
334        assert_eq!(context, Annotated::from_json(json).unwrap());
335        assert_eq!(json, context.to_json().unwrap());
336    }
337
338    #[test]
339    fn test_untagged_context_deserialize() {
340        let json = r#"{"os": {"name": "Linux"}}"#;
341
342        let mut map = Contexts::new();
343        map.add(OsContext {
344            name: Annotated::new("Linux".to_string()),
345            ..Default::default()
346        });
347
348        assert_eq!(Annotated::new(map), Annotated::from_json(json).unwrap());
349    }
350
351    #[test]
352    fn test_multiple_contexts_roundtrip() {
353        let json =
354            r#"{"os":{"name":"Linux","type":"os"},"runtime":{"name":"rustc","type":"runtime"}}"#;
355
356        let mut map = Contexts::new();
357        map.add(OsContext {
358            name: Annotated::new("Linux".to_string()),
359            ..Default::default()
360        });
361        map.add(RuntimeContext {
362            name: Annotated::new("rustc".to_string()),
363            ..Default::default()
364        });
365
366        let contexts = Annotated::new(map);
367        assert_eq!(contexts, Annotated::from_json(json).unwrap());
368        assert_eq!(json, contexts.to_json().unwrap());
369    }
370
371    #[test]
372    fn test_context_processing() {
373        let mut event = Annotated::new(Event {
374            contexts: {
375                let mut contexts = Contexts::new();
376                contexts.add(RuntimeContext {
377                    name: Annotated::new("php".to_owned()),
378                    version: Annotated::new("7.1.20-1+ubuntu16.04.1+deb.sury.org+1".to_owned()),
379                    ..Default::default()
380                });
381                Annotated::new(contexts)
382            },
383            ..Default::default()
384        });
385
386        struct FooProcessor {
387            called: bool,
388        }
389
390        impl Processor for FooProcessor {
391            #[inline]
392            fn process_context(
393                &mut self,
394                _value: &mut Context,
395                _meta: &mut Meta,
396                _state: &ProcessingState<'_>,
397            ) -> ProcessingResult {
398                self.called = true;
399                Ok(())
400            }
401        }
402
403        let mut processor = FooProcessor { called: false };
404        crate::processor::process_value(&mut event, &mut processor, ProcessingState::root())
405            .unwrap();
406        assert!(processor.called);
407    }
408}