Skip to main content

relay_event_schema/protocol/
event.rs

1use std::fmt;
2use std::str::FromStr;
3
4use relay_common::time;
5use relay_protocol::{
6    Annotated, Array, Empty, FiniteF64, FromValue, Getter, GetterIter, IntoValue, Object, Val,
7    Value,
8};
9use sentry_release_parser::Release as ParsedRelease;
10use uuid::Uuid;
11
12use crate::processor::ProcessValue;
13use crate::protocol::{
14    AppContext, Breadcrumb, Breakdowns, BrowserContext, ClientSdkInfo, Contexts, Csp, DebugMeta,
15    DefaultContext, DeviceContext, EventType, Exception, ExpectCt, ExpectStaple, Fingerprint,
16    GpuContext, Hpkp, LenientString, Level, LogEntry, Measurements, Metrics, MonitorContext,
17    OsContext, ProfileContext, RelayInfo, Request, ResponseContext, RuntimeContext, Span, SpanId,
18    Stacktrace, Tags, TemplateInfo, Thread, Timestamp, TraceContext, TransactionInfo, User, Values,
19};
20
21/// Wrapper around a UUID with slightly different formatting.
22#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct EventId(pub Uuid);
24
25impl EventId {
26    /// Creates a new event id using a UUID v4.
27    #[inline]
28    pub fn new() -> Self {
29        Self(Uuid::new_v4())
30    }
31
32    /// Creates a new completely zeroed event id.
33    pub fn nil() -> Self {
34        Self(Uuid::nil())
35    }
36
37    /// Tests if the UUID is nil.
38    #[inline]
39    pub fn is_nil(&self) -> bool {
40        self.0.is_nil()
41    }
42}
43
44impl Default for EventId {
45    #[inline]
46    fn default() -> Self {
47        Self::new()
48    }
49}
50
51relay_protocol::derive_string_meta_structure!(EventId, "event id");
52
53impl ProcessValue for EventId {}
54
55impl fmt::Display for EventId {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        write!(f, "{}", self.0.as_simple())
58    }
59}
60
61impl FromStr for EventId {
62    type Err = <Uuid as FromStr>::Err;
63
64    fn from_str(uuid_str: &str) -> Result<Self, Self::Err> {
65        uuid_str.parse().map(EventId)
66    }
67}
68
69relay_common::impl_str_serde!(EventId, "an event identifier");
70
71impl TryFrom<&SpanId> for EventId {
72    type Error = <EventId as FromStr>::Err;
73
74    fn try_from(value: &SpanId) -> Result<Self, Self::Error> {
75        // TODO: Represent SpanId as bytes / u64 so we can call `Uuid::from_u64_pair`.
76        let s = format!("0000000000000000{value}");
77        s.parse()
78    }
79}
80
81#[derive(Debug, FromValue, IntoValue, ProcessValue, Empty, Clone, PartialEq)]
82pub struct ExtraValue(#[metastructure(max_depth = 7, max_bytes = 16_384)] pub Value);
83
84impl<T: Into<Value>> From<T> for ExtraValue {
85    fn from(value: T) -> ExtraValue {
86        ExtraValue(value.into())
87    }
88}
89
90/// An event processing error.
91#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
92pub struct EventProcessingError {
93    /// The error kind.
94    #[metastructure(field = "type", required = true)]
95    pub ty: Annotated<String>,
96
97    /// Affected key or deep path.
98    pub name: Annotated<String>,
99
100    /// The original value causing this error.
101    pub value: Annotated<Value>,
102
103    /// Additional data explaining this error.
104    #[metastructure(additional_properties, pii = "maybe")]
105    pub other: Object<Value>,
106}
107
108/// The grouping config that should be used for grouping this event.
109///
110/// This is currently only supplied as part of normalization and the payload
111/// only permits the ID of the algorithm to be set and no parameters yet.
112#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
113pub struct GroupingConfig {
114    /// The id of the grouping config.
115    #[metastructure(max_chars = 128)]
116    pub id: Annotated<String>,
117    /// The enhancements configuration.
118    pub enhancements: Annotated<String>,
119}
120
121/// The sentry v7 event structure.
122#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
123#[metastructure(process_func = "process_event", value_type = "Event")]
124pub struct Event {
125    /// Unique identifier of this event.
126    ///
127    /// Hexadecimal string representing a uuid4 value. The length is exactly 32 characters. Dashes
128    /// are not allowed. Has to be lowercase.
129    ///
130    /// Even though this field is backfilled on the server with a new uuid4, it is strongly
131    /// recommended to generate that uuid4 clientside. There are some features like user feedback
132    /// which are easier to implement that way, and debugging in case events get lost in your
133    /// Sentry installation is also easier.
134    ///
135    /// Example:
136    ///
137    /// ```json
138    /// {
139    ///   "event_id": "fc6d8c0c43fc4630ad850ee518f1b9d0"
140    /// }
141    /// ```
142    #[metastructure(field = "event_id")]
143    pub id: Annotated<EventId>,
144
145    /// Severity level of the event. Defaults to `error`.
146    ///
147    /// Example:
148    ///
149    /// ```json
150    /// {"level": "warning"}
151    /// ```
152    pub level: Annotated<Level>,
153
154    /// Version
155    pub version: Annotated<String>,
156
157    /// Type of the event. Defaults to `default`.
158    ///
159    /// The event type determines how Sentry handles the event and has an impact on processing, rate
160    /// limiting, and quotas. There are three fundamental classes of event types:
161    ///
162    ///  - **Error monitoring events**: Processed and grouped into unique issues based on their
163    ///    exception stack traces and error messages.
164    ///  - **Security events**: Derived from Browser security violation reports and grouped into
165    ///    unique issues based on the endpoint and violation. SDKs do not send such events.
166    ///  - **Transaction events** (`transaction`): Contain operation spans and collected into traces
167    ///    for performance monitoring.
168    ///
169    /// Transactions must explicitly specify the `"transaction"` event type. In all other cases,
170    /// Sentry infers the appropriate event type from the payload and overrides the stated type.
171    /// SDKs should not send an event type other than for transactions.
172    ///
173    /// Example:
174    ///
175    /// ```json
176    /// {
177    ///   "type": "transaction",
178    ///   "spans": []
179    /// }
180    /// ```
181    #[metastructure(field = "type")]
182    pub ty: Annotated<EventType>,
183
184    /// Manual fingerprint override.
185    ///
186    /// A list of strings used to dictate how this event is supposed to be grouped with other
187    /// events into issues. For more information about overriding grouping see [Customize Grouping
188    /// with Fingerprints](https://docs.sentry.io/data-management/event-grouping/).
189    ///
190    /// ```json
191    /// {
192    ///     "fingerprint": ["myrpc", "POST", "/foo.bar"]
193    /// }
194    #[metastructure(skip_serialization = "empty")]
195    pub fingerprint: Annotated<Fingerprint>,
196
197    /// Custom culprit of the event.
198    ///
199    /// This field is deprecated and shall not be set by client SDKs.
200    #[metastructure(max_chars = 200, pii = "maybe")]
201    pub culprit: Annotated<String>,
202
203    /// Transaction name of the event.
204    ///
205    /// For example, in a web app, this might be the route name (`"/users/<username>/"` or
206    /// `UserView`), in a task queue it might be the function + module name.
207    #[metastructure(max_chars = 200, trim_whitespace = true)]
208    pub transaction: Annotated<String>,
209
210    /// Additional information about the name of the transaction.
211    #[metastructure(skip_serialization = "null")]
212    pub transaction_info: Annotated<TransactionInfo>,
213
214    /// Time since the start of the transaction until the error occurred.
215    pub time_spent: Annotated<u64>,
216
217    /// Custom parameterized message for this event.
218    #[metastructure(legacy_alias = "sentry.interfaces.Message", legacy_alias = "message")]
219    #[metastructure(skip_serialization = "empty")]
220    pub logentry: Annotated<LogEntry>,
221
222    /// Logger that created the event.
223    #[metastructure(
224        max_chars = 64, // DB-imposed limit
225        deny_chars = "\r\n",
226    )]
227    pub logger: Annotated<String>,
228
229    /// Name and versions of all installed modules/packages/dependencies in the current
230    /// environment/application.
231    ///
232    /// ```json
233    /// { "django": "3.0.0", "celery": "4.2.1" }
234    /// ```
235    ///
236    /// In Python this is a list of installed packages as reported by `pkg_resources` together with
237    /// their reported version string.
238    ///
239    /// This is primarily used for suggesting to enable certain SDK integrations from within the UI
240    /// and for making informed decisions on which frameworks to support in future development
241    /// efforts.
242    #[metastructure(skip_serialization = "empty_deep", max_depth = 7, max_bytes = 8192)]
243    pub modules: Annotated<Object<String>>,
244
245    /// Platform identifier of this event (defaults to "other").
246    ///
247    /// A string representing the platform the SDK is submitting from. This will be used by the
248    /// Sentry interface to customize various components in the interface, but also to enter or
249    /// skip stacktrace processing.
250    ///
251    /// Acceptable values are: `as3`, `c`, `cfml`, `cocoa`, `csharp`, `elixir`, `haskell`, `go`,
252    /// `groovy`, `java`, `javascript`, `native`, `node`, `objc`, `other`, `perl`, `php`, `python`,
253    /// `ruby`
254    pub platform: Annotated<String>,
255
256    /// Timestamp when the event was created.
257    ///
258    /// Indicates when the event was created in the Sentry SDK. The format is either a string as
259    /// defined in [RFC 3339](https://tools.ietf.org/html/rfc3339) or a numeric (integer or float)
260    /// value representing the number of seconds that have elapsed since the [Unix
261    /// epoch](https://en.wikipedia.org/wiki/Unix_time).
262    ///
263    /// Timezone is assumed to be UTC if missing.
264    ///
265    /// Sub-microsecond precision is not preserved with numeric values due to precision
266    /// limitations with floats (at least in our systems). With that caveat in mind, just send
267    /// whatever is easiest to produce.
268    ///
269    /// All timestamps in the event protocol are formatted this way.
270    ///
271    /// # Example
272    ///
273    /// All of these are the same date:
274    ///
275    /// ```json
276    /// { "timestamp": "2011-05-02T17:41:36Z" }
277    /// { "timestamp": "2011-05-02T17:41:36" }
278    /// { "timestamp": "2011-05-02T17:41:36.000" }
279    /// { "timestamp": 1304358096.0 }
280    /// ```
281    pub timestamp: Annotated<Timestamp>,
282
283    /// Timestamp when the event has started (relevant for event type = "transaction")
284    #[metastructure(omit_from_schema)] // we only document error events for now
285    pub start_timestamp: Annotated<Timestamp>,
286
287    /// Timestamp when the event has been received by Sentry.
288    pub received: Annotated<Timestamp>,
289
290    /// Server or device name the event was generated on.
291    ///
292    /// This is supposed to be a hostname.
293    #[metastructure(pii = "true", max_chars = 256, max_chars_allowance = 20)]
294    pub server_name: Annotated<String>,
295
296    /// The release version of the application.
297    ///
298    /// **Release versions must be unique across all projects in your organization.** This value
299    /// can be the git SHA for the given project, or a product identifier with a semantic version.
300    #[metastructure(
301        max_chars = 200,  // release ends in tag
302        // release allowed chars are validated in the sentry-release-parser crate!
303        required = false,
304        trim_whitespace = true,
305        nonempty = true,
306        skip_serialization = "empty"
307    )]
308    pub release: Annotated<LenientString>,
309
310    /// Program's distribution identifier.
311    ///
312    /// The distribution of the application.
313    ///
314    /// Distributions are used to disambiguate build or deployment variants of the same release of
315    /// an application. For example, the dist can be the build number of an XCode build or the
316    /// version code of an Android build.
317    #[metastructure(
318        allow_chars = "a-zA-Z0-9_.-",
319        trim_whitespace = true,
320        required = false,
321        nonempty = true
322    )]
323    pub dist: Annotated<String>,
324
325    /// The environment name, such as `production` or `staging`.
326    ///
327    /// ```json
328    /// { "environment": "production" }
329    /// ```
330    #[metastructure(
331        max_chars = 64,
332        // environment allowed chars are validated in the sentry-release-parser crate!
333        nonempty = true,
334        required = false,
335        trim_whitespace = true
336    )]
337    pub environment: Annotated<String>,
338
339    /// Deprecated in favor of tags.
340    #[metastructure(max_chars = 256, max_chars_allowance = 20)]
341    #[metastructure(omit_from_schema)] // deprecated
342    pub site: Annotated<String>,
343
344    /// Information about the user who triggered this event.
345    #[metastructure(legacy_alias = "sentry.interfaces.User")]
346    #[metastructure(skip_serialization = "empty")]
347    pub user: Annotated<User>,
348
349    /// Information about a web request that occurred during the event.
350    #[metastructure(legacy_alias = "sentry.interfaces.Http")]
351    #[metastructure(skip_serialization = "empty")]
352    pub request: Annotated<Request>,
353
354    /// Contexts describing the environment (e.g. device, os or browser).
355    #[metastructure(legacy_alias = "sentry.interfaces.Contexts")]
356    pub contexts: Annotated<Contexts>,
357
358    /// List of breadcrumbs recorded before this event.
359    #[metastructure(legacy_alias = "sentry.interfaces.Breadcrumbs")]
360    #[metastructure(skip_serialization = "empty")]
361    pub breadcrumbs: Annotated<Values<Breadcrumb>>,
362
363    /// One or multiple chained (nested) exceptions.
364    #[metastructure(legacy_alias = "sentry.interfaces.Exception")]
365    #[metastructure(field = "exception")]
366    #[metastructure(skip_serialization = "empty")]
367    pub exceptions: Annotated<Values<Exception>>,
368
369    /// Event stacktrace.
370    ///
371    /// DEPRECATED: Prefer `threads` or `exception` depending on which is more appropriate.
372    #[metastructure(skip_serialization = "empty")]
373    #[metastructure(legacy_alias = "sentry.interfaces.Stacktrace")]
374    pub stacktrace: Annotated<Stacktrace>,
375
376    /// Simplified template error location information.
377    /// DEPRECATED: Non-Raven clients are not supposed to send this anymore, but rather just report
378    /// synthetic frames.
379    #[metastructure(legacy_alias = "sentry.interfaces.Template")]
380    #[metastructure(omit_from_schema)]
381    pub template: Annotated<TemplateInfo>,
382
383    /// Threads that were active when the event occurred.
384    #[metastructure(skip_serialization = "empty")]
385    pub threads: Annotated<Values<Thread>>,
386
387    /// Custom tags for this event.
388    ///
389    /// A map or list of tags for this event. Each tag must be less than 200 characters.
390    #[metastructure(skip_serialization = "empty", pii = "maybe")]
391    pub tags: Annotated<Tags>,
392
393    /// Arbitrary extra information set by the user.
394    ///
395    /// ```json
396    /// {
397    ///     "extra": {
398    ///         "my_key": 1,
399    ///         "some_other_value": "foo bar"
400    ///     }
401    /// }```
402    #[metastructure(max_depth = 7, max_bytes = 262_144)]
403    #[metastructure(pii = "true", skip_serialization = "empty")]
404    pub extra: Annotated<Object<ExtraValue>>,
405
406    /// Meta data for event processing and debugging.
407    #[metastructure(skip_serialization = "empty")]
408    pub debug_meta: Annotated<DebugMeta>,
409
410    /// Information about the Sentry SDK that generated this event.
411    #[metastructure(field = "sdk")]
412    #[metastructure(skip_serialization = "empty")]
413    pub client_sdk: Annotated<ClientSdkInfo>,
414
415    /// Information about the Relays that processed this event during ingest.
416    #[metastructure(max_depth = 5, max_bytes = 2048)]
417    #[metastructure(skip_serialization = "empty", omit_from_schema)]
418    pub ingest_path: Annotated<Array<RelayInfo>>,
419
420    /// Errors encountered during processing. Intended to be phased out in favor of
421    /// annotation/metadata system.
422    #[metastructure(skip_serialization = "empty_deep")]
423    pub errors: Annotated<Array<EventProcessingError>>,
424
425    /// Project key which sent this event.
426    #[metastructure(omit_from_schema)] // not part of external schema
427    pub key_id: Annotated<String>,
428
429    /// Project which sent this event.
430    #[metastructure(omit_from_schema)] // not part of external schema
431    pub project: Annotated<u64>,
432
433    /// The grouping configuration for this event.
434    #[metastructure(omit_from_schema)] // not part of external schema
435    pub grouping_config: Annotated<Object<Value>>,
436
437    /// Legacy checksum used for grouping before fingerprint hashes.
438    #[metastructure(max_chars = 128)]
439    #[metastructure(omit_from_schema)] // deprecated
440    pub checksum: Annotated<String>,
441
442    /// CSP (security) reports.
443    #[metastructure(legacy_alias = "sentry.interfaces.Csp")]
444    #[metastructure(omit_from_schema)] // we only document error events for now
445    pub csp: Annotated<Csp>,
446
447    /// HPKP (security) reports.
448    #[metastructure(pii = "true", legacy_alias = "sentry.interfaces.Hpkp")]
449    #[metastructure(omit_from_schema)] // we only document error events for now
450    pub hpkp: Annotated<Hpkp>,
451
452    /// ExpectCT (security) reports.
453    #[metastructure(pii = "true", legacy_alias = "sentry.interfaces.ExpectCT")]
454    #[metastructure(omit_from_schema)] // we only document error events for now
455    pub expectct: Annotated<ExpectCt>,
456
457    /// ExpectStaple (security) reports.
458    #[metastructure(pii = "true", legacy_alias = "sentry.interfaces.ExpectStaple")]
459    #[metastructure(omit_from_schema)] // we only document error events for now
460    pub expectstaple: Annotated<ExpectStaple>,
461
462    /// Spans for tracing.
463    #[metastructure(max_bytes = 819200)]
464    #[metastructure(omit_from_schema)] // we only document error events for now
465    pub spans: Annotated<Array<Span>>,
466
467    /// Measurements which holds observed values such as web vitals.
468    ///
469    /// Measurements are only available on transactions. They contain measurement values of observed
470    /// values such as Largest Contentful Paint (LCP).
471    #[metastructure(skip_serialization = "empty")]
472    #[metastructure(omit_from_schema)] // we only document error events for now
473    pub measurements: Annotated<Measurements>,
474
475    /// Breakdowns which holds product-defined values such as span operation breakdowns.
476    #[metastructure(skip_serialization = "empty")]
477    #[metastructure(omit_from_schema)] // we only document error events for now
478    pub breakdowns: Annotated<Breakdowns>,
479
480    /// Information about attempts to scrape a JS source or sourcemap file from the web.
481    /// This field is populated by sentry.
482    #[metastructure(omit_from_schema)] // not part of external schema
483    pub scraping_attempts: Annotated<Value>,
484
485    /// Internal ingestion and processing metrics.
486    ///
487    /// This value should not be ingested and will be overwritten by the store normalizer.
488    #[metastructure(omit_from_schema)]
489    pub _metrics: Annotated<Metrics>,
490
491    /// Value of the `DynamicSamplingContext` for this event.
492    #[metastructure(omit_from_schema)]
493    pub _dsc: Annotated<Value>,
494
495    /// Temporary flag that controls where performance issues are detected.
496    ///
497    /// When the flag is set to true, this transaction event will be skipped for performance issue
498    /// detection in favor of the spans pipeline.
499    #[metastructure(
500        field = "_performance_issues_spans",
501        skip_serialization = "empty",
502        trim = false
503    )]
504    pub performance_issues_spans: Annotated<bool>,
505
506    /// Additional arbitrary fields for forwards compatibility.
507    #[metastructure(additional_properties, pii = "true")]
508    pub other: Object<Value>,
509}
510
511impl Event {
512    /// Returns the value of a tag with the given key.
513    ///
514    /// If tags are specified in a pair list and the tag is declared multiple times, this function
515    /// returns the first match.
516    pub fn tag_value(&self, tag_key: &str) -> Option<&str> {
517        if let Some(tags) = self.tags.value() {
518            tags.get(tag_key)
519        } else {
520            None
521        }
522    }
523
524    /// Returns `true` if [`modules`](Self::modules) contains the given module.
525    pub fn has_module(&self, module_name: &str) -> bool {
526        self.modules
527            .value()
528            .map(|m| m.contains_key(module_name))
529            .unwrap_or(false)
530    }
531
532    /// Returns the identifier of the client SDK if available.
533    ///
534    /// Sentry's own SDKs use a naming schema prefixed with `sentry.`. Defaults to `"unknown"`.
535    pub fn sdk_name(&self) -> &str {
536        if let Some(client_sdk) = self.client_sdk.value()
537            && let Some(name) = client_sdk.name.as_str()
538        {
539            return name;
540        }
541
542        "unknown"
543    }
544
545    /// Returns the version of the client SDK if available.
546    ///
547    /// Defaults to `"unknown"`.
548    pub fn sdk_version(&self) -> &str {
549        if let Some(client_sdk) = self.client_sdk.value()
550            && let Some(version) = client_sdk.version.as_str()
551        {
552            return version;
553        }
554
555        "unknown"
556    }
557
558    /// Returns the raw user agent string.
559    ///
560    /// Returns `Some` if the event's request interface contains a `user-agent` header. Returns
561    /// `None` otherwise.
562    pub fn user_agent(&self) -> Option<&str> {
563        let headers = self.request.value()?.headers.value()?;
564
565        for item in headers.iter() {
566            if let Some((o_k, v)) = item.value()
567                && let Some(k) = o_k.as_str()
568                && k.eq_ignore_ascii_case("user-agent")
569            {
570                return v.as_str();
571            }
572        }
573
574        None
575    }
576
577    /// Returns extra data at the specified path.
578    ///
579    /// The path is evaluated recursively where each path component is joined by a period (`"."`).
580    /// Periods in extra keys are not supported.
581    pub fn extra_at(&self, path: &str) -> Option<&Value> {
582        let mut path = path.split('.');
583
584        // Get the top-level item explicitly, since those have a different type
585        let mut value = &self.extra.value()?.get(path.next()?)?.value()?.0;
586
587        // Iterate recursively to fetch nested values
588        for key in path {
589            if let Value::Object(object) = value {
590                value = object.get(key)?.value()?;
591            } else {
592                return None;
593            }
594        }
595
596        Some(value)
597    }
598
599    /// Returns parsed components of the Release string in [`Self::release`].
600    pub fn parse_release(&self) -> Option<ParsedRelease<'_>> {
601        sentry_release_parser::Release::parse(self.release.as_str()?).ok()
602    }
603
604    /// Returns the numeric measurement value.
605    ///
606    /// The name is provided without a prefix, for example `"lcp"` loads `event.measurements.lcp`.
607    pub fn measurement(&self, name: &str) -> Option<FiniteF64> {
608        let annotated = self.measurements.value()?.get(name)?;
609        Some(*annotated.value()?.value.value()?)
610    }
611
612    /// Returns the numeric breakdown value.
613    pub fn breakdown(&self, breakdown: &str, measurement: &str) -> Option<FiniteF64> {
614        let breakdown = self.breakdowns.value()?.get(breakdown)?.value()?;
615        Some(*breakdown.get(measurement)?.value()?.value.value()?)
616    }
617
618    /// Returns a reference to the context if it exists in its default key.
619    pub fn context<C: DefaultContext>(&self) -> Option<&C> {
620        self.contexts.value()?.get()
621    }
622
623    /// Returns a mutable reference to the context if it exists in its default key.
624    pub fn context_mut<C: DefaultContext>(&mut self) -> Option<&mut C> {
625        self.contexts.value_mut().as_mut()?.get_mut()
626    }
627}
628
629fn or_none(string: &Annotated<impl AsRef<str>>) -> Option<&str> {
630    match string.as_str() {
631        None | Some("") => None,
632        Some(other) => Some(other),
633    }
634}
635
636impl Getter for Event {
637    fn get_value(&self, path: &str) -> Option<Val<'_>> {
638        Some(match path.strip_prefix("event.")? {
639            // Simple fields
640            "level" => self.level.value()?.name().into(),
641            "release" => self.release.as_str()?.into(),
642            "dist" => self.dist.as_str()?.into(),
643            "environment" => self.environment.as_str()?.into(),
644            "transaction" => self.transaction.as_str()?.into(),
645            "logger" => self.logger.as_str()?.into(),
646            "platform" => self.platform.as_str().unwrap_or("other").into(),
647
648            // Fields in top level structures (called "interfaces" in Sentry)
649            "logentry.formatted" => self.logentry.value()?.formatted.value()?.as_ref().into(),
650            "logentry.message" => self.logentry.value()?.message.value()?.as_ref().into(),
651            "user.email" => or_none(&self.user.value()?.email)?.into(),
652            "user.id" => or_none(&self.user.value()?.id)?.into(),
653            "user.ip_address" => self.user.value()?.ip_address.as_str()?.into(),
654            "user.name" => self.user.value()?.name.as_str()?.into(),
655            "user.segment" => or_none(&self.user.value()?.segment)?.into(),
656            "user.geo.city" => self.user.value()?.geo.value()?.city.as_str()?.into(),
657            "user.geo.country_code" => self
658                .user
659                .value()?
660                .geo
661                .value()?
662                .country_code
663                .as_str()?
664                .into(),
665            "user.geo.region" => self.user.value()?.geo.value()?.region.as_str()?.into(),
666            "user.geo.subdivision" => self.user.value()?.geo.value()?.subdivision.as_str()?.into(),
667            "request.method" => self.request.value()?.method.as_str()?.into(),
668            "request.url" => self.request.value()?.url.as_str()?.into(),
669            "transaction.source" => self
670                .transaction_info
671                .value()?
672                .source
673                .value()?
674                .as_str()
675                .into(),
676            "sdk.name" => self.client_sdk.value()?.name.as_str()?.into(),
677            "sdk.version" => self.client_sdk.value()?.version.as_str()?.into(),
678
679            // Computed fields (after normalization).
680            "sentry_user" => self.user.value()?.sentry_user.as_str()?.into(),
681
682            // Partial implementation of contexts.
683            "contexts.app.in_foreground" => {
684                self.context::<AppContext>()?.in_foreground.value()?.into()
685            }
686            "contexts.app.device_app_hash" => self
687                .context::<AppContext>()?
688                .device_app_hash
689                .as_str()?
690                .into(),
691            "contexts.device.arch" => self.context::<DeviceContext>()?.arch.as_str()?.into(),
692            "contexts.device.battery_level" => self
693                .context::<DeviceContext>()?
694                .battery_level
695                .value()?
696                .into(),
697            "contexts.device.brand" => self.context::<DeviceContext>()?.brand.as_str()?.into(),
698            "contexts.device.charging" => self.context::<DeviceContext>()?.charging.value()?.into(),
699            "contexts.device.family" => self.context::<DeviceContext>()?.family.as_str()?.into(),
700            "contexts.device.model" => self.context::<DeviceContext>()?.model.as_str()?.into(),
701            "contexts.device.locale" => self.context::<DeviceContext>()?.locale.as_str()?.into(),
702            "contexts.device.online" => self.context::<DeviceContext>()?.online.value()?.into(),
703            "contexts.device.orientation" => self
704                .context::<DeviceContext>()?
705                .orientation
706                .as_str()?
707                .into(),
708            "contexts.device.name" => self.context::<DeviceContext>()?.name.as_str()?.into(),
709            "contexts.device.screen_density" => self
710                .context::<DeviceContext>()?
711                .screen_density
712                .value()?
713                .into(),
714            "contexts.device.screen_dpi" => {
715                self.context::<DeviceContext>()?.screen_dpi.value()?.into()
716            }
717            "contexts.device.screen_width_pixels" => self
718                .context::<DeviceContext>()?
719                .screen_width_pixels
720                .value()?
721                .into(),
722            "contexts.device.screen_height_pixels" => self
723                .context::<DeviceContext>()?
724                .screen_height_pixels
725                .value()?
726                .into(),
727            "contexts.device.simulator" => {
728                self.context::<DeviceContext>()?.simulator.value()?.into()
729            }
730            "contexts.gpu.vendor_name" => {
731                self.context::<GpuContext>()?.vendor_name.as_str()?.into()
732            }
733            "contexts.gpu.name" => self.context::<GpuContext>()?.name.as_str()?.into(),
734            "contexts.monitor.id" => self.context::<MonitorContext>()?.get("id")?.value()?.into(),
735            "contexts.monitor.slug" => self
736                .context::<MonitorContext>()?
737                .get("slug")?
738                .value()?
739                .into(),
740            "contexts.os" => self.context::<OsContext>()?.os.as_str()?.into(),
741            "contexts.os.build" => self.context::<OsContext>()?.build.as_str()?.into(),
742            "contexts.os.kernel_version" => {
743                self.context::<OsContext>()?.kernel_version.as_str()?.into()
744            }
745            "contexts.os.name" => self.context::<OsContext>()?.name.as_str()?.into(),
746            "contexts.os.version" => self.context::<OsContext>()?.version.as_str()?.into(),
747            "contexts.os.rooted" => self.context::<OsContext>()?.rooted.value()?.into(),
748            "contexts.browser" => self.context::<BrowserContext>()?.browser.as_str()?.into(),
749            "contexts.browser.name" => self.context::<BrowserContext>()?.name.as_str()?.into(),
750            "contexts.browser.version" => {
751                self.context::<BrowserContext>()?.version.as_str()?.into()
752            }
753            "contexts.profile.profile_id" => {
754                (&self.context::<ProfileContext>()?.profile_id.value()?.0).into()
755            }
756            "contexts.device.uuid" => self.context::<DeviceContext>()?.uuid.value()?.into(),
757            "contexts.trace.status" => self
758                .context::<TraceContext>()?
759                .status
760                .value()?
761                .as_str()
762                .into(),
763            "contexts.trace.op" => self.context::<TraceContext>()?.op.as_str()?.into(),
764            "contexts.response.status_code" => self
765                .context::<ResponseContext>()?
766                .status_code
767                .value()?
768                .into(),
769            "contexts.unreal.crash_type" => match self.contexts.value()?.get_key("unreal")? {
770                super::Context::Other(context) => context.get("crash_type")?.value()?.into(),
771                _ => return None,
772            },
773            "contexts.runtime" => self.context::<RuntimeContext>()?.runtime.as_str()?.into(),
774            "contexts.runtime.name" => self.context::<RuntimeContext>()?.name.as_str()?.into(),
775
776            // Computed fields (see Discover)
777            "duration" => {
778                let start = self.start_timestamp.value()?;
779                let end = self.timestamp.value()?;
780                if start <= end && self.ty.value() == Some(&EventType::Transaction) {
781                    time::chrono_to_positive_millis(*end - *start).into()
782                } else {
783                    return None;
784                }
785            }
786
787            // Dynamic access to certain data bags
788            path => {
789                if let Some(rest) = path.strip_prefix("release.") {
790                    let release = self.parse_release()?;
791                    match rest {
792                        "build" => release.build_hash()?.into(),
793                        "package" => release.package()?.into(),
794                        "version.short" => release.version()?.raw_short().into(),
795                        _ => return None,
796                    }
797                } else if let Some(rest) = path.strip_prefix("measurements.") {
798                    let name = rest.strip_suffix(".value")?;
799                    self.measurement(name)?.into()
800                } else if let Some(rest) = path.strip_prefix("breakdowns.") {
801                    let (breakdown, measurement) = rest.split_once('.')?;
802                    self.breakdown(breakdown, measurement)?.into()
803                } else if let Some(rest) = path.strip_prefix("extra.") {
804                    self.extra_at(rest)?.into()
805                } else if let Some(rest) = path.strip_prefix("tags.") {
806                    self.tags.value()?.get(rest)?.into()
807                } else {
808                    let rest = path.strip_prefix("request.headers.")?;
809                    self.request
810                        .value()?
811                        .headers
812                        .value()?
813                        .get_header(rest)?
814                        .into()
815                }
816            }
817        })
818    }
819
820    fn get_iter(&self, path: &str) -> Option<GetterIter<'_>> {
821        Some(match path.strip_prefix("event.")? {
822            "exception.values" => {
823                GetterIter::new_annotated(self.exceptions.value()?.values.value()?)
824            }
825            _ => return None,
826        })
827    }
828}
829
830#[cfg(test)]
831mod tests {
832    use chrono::{TimeZone, Utc};
833    use relay_protocol::{ErrorKind, HexId, Map, Meta};
834    use similar_asserts::assert_eq;
835    use std::collections::BTreeMap;
836    use uuid::uuid;
837
838    use super::*;
839    use crate::protocol::{
840        Headers, IpAddr, JsonLenientString, PairList, TagEntry, TransactionSource,
841    };
842
843    #[test]
844    fn test_event_roundtrip() {
845        // NOTE: Interfaces will be tested separately.
846        let json = r#"{
847  "event_id": "52df9022835246eeb317dbd739ccd059",
848  "level": "debug",
849  "fingerprint": [
850    "myprint"
851  ],
852  "culprit": "myculprit",
853  "transaction": "mytransaction",
854  "logentry": {
855    "formatted": "mymessage"
856  },
857  "logger": "mylogger",
858  "modules": {
859    "mymodule": "1.0.0"
860  },
861  "platform": "myplatform",
862  "timestamp": 946684800.0,
863  "server_name": "myhost",
864  "release": "myrelease",
865  "dist": "mydist",
866  "environment": "myenv",
867  "tags": [
868    [
869      "tag",
870      "value"
871    ]
872  ],
873  "extra": {
874    "extra": "value"
875  },
876  "other": "value",
877  "_meta": {
878    "event_id": {
879      "": {
880        "err": [
881          "invalid_data"
882        ]
883      }
884    }
885  }
886}"#;
887
888        let event = Annotated::new(Event {
889            id: Annotated(
890                Some("52df9022-8352-46ee-b317-dbd739ccd059".parse().unwrap()),
891                Meta::from_error(ErrorKind::InvalidData),
892            ),
893            level: Annotated::new(Level::Debug),
894            fingerprint: Annotated::new(vec!["myprint".to_owned()].into()),
895            culprit: Annotated::new("myculprit".to_owned()),
896            transaction: Annotated::new("mytransaction".to_owned()),
897            logentry: Annotated::new(LogEntry {
898                formatted: Annotated::new("mymessage".to_owned().into()),
899                ..Default::default()
900            }),
901            logger: Annotated::new("mylogger".to_owned()),
902            modules: {
903                let mut map = Map::new();
904                map.insert("mymodule".to_owned(), Annotated::new("1.0.0".to_owned()));
905                Annotated::new(map)
906            },
907            platform: Annotated::new("myplatform".to_owned()),
908            timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
909            server_name: Annotated::new("myhost".to_owned()),
910            release: Annotated::new("myrelease".to_owned().into()),
911            dist: Annotated::new("mydist".to_owned()),
912            environment: Annotated::new("myenv".to_owned()),
913            tags: {
914                let items = vec![Annotated::new(TagEntry(
915                    Annotated::new("tag".to_owned()),
916                    Annotated::new("value".to_owned()),
917                ))];
918                Annotated::new(Tags(items.into()))
919            },
920            extra: {
921                let mut map = Map::new();
922                map.insert(
923                    "extra".to_owned(),
924                    Annotated::new(ExtraValue(Value::String("value".to_owned()))),
925                );
926                Annotated::new(map)
927            },
928            other: {
929                let mut map = Map::new();
930                map.insert(
931                    "other".to_owned(),
932                    Annotated::new(Value::String("value".to_owned())),
933                );
934                map
935            },
936            ..Default::default()
937        });
938
939        assert_eq!(event, Annotated::from_json(json).unwrap());
940        assert_eq!(json, event.to_json_pretty().unwrap());
941    }
942
943    #[test]
944    fn test_event_default_values() {
945        let json = "{}";
946        let event = Annotated::new(Event::default());
947
948        assert_eq!(event, Annotated::from_json(json).unwrap());
949        assert_eq!(json, event.to_json_pretty().unwrap());
950    }
951
952    #[test]
953    fn test_event_default_values_with_meta() {
954        let json = r#"{
955  "event_id": "52df9022835246eeb317dbd739ccd059",
956  "fingerprint": [
957    "{{ default }}"
958  ],
959  "platform": "other",
960  "_meta": {
961    "event_id": {
962      "": {
963        "err": [
964          "invalid_data"
965        ]
966      }
967    },
968    "fingerprint": {
969      "": {
970        "err": [
971          "invalid_data"
972        ]
973      }
974    },
975    "platform": {
976      "": {
977        "err": [
978          "invalid_data"
979        ]
980      }
981    }
982  }
983}"#;
984
985        let event = Annotated::new(Event {
986            id: Annotated(
987                Some("52df9022-8352-46ee-b317-dbd739ccd059".parse().unwrap()),
988                Meta::from_error(ErrorKind::InvalidData),
989            ),
990            fingerprint: Annotated(
991                Some(vec!["{{ default }}".to_owned()].into()),
992                Meta::from_error(ErrorKind::InvalidData),
993            ),
994            platform: Annotated(
995                Some("other".to_owned()),
996                Meta::from_error(ErrorKind::InvalidData),
997            ),
998            ..Default::default()
999        });
1000
1001        assert_eq!(event, Annotated::<Event>::from_json(json).unwrap());
1002        assert_eq!(json, event.to_json_pretty().unwrap());
1003    }
1004
1005    #[test]
1006    fn test_event_type() {
1007        assert_eq!(
1008            EventType::Default,
1009            *Annotated::<EventType>::from_json("\"default\"")
1010                .unwrap()
1011                .value()
1012                .unwrap()
1013        );
1014    }
1015
1016    #[test]
1017    fn test_fingerprint_empty_string() {
1018        let json = r#"{"fingerprint":[""]}"#;
1019        let event = Annotated::new(Event {
1020            fingerprint: Annotated::new(vec!["".to_owned()].into()),
1021            ..Default::default()
1022        });
1023
1024        assert_eq!(json, event.to_json().unwrap());
1025        assert_eq!(event, Annotated::from_json(json).unwrap());
1026    }
1027
1028    #[test]
1029    fn test_fingerprint_null_values() {
1030        let input = r#"{"fingerprint":[null]}"#;
1031        let output = r#"{}"#;
1032        let event = Annotated::new(Event {
1033            fingerprint: Annotated::new(vec![].into()),
1034            ..Default::default()
1035        });
1036
1037        assert_eq!(event, Annotated::from_json(input).unwrap());
1038        assert_eq!(output, event.to_json().unwrap());
1039    }
1040
1041    #[test]
1042    fn test_empty_threads() {
1043        let input = r#"{"threads": {}}"#;
1044        let output = r#"{}"#;
1045
1046        let event = Annotated::new(Event::default());
1047
1048        assert_eq!(event, Annotated::from_json(input).unwrap());
1049        assert_eq!(output, event.to_json().unwrap());
1050    }
1051
1052    #[test]
1053    fn test_lenient_release() {
1054        let input = r#"{"release":42}"#;
1055        let output = r#"{"release":"42"}"#;
1056        let event = Annotated::new(Event {
1057            release: Annotated::new("42".to_owned().into()),
1058            ..Default::default()
1059        });
1060
1061        assert_eq!(event, Annotated::from_json(input).unwrap());
1062        assert_eq!(output, event.to_json().unwrap());
1063    }
1064
1065    #[test]
1066    fn test_extra_at() {
1067        let json = serde_json::json!({
1068            "extra": {
1069                "a": "string1",
1070                "b": 42,
1071                "c": {
1072                    "d": "string2",
1073                    "e": null,
1074                },
1075            },
1076        });
1077
1078        let event = Event::from_value(json.into());
1079        let event = event.value().unwrap();
1080
1081        assert_eq!(
1082            Some(&Value::String("string1".to_owned())),
1083            event.extra_at("a")
1084        );
1085        assert_eq!(Some(&Value::I64(42)), event.extra_at("b"));
1086        assert!(matches!(event.extra_at("c"), Some(&Value::Object(_))));
1087        assert_eq!(None, event.extra_at("d"));
1088        assert_eq!(
1089            Some(&Value::String("string2".to_owned())),
1090            event.extra_at("c.d")
1091        );
1092        assert_eq!(None, event.extra_at("c.e"));
1093        assert_eq!(None, event.extra_at("c.f"));
1094    }
1095
1096    #[test]
1097    fn test_scrape_attempts() {
1098        let json = serde_json::json!({
1099            "scraping_attempts": [
1100                {"status": "not_attempted", "url": "http://example.com/embedded.js"},
1101                {"status": "not_attempted", "url": "http://example.com/embedded.js.map"},
1102            ]
1103        });
1104
1105        let event = Event::from_value(json.into());
1106        assert!(!event.value().unwrap().scraping_attempts.meta().has_errors());
1107    }
1108
1109    #[test]
1110    fn test_field_value_provider_event_filled() {
1111        let event = Event {
1112            level: Annotated::new(Level::Info),
1113            release: Annotated::new(LenientString("1.1.1".to_owned())),
1114            environment: Annotated::new("prod".to_owned()),
1115            user: Annotated::new(User {
1116                ip_address: Annotated::new(IpAddr("127.0.0.1".to_owned())),
1117                id: Annotated::new(LenientString("user-id".into())),
1118                segment: Annotated::new("user-seg".into()),
1119                sentry_user: Annotated::new("id:user-id".into()),
1120                ..Default::default()
1121            }),
1122            client_sdk: Annotated::new(ClientSdkInfo {
1123                name: Annotated::new("sentry-javascript".into()),
1124                version: Annotated::new("1.87.0".into()),
1125                ..Default::default()
1126            }),
1127            exceptions: Annotated::new(Values {
1128                values: Annotated::new(vec![Annotated::new(Exception {
1129                    value: Annotated::new(JsonLenientString::from(
1130                        "canvas.contentDocument".to_owned(),
1131                    )),
1132                    ..Default::default()
1133                })]),
1134                ..Default::default()
1135            }),
1136            logentry: Annotated::new(LogEntry {
1137                formatted: Annotated::new("formatted".to_owned().into()),
1138                message: Annotated::new("message".to_owned().into()),
1139                ..Default::default()
1140            }),
1141            request: Annotated::new(Request {
1142                headers: Annotated::new(Headers(PairList(vec![Annotated::new((
1143                    Annotated::new("user-agent".into()),
1144                    Annotated::new("Slurp".into()),
1145                ))]))),
1146                url: Annotated::new("https://sentry.io".into()),
1147                ..Default::default()
1148            }),
1149            transaction: Annotated::new("some-transaction".into()),
1150            transaction_info: Annotated::new(TransactionInfo {
1151                source: Annotated::new(TransactionSource::Route),
1152                ..Default::default()
1153            }),
1154            tags: {
1155                let items = vec![Annotated::new(TagEntry(
1156                    Annotated::new("custom".to_owned()),
1157                    Annotated::new("custom-value".to_owned()),
1158                ))];
1159                Annotated::new(Tags(items.into()))
1160            },
1161            contexts: Annotated::new({
1162                let mut contexts = Contexts::new();
1163                contexts.add(DeviceContext {
1164                    name: Annotated::new("iphone".to_owned()),
1165                    family: Annotated::new("iphone-fam".to_owned()),
1166                    model: Annotated::new("iphone7,3".to_owned()),
1167                    screen_dpi: Annotated::new(560),
1168                    screen_width_pixels: Annotated::new(1920),
1169                    screen_height_pixels: Annotated::new(1080),
1170                    locale: Annotated::new("US".into()),
1171                    uuid: Annotated::new(uuid!("abadcade-feed-dead-beef-baddadfeeded")),
1172                    charging: Annotated::new(true),
1173                    ..DeviceContext::default()
1174                });
1175                contexts.add(OsContext {
1176                    name: Annotated::new("iOS".to_owned()),
1177                    version: Annotated::new("11.4.2".to_owned()),
1178                    kernel_version: Annotated::new("17.4.0".to_owned()),
1179                    ..OsContext::default()
1180                });
1181                contexts.add(ProfileContext {
1182                    profile_id: Annotated::new(EventId(uuid!(
1183                        "abadcade-feed-dead-beef-8addadfeedaa"
1184                    ))),
1185                    ..ProfileContext::default()
1186                });
1187                let mut monitor_context_fields = BTreeMap::new();
1188                monitor_context_fields.insert(
1189                    "id".to_owned(),
1190                    Annotated::new(Value::String("123".to_owned())),
1191                );
1192                monitor_context_fields.insert(
1193                    "slug".to_owned(),
1194                    Annotated::new(Value::String("my_monitor".to_owned())),
1195                );
1196                contexts.add(MonitorContext(monitor_context_fields));
1197                contexts
1198            }),
1199            ..Default::default()
1200        };
1201
1202        assert_eq!(Some(Val::String("info")), event.get_value("event.level"));
1203
1204        assert_eq!(Some(Val::String("1.1.1")), event.get_value("event.release"));
1205        assert_eq!(
1206            Some(Val::String("prod")),
1207            event.get_value("event.environment")
1208        );
1209        assert_eq!(
1210            Some(Val::String("user-id")),
1211            event.get_value("event.user.id")
1212        );
1213        assert_eq!(
1214            Some(Val::String("id:user-id")),
1215            event.get_value("event.sentry_user")
1216        );
1217        assert_eq!(
1218            Some(Val::String("user-seg")),
1219            event.get_value("event.user.segment")
1220        );
1221        assert_eq!(
1222            Some(Val::String("some-transaction")),
1223            event.get_value("event.transaction")
1224        );
1225        assert_eq!(
1226            Some(Val::String("iphone")),
1227            event.get_value("event.contexts.device.name")
1228        );
1229        assert_eq!(
1230            Some(Val::String("iphone-fam")),
1231            event.get_value("event.contexts.device.family")
1232        );
1233        assert_eq!(
1234            Some(Val::String("iOS")),
1235            event.get_value("event.contexts.os.name")
1236        );
1237        assert_eq!(
1238            Some(Val::String("11.4.2")),
1239            event.get_value("event.contexts.os.version")
1240        );
1241        assert_eq!(
1242            Some(Val::String("custom-value")),
1243            event.get_value("event.tags.custom")
1244        );
1245        assert_eq!(None, event.get_value("event.tags.doesntexist"));
1246        assert_eq!(
1247            Some(Val::String("sentry-javascript")),
1248            event.get_value("event.sdk.name")
1249        );
1250        assert_eq!(
1251            Some(Val::String("1.87.0")),
1252            event.get_value("event.sdk.version")
1253        );
1254        assert_eq!(
1255            Some(Val::String("17.4.0")),
1256            event.get_value("event.contexts.os.kernel_version")
1257        );
1258        assert_eq!(
1259            Some(Val::I64(560)),
1260            event.get_value("event.contexts.device.screen_dpi")
1261        );
1262        assert_eq!(
1263            Some(Val::Bool(true)),
1264            event.get_value("event.contexts.device.charging")
1265        );
1266        assert_eq!(
1267            Some(Val::U64(1920)),
1268            event.get_value("event.contexts.device.screen_width_pixels")
1269        );
1270        assert_eq!(
1271            Some(Val::U64(1080)),
1272            event.get_value("event.contexts.device.screen_height_pixels")
1273        );
1274        assert_eq!(
1275            Some(Val::String("US")),
1276            event.get_value("event.contexts.device.locale")
1277        );
1278        assert_eq!(
1279            Some(Val::HexId(HexId(
1280                uuid!("abadcade-feed-dead-beef-baddadfeeded").as_bytes()
1281            ))),
1282            event.get_value("event.contexts.device.uuid")
1283        );
1284        assert_eq!(
1285            Some(Val::String("https://sentry.io")),
1286            event.get_value("event.request.url")
1287        );
1288        assert_eq!(
1289            Some(Val::HexId(HexId(
1290                uuid!("abadcade-feed-dead-beef-8addadfeedaa").as_bytes()
1291            ))),
1292            event.get_value("event.contexts.profile.profile_id")
1293        );
1294        assert_eq!(
1295            Some(Val::String("route")),
1296            event.get_value("event.transaction.source")
1297        );
1298
1299        let mut exceptions = event.get_iter("event.exception.values").unwrap();
1300        let exception = exceptions.next().unwrap();
1301        assert_eq!(
1302            Some(Val::String("canvas.contentDocument")),
1303            exception.get_value("value")
1304        );
1305        assert!(exceptions.next().is_none());
1306
1307        assert_eq!(
1308            Some(Val::String("formatted")),
1309            event.get_value("event.logentry.formatted")
1310        );
1311        assert_eq!(
1312            Some(Val::String("message")),
1313            event.get_value("event.logentry.message")
1314        );
1315        assert_eq!(
1316            Some(Val::String("123")),
1317            event.get_value("event.contexts.monitor.id")
1318        );
1319        assert_eq!(
1320            Some(Val::String("my_monitor")),
1321            event.get_value("event.contexts.monitor.slug")
1322        );
1323    }
1324
1325    #[test]
1326    fn test_field_value_provider_event_empty() {
1327        let event = Event::default();
1328
1329        assert_eq!(None, event.get_value("event.release"));
1330        assert_eq!(None, event.get_value("event.environment"));
1331        assert_eq!(None, event.get_value("event.user.id"));
1332        assert_eq!(None, event.get_value("event.user.segment"));
1333
1334        // now try with an empty user
1335        let event = Event {
1336            user: Annotated::new(User {
1337                ..Default::default()
1338            }),
1339            ..Default::default()
1340        };
1341
1342        assert_eq!(None, event.get_value("event.user.id"));
1343        assert_eq!(None, event.get_value("event.user.segment"));
1344        assert_eq!(None, event.get_value("event.transaction"));
1345    }
1346}