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