Skip to main content

sentry_types/protocol/
envelope.rs

1use std::mem;
2use std::{borrow::Cow, io::Write, path::Path, time::SystemTime};
3
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6use uuid::Uuid;
7
8use crate::Dsn;
9use crate::{protocol::v7::ClientReport, utils::ts_rfc3339_opt};
10
11use super::v7 as protocol;
12
13use protocol::{
14    Attachment, AttachmentType, ClientSdkInfo, DynamicSamplingContext, Event, Log, Metric,
15    MonitorCheckIn, SessionAggregates, SessionUpdate, Transaction,
16};
17
18/// Raised if a envelope cannot be parsed from a given input.
19#[derive(Debug, Error)]
20pub enum EnvelopeError {
21    /// Unexpected end of file
22    #[error("unexpected end of file")]
23    UnexpectedEof,
24    /// Missing envelope header
25    #[error("missing envelope header")]
26    MissingHeader,
27    /// Missing item header
28    #[error("missing item header")]
29    MissingItemHeader,
30    /// Missing newline after header or payload
31    #[error("missing newline after header or payload")]
32    MissingNewline,
33    /// Invalid envelope header
34    #[error("invalid envelope header")]
35    InvalidHeader(#[source] serde_json::Error),
36    /// Invalid item header
37    #[error("invalid item header")]
38    InvalidItemHeader(#[source] serde_json::Error),
39    /// Invalid item payload
40    #[error("invalid item payload")]
41    InvalidItemPayload(#[source] serde_json::Error),
42}
43
44/// The supported [Sentry Envelope Headers](https://develop.sentry.dev/sdk/data-model/envelopes/#headers).
45#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
46pub struct EnvelopeHeaders {
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    event_id: Option<Uuid>,
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    dsn: Option<Dsn>,
51    #[serde(default, skip_serializing_if = "Option::is_none")]
52    sdk: Option<ClientSdkInfo>,
53    #[serde(
54        default,
55        skip_serializing_if = "Option::is_none",
56        with = "ts_rfc3339_opt"
57    )]
58    sent_at: Option<SystemTime>,
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    trace: Option<DynamicSamplingContext>,
61}
62
63impl EnvelopeHeaders {
64    /// Creates empty Envelope headers.
65    pub fn new() -> EnvelopeHeaders {
66        Default::default()
67    }
68
69    /// Sets the Event ID.
70    #[must_use]
71    pub fn with_event_id(mut self, event_id: Uuid) -> Self {
72        self.event_id = Some(event_id);
73        self
74    }
75
76    /// Sets the DSN.
77    #[must_use]
78    pub fn with_dsn(mut self, dsn: Dsn) -> Self {
79        self.dsn = Some(dsn);
80        self
81    }
82
83    /// Sets the SDK information.
84    #[must_use]
85    pub fn with_sdk(mut self, sdk: ClientSdkInfo) -> Self {
86        self.sdk = Some(sdk);
87        self
88    }
89
90    /// Sets the time this envelope was sent at.
91    /// This timestamp should be generated as close as possible to the transmission of the event.
92    #[must_use]
93    pub fn with_sent_at(mut self, sent_at: SystemTime) -> Self {
94        self.sent_at = Some(sent_at);
95        self
96    }
97
98    /// Sets the Dynamic Sampling Context.
99    #[must_use]
100    pub fn with_trace(mut self, trace: DynamicSamplingContext) -> Self {
101        self.trace = Some(trace);
102        self
103    }
104}
105
106/// An Envelope Item Type.
107#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
108#[non_exhaustive]
109enum EnvelopeItemType {
110    /// An Event Item type.
111    #[serde(rename = "event")]
112    Event,
113    /// A Session Item type.
114    #[serde(rename = "session")]
115    SessionUpdate,
116    /// A Session Aggregates Item type.
117    #[serde(rename = "sessions")]
118    SessionAggregates,
119    /// A Transaction Item type.
120    #[serde(rename = "transaction")]
121    Transaction,
122    /// An Attachment Item type.
123    #[serde(rename = "attachment")]
124    Attachment,
125    /// A Monitor Check In Item Type.
126    #[serde(rename = "check_in")]
127    MonitorCheckIn,
128    /// A container of Log items.
129    #[serde(rename = "log")]
130    LogsContainer,
131    /// A container of Metric items.
132    /// Serialized to a `trace_metric` envelope item.
133    #[serde(rename = "trace_metric")]
134    MetricsContainer,
135    /// A client report.
136    #[serde(rename = "client_report")]
137    ClientReport,
138}
139
140/// An Envelope Item Header.
141#[derive(Clone, Debug, Deserialize)]
142struct EnvelopeItemHeader {
143    r#type: EnvelopeItemType,
144    length: Option<usize>,
145    // Applies both to Attachment and ItemContainer Item type
146    content_type: Option<String>,
147    // Fields below apply only to Attachment Item types
148    filename: Option<String>,
149    attachment_type: Option<AttachmentType>,
150}
151
152/// An Envelope Item.
153///
154/// See the [documentation on Items](https://develop.sentry.dev/sdk/envelopes/#items)
155/// for more details.
156#[derive(Clone, Debug, PartialEq)]
157#[non_exhaustive]
158#[allow(clippy::large_enum_variant)]
159pub enum EnvelopeItem {
160    /// An Event Item.
161    ///
162    /// See the [Event Item documentation](https://develop.sentry.dev/sdk/envelopes/#event)
163    /// for more details.
164    Event(Event<'static>),
165    /// A Session Item.
166    ///
167    /// See the [Session Item documentation](https://develop.sentry.dev/sdk/envelopes/#session)
168    /// for more details.
169    SessionUpdate(SessionUpdate<'static>),
170    /// A Session Aggregates Item.
171    ///
172    /// See the [Session Aggregates Item documentation](https://develop.sentry.dev/sdk/envelopes/#sessions)
173    /// for more details.
174    SessionAggregates(SessionAggregates<'static>),
175    /// A Transaction Item.
176    ///
177    /// See the [Transaction Item documentation](https://develop.sentry.dev/sdk/envelopes/#transaction)
178    /// for more details.
179    Transaction(Transaction<'static>),
180    /// An Attachment Item.
181    ///
182    /// See the [Attachment Item documentation](https://develop.sentry.dev/sdk/envelopes/#attachment)
183    /// for more details.
184    Attachment(Attachment),
185    /// A MonitorCheckIn item.
186    MonitorCheckIn(MonitorCheckIn),
187    /// An aggregated client report
188    ClientReport(ClientReport),
189    /// A container for a list of multiple items.
190    ItemContainer(ItemContainer),
191    /// This is a sentinel item used to `filter` raw envelopes.
192    Raw,
193    // TODO:
194    // etc…
195}
196
197/// A container for a list of multiple items.
198/// It's considered a single envelope item, with its `type` corresponding to the contained items'
199/// `type`.
200#[derive(Clone, Debug, PartialEq)]
201#[non_exhaustive]
202pub enum ItemContainer {
203    /// A list of logs.
204    Logs(Vec<Log>),
205    /// A list of metrics.
206    Metrics(Vec<Metric>),
207}
208
209#[allow(clippy::len_without_is_empty, reason = "is_empty is not needed")]
210impl ItemContainer {
211    /// The number of items in this item container.
212    pub fn len(&self) -> usize {
213        match self {
214            Self::Logs(logs) => logs.len(),
215            Self::Metrics(metrics) => metrics.len(),
216        }
217    }
218
219    /// The `type` of this item container, which corresponds to the `type` of the contained items.
220    pub fn ty(&self) -> &'static str {
221        match self {
222            Self::Logs(_) => "log",
223            Self::Metrics(_) => "trace_metric",
224        }
225    }
226
227    /// The `content-type` expected by Relay for this item container.
228    pub fn content_type(&self) -> &'static str {
229        match self {
230            Self::Logs(_) => "application/vnd.sentry.items.log+json",
231            Self::Metrics(_) => "application/vnd.sentry.items.trace-metric+json",
232        }
233    }
234}
235
236impl From<Vec<Log>> for ItemContainer {
237    fn from(logs: Vec<Log>) -> Self {
238        Self::Logs(logs)
239    }
240}
241
242/// A lightweight wrapper for serializing/deserializing a slice of items,
243/// so that it looks like:
244///
245/// ```json
246/// { items: [...] }
247/// ```
248#[derive(Deserialize, Serialize)]
249struct ItemsSerdeWrapper<'a, T: Clone> {
250    items: Cow<'a, [T]>,
251}
252
253impl From<Vec<Metric>> for ItemContainer {
254    fn from(metrics: Vec<Metric>) -> Self {
255        Self::Metrics(metrics)
256    }
257}
258
259impl ItemContainer {
260    fn item_type(&self) -> EnvelopeItemType {
261        match self {
262            Self::Logs(_) => EnvelopeItemType::LogsContainer,
263            Self::Metrics(_) => EnvelopeItemType::MetricsContainer,
264        }
265    }
266}
267
268impl EnvelopeItem {
269    fn item_type(&self) -> Option<EnvelopeItemType> {
270        match self {
271            Self::Event(_) => Some(EnvelopeItemType::Event),
272            Self::SessionUpdate(_) => Some(EnvelopeItemType::SessionUpdate),
273            Self::SessionAggregates(_) => Some(EnvelopeItemType::SessionAggregates),
274            Self::Transaction(_) => Some(EnvelopeItemType::Transaction),
275            Self::Attachment(_) => Some(EnvelopeItemType::Attachment),
276            Self::MonitorCheckIn(_) => Some(EnvelopeItemType::MonitorCheckIn),
277            Self::ClientReport(_) => Some(EnvelopeItemType::ClientReport),
278            Self::ItemContainer(container) => Some(container.item_type()),
279            Self::Raw => None,
280        }
281    }
282}
283
284impl From<Event<'static>> for EnvelopeItem {
285    fn from(event: Event<'static>) -> Self {
286        EnvelopeItem::Event(event)
287    }
288}
289
290impl From<SessionUpdate<'static>> for EnvelopeItem {
291    fn from(session: SessionUpdate<'static>) -> Self {
292        EnvelopeItem::SessionUpdate(session)
293    }
294}
295
296impl From<SessionAggregates<'static>> for EnvelopeItem {
297    fn from(aggregates: SessionAggregates<'static>) -> Self {
298        EnvelopeItem::SessionAggregates(aggregates)
299    }
300}
301
302impl From<Transaction<'static>> for EnvelopeItem {
303    fn from(transaction: Transaction<'static>) -> Self {
304        EnvelopeItem::Transaction(transaction)
305    }
306}
307
308impl From<Attachment> for EnvelopeItem {
309    fn from(attachment: Attachment) -> Self {
310        EnvelopeItem::Attachment(attachment)
311    }
312}
313
314impl From<MonitorCheckIn> for EnvelopeItem {
315    fn from(check_in: MonitorCheckIn) -> Self {
316        EnvelopeItem::MonitorCheckIn(check_in)
317    }
318}
319
320impl From<ItemContainer> for EnvelopeItem {
321    fn from(container: ItemContainer) -> Self {
322        EnvelopeItem::ItemContainer(container)
323    }
324}
325
326impl From<Vec<Log>> for EnvelopeItem {
327    fn from(logs: Vec<Log>) -> Self {
328        EnvelopeItem::ItemContainer(logs.into())
329    }
330}
331
332impl From<Vec<Metric>> for EnvelopeItem {
333    fn from(metrics: Vec<Metric>) -> Self {
334        EnvelopeItem::ItemContainer(metrics.into())
335    }
336}
337
338impl From<ClientReport> for EnvelopeItem {
339    fn from(value: ClientReport) -> Self {
340        EnvelopeItem::ClientReport(value)
341    }
342}
343
344/// An Iterator over the items of an Envelope.
345#[derive(Clone)]
346pub struct EnvelopeItemIter<'s> {
347    inner: std::slice::Iter<'s, EnvelopeItem>,
348}
349
350impl<'s> Iterator for EnvelopeItemIter<'s> {
351    type Item = &'s EnvelopeItem;
352
353    fn next(&mut self) -> Option<Self::Item> {
354        self.inner.next()
355    }
356}
357
358/// The items contained in an [`Envelope`].
359///
360/// This may be a vector of [`EnvelopeItem`]s (the standard case)
361/// or a binary blob.
362#[derive(Debug, Clone, PartialEq)]
363enum Items {
364    EnvelopeItems(Vec<EnvelopeItem>),
365    Raw(Vec<u8>),
366}
367
368impl Default for Items {
369    fn default() -> Self {
370        Self::EnvelopeItems(Default::default())
371    }
372}
373
374impl Items {
375    fn is_empty(&self) -> bool {
376        match self {
377            Items::EnvelopeItems(items) => items.is_empty(),
378            Items::Raw(bytes) => bytes.is_empty(),
379        }
380    }
381}
382
383/// A trait for types which can filter items in envelopes.
384///
385/// This trait is used by [`Envelope::filter`].
386pub trait EnvelopeFilter: private::Sealed {
387    /// The function used to filter the envelopes.
388    ///
389    /// A return value of `true` indicates that the item should be kept in the envelope, `false`
390    /// will filter the value out.
391    ///
392    /// A value of `true` does not guarantee the item is kept; in particular, [`Envelope::filter`]
393    /// removes attachments if the corresponding event or transaction item is removed from the
394    /// envelope, as it no longer makes sense to send them in this case.
395    fn filter(&mut self, item: &EnvelopeItem) -> bool;
396
397    /// A callback which is called with all items removed by filtering, including items for which
398    /// [`Self::filter`] had returned `true`, such as no-longer-applicable attachments.
399    fn on_filtered(&mut self, item: EnvelopeItem) {
400        let _ = item;
401    }
402}
403
404/// A container for callbacks that can be passed to [`Envelope::filter`].
405pub struct EnvelopeFilterCallbacks<F, C> {
406    filter: F,
407    on_filtered: C,
408}
409
410impl<F, C> EnvelopeFilterCallbacks<F, C> {
411    /// Create a new [`EnvelopeFilterCallbacks`].
412    ///
413    /// `filter` will be called to determine whether the envelope items should be kept.
414    /// `on_filtered` will be called on all envelope items which are then dropped.
415    pub fn new(filter: F, on_filtered: C) -> Self {
416        Self {
417            filter,
418            on_filtered,
419        }
420    }
421}
422
423/// A Sentry Envelope.
424///
425/// An Envelope is the data format that Sentry uses for Ingestion. It can contain
426/// multiple Items, some of which are related, such as Events, and Event Attachments.
427/// Other Items, such as Sessions are independent.
428///
429/// See the [documentation on Envelopes](https://develop.sentry.dev/sdk/envelopes/)
430/// for more details.
431#[derive(Clone, Default, Debug, PartialEq)]
432pub struct Envelope {
433    headers: EnvelopeHeaders,
434    items: Items,
435}
436
437impl Envelope {
438    /// Creates a new empty Envelope.
439    pub fn new() -> Envelope {
440        Default::default()
441    }
442
443    /// Add a new Envelope Item.
444    pub fn add_item<I>(&mut self, item: I)
445    where
446        I: Into<EnvelopeItem>,
447    {
448        let item = item.into();
449
450        let Items::EnvelopeItems(ref mut items) = self.items else {
451            if item != EnvelopeItem::Raw {
452                eprintln!(
453                    "WARNING: This envelope contains raw items. Adding an item is not supported."
454                );
455            }
456            return;
457        };
458
459        if self.headers.event_id.is_none() {
460            if let EnvelopeItem::Event(ref event) = item {
461                self.headers.event_id = Some(event.event_id);
462            } else if let EnvelopeItem::Transaction(ref transaction) = item {
463                self.headers.event_id = Some(transaction.event_id);
464            }
465        }
466        items.push(item);
467    }
468
469    /// Create an [`Iterator`] over all the [`EnvelopeItem`]s.
470    pub fn items(&self) -> EnvelopeItemIter<'_> {
471        let inner = match &self.items {
472            Items::EnvelopeItems(items) => items.iter(),
473            Items::Raw(_) => [].iter(),
474        };
475
476        EnvelopeItemIter { inner }
477    }
478
479    /// Consume the Envelope and create an [`Iterator`] over all
480    /// owned [`EnvelopeItem`]s.
481    ///
482    /// Raw envelopes yield an empty iterator.
483    pub fn into_items(self) -> impl Iterator<Item = EnvelopeItem> {
484        match self.items {
485            Items::EnvelopeItems(items) => items.into_iter(),
486            Items::Raw(_) => Default::default(),
487        }
488    }
489
490    /// Returns the Envelope headers.
491    pub fn headers(&self) -> &EnvelopeHeaders {
492        &self.headers
493    }
494
495    /// Sets the Envelope headers.
496    #[must_use]
497    pub fn with_headers(mut self, headers: EnvelopeHeaders) -> Self {
498        self.headers = headers;
499        self
500    }
501
502    /// Returns the Envelopes Uuid, if any.
503    pub fn uuid(&self) -> Option<&Uuid> {
504        self.headers.event_id.as_ref()
505    }
506
507    /// Returns the [`Event`] contained in this Envelope, if any.
508    ///
509    /// [`Event`]: struct.Event.html
510    pub fn event(&self) -> Option<&Event<'static>> {
511        let Items::EnvelopeItems(ref items) = self.items else {
512            return None;
513        };
514
515        items.iter().find_map(|item| match item {
516            EnvelopeItem::Event(event) => Some(event),
517            _ => None,
518        })
519    }
520
521    /// Filters the Envelope's [`EnvelopeItem`]s with an [`EnvelopeFilter`],
522    /// and returns a new Envelope containing only the filtered items.
523    ///
524    /// Additionally, [`EnvelopeItem::Attachment`]s are only kept if the Envelope
525    /// contains an [`EnvelopeItem::Event`] or [`EnvelopeItem::Transaction`].
526    ///
527    /// [`None`] is returned if no items remain in the Envelope after filtering.
528    pub fn filter<F>(self, mut filter: F) -> Option<Self>
529    where
530        F: EnvelopeFilter,
531    {
532        let Items::EnvelopeItems(items) = self.items else {
533            return if filter.filter(&EnvelopeItem::Raw) {
534                Some(self)
535            } else {
536                filter.on_filtered(EnvelopeItem::Raw);
537                None
538            };
539        };
540
541        let mut filtered = Envelope::new();
542        for item in items {
543            if filter.filter(&item) {
544                filtered.add_item(item);
545            } else {
546                filter.on_filtered(item);
547            }
548        }
549
550        // filter again, removing attachments which do not make any sense without
551        // an event/transaction
552        if filtered.uuid().is_none() {
553            if let Items::EnvelopeItems(ref mut items) = filtered.items {
554                let old_items = mem::take(items);
555                *items = old_items
556                    .into_iter()
557                    .filter_map(|item| match item {
558                        EnvelopeItem::Attachment(..) => {
559                            filter.on_filtered(item);
560                            None
561                        }
562                        _ => Some(item),
563                    })
564                    .collect();
565            }
566        }
567
568        if filtered.items.is_empty() {
569            None
570        } else {
571            Some(filtered)
572        }
573    }
574
575    /// Serialize the Envelope into the given [`Write`].
576    ///
577    /// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
578    pub fn to_writer<W>(&self, mut writer: W) -> std::io::Result<()>
579    where
580        W: Write,
581    {
582        let items = match &self.items {
583            Items::Raw(bytes) => return writer.write_all(bytes).map(|_| ()),
584            Items::EnvelopeItems(items) => items,
585        };
586
587        // write the headers:
588        serde_json::to_writer(&mut writer, &self.headers)?;
589        writeln!(writer)?;
590
591        let mut item_buf = Vec::new();
592        // write each item:
593        for item in items {
594            // we write them to a temporary buffer first, since we need their length
595            match item {
596                EnvelopeItem::Event(event) => serde_json::to_writer(&mut item_buf, event)?,
597                EnvelopeItem::SessionUpdate(session) => {
598                    serde_json::to_writer(&mut item_buf, session)?
599                }
600                EnvelopeItem::SessionAggregates(aggregates) => {
601                    serde_json::to_writer(&mut item_buf, aggregates)?
602                }
603                EnvelopeItem::Transaction(transaction) => {
604                    serde_json::to_writer(&mut item_buf, transaction)?
605                }
606                EnvelopeItem::Attachment(attachment) => {
607                    attachment.to_writer(&mut writer)?;
608                    writeln!(writer)?;
609                    continue;
610                }
611                EnvelopeItem::MonitorCheckIn(check_in) => {
612                    serde_json::to_writer(&mut item_buf, check_in)?
613                }
614                EnvelopeItem::ClientReport(client_report) => {
615                    serde_json::to_writer(&mut item_buf, client_report)?
616                }
617                EnvelopeItem::ItemContainer(container) => match container {
618                    ItemContainer::Logs(logs) => {
619                        let wrapper = ItemsSerdeWrapper { items: logs.into() };
620                        serde_json::to_writer(&mut item_buf, &wrapper)?
621                    }
622                    ItemContainer::Metrics(metrics) => {
623                        let wrapper = ItemsSerdeWrapper {
624                            items: metrics.into(),
625                        };
626                        serde_json::to_writer(&mut item_buf, &wrapper)?
627                    }
628                },
629                EnvelopeItem::Raw => {
630                    continue;
631                }
632            }
633            let item_type = item
634                .item_type()
635                .expect("raw envelope items are skipped during serialization");
636            let item_type = serde_json::to_string(&item_type)?;
637
638            if let EnvelopeItem::ItemContainer(container) = item {
639                writeln!(
640                    writer,
641                    r#"{{"type":{},"item_count":{},"content_type":"{}"}}"#,
642                    item_type,
643                    container.len(),
644                    container.content_type()
645                )?;
646            } else {
647                writeln!(
648                    writer,
649                    r#"{{"type":{},"length":{}}}"#,
650                    item_type,
651                    item_buf.len()
652                )?;
653            }
654            writer.write_all(&item_buf)?;
655            writeln!(writer)?;
656            item_buf.clear();
657        }
658
659        Ok(())
660    }
661
662    /// Creates a new Envelope from slice.
663    pub fn from_slice(slice: &[u8]) -> Result<Envelope, EnvelopeError> {
664        let (headers, offset) = Self::parse_headers(slice)?;
665        let items = Self::parse_items(slice, offset)?;
666
667        let mut envelope = Envelope {
668            headers,
669            ..Default::default()
670        };
671
672        for item in items {
673            envelope.add_item(item);
674        }
675
676        Ok(envelope)
677    }
678
679    /// Creates a new raw Envelope from the given buffer.
680    pub fn from_bytes_raw(bytes: Vec<u8>) -> Result<Self, EnvelopeError> {
681        Ok(Self {
682            items: Items::Raw(bytes),
683            ..Default::default()
684        })
685    }
686
687    /// Creates a new Envelope from path.
688    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Envelope, EnvelopeError> {
689        let bytes = std::fs::read(path).map_err(|_| EnvelopeError::UnexpectedEof)?;
690        Envelope::from_slice(&bytes)
691    }
692
693    /// Creates a new Envelope from path without attempting to parse anything.
694    ///
695    /// The resulting Envelope will have no `event_id` and the file contents will
696    /// be contained verbatim in the `items` field.
697    pub fn from_path_raw<P: AsRef<Path>>(path: P) -> Result<Self, EnvelopeError> {
698        let bytes = std::fs::read(path).map_err(|_| EnvelopeError::UnexpectedEof)?;
699        Self::from_bytes_raw(bytes)
700    }
701
702    fn parse_headers(slice: &[u8]) -> Result<(EnvelopeHeaders, usize), EnvelopeError> {
703        let first_line = slice
704            .split(|b| *b == b'\n')
705            .next()
706            .ok_or(EnvelopeError::MissingHeader)?;
707
708        let headers: EnvelopeHeaders =
709            serde_json::from_slice(first_line).map_err(EnvelopeError::InvalidHeader)?;
710
711        let offset = first_line.len();
712        Self::require_termination(slice, offset)?;
713
714        // Valid slices cannot be larger than `isize::MAX` bytes:
715        // https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety
716        let byte_after_header = offset
717            .checked_add(1)
718            .expect("offset is at most isize::MAX; adding 1 cannot overflow usize");
719
720        Ok((headers, byte_after_header))
721    }
722
723    fn parse_items(slice: &[u8], mut offset: usize) -> Result<Vec<EnvelopeItem>, EnvelopeError> {
724        let mut items = Vec::new();
725
726        while offset < slice.len() {
727            let bytes = slice
728                .get(offset..)
729                .ok_or(EnvelopeError::MissingItemHeader)?;
730            let (item, item_offset) = Self::parse_item(bytes)?;
731            // `bytes` is `slice[offset..]`, so `bytes.len() == slice.len() - offset`.
732            // Since `item_offset <= bytes.len() + 1`, `offset + item_offset <= slice.len() + 1`.
733            // Valid slices cannot exceed `isize::MAX` bytes, so `slice.len() + 1` cannot overflow `usize`.
734            offset = offset
735                .checked_add(item_offset)
736                .expect("offset + item_offset is at most slice.len() + 1");
737            items.push(item);
738        }
739
740        Ok(items)
741    }
742
743    /// Parses one envelope item from the beginning of `slice`.
744    ///
745    /// Returns the parsed item and the offset at which parsing should continue.
746    ///
747    /// The offset is relative to `slice`. It points just after the payload terminator if one is
748    /// present, or one byte past the end of `slice` if the payload ends at the end of `slice`.
749    /// The offset therefore satisfies `1 <= offset <= slice.len() + 1`.
750    fn parse_item(slice: &[u8]) -> Result<(EnvelopeItem, usize), EnvelopeError> {
751        let mut stream = serde_json::Deserializer::from_slice(slice).into_iter();
752
753        let header: EnvelopeItemHeader = match stream.next() {
754            None => return Err(EnvelopeError::UnexpectedEof),
755            Some(Err(error)) => return Err(EnvelopeError::InvalidItemHeader(error)),
756            Some(Ok(header)) => header,
757        };
758
759        // Each header is terminated by a UNIX newline.
760        let header_end = stream.byte_offset();
761        Self::require_termination(slice, header_end)?;
762
763        // The last header does not require a trailing newline, so `payload_start` may point
764        // past the end of the buffer.
765        // The checked_add never overflows since slices cannot exceed isize::MAX bytes:
766        // https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety
767        let payload_start = std::cmp::min(header_end, slice.len().saturating_sub(1))
768            .checked_add(1)
769            .expect("&[u8] length is at most isize::MAX; adding one cannot overflow usize");
770        let payload_end = match header.length {
771            Some(len) => {
772                let payload_end = payload_start.saturating_add(len);
773                if slice.len() < payload_end {
774                    return Err(EnvelopeError::UnexpectedEof);
775                }
776
777                // Each payload is terminated by a UNIX newline.
778                Self::require_termination(slice, payload_end)?;
779                payload_end
780            }
781            None => match slice.get(payload_start..) {
782                Some(range) => match range.iter().position(|&b| b == b'\n') {
783                    Some(relative_end) => payload_start.checked_add(relative_end).expect(
784                        "relative_end is an index into slice[payload_start..]; \
785                        so the sum is within slice and cannot overflow usize",
786                    ),
787                    None => slice.len(),
788                },
789                None => slice.len(),
790            },
791        };
792
793        let payload = slice.get(payload_start..payload_end).unwrap();
794
795        let item = match header.r#type {
796            EnvelopeItemType::Event => serde_json::from_slice(payload).map(EnvelopeItem::Event),
797            EnvelopeItemType::Transaction => {
798                serde_json::from_slice(payload).map(EnvelopeItem::Transaction)
799            }
800            EnvelopeItemType::SessionUpdate => {
801                serde_json::from_slice(payload).map(EnvelopeItem::SessionUpdate)
802            }
803            EnvelopeItemType::SessionAggregates => {
804                serde_json::from_slice(payload).map(EnvelopeItem::SessionAggregates)
805            }
806            EnvelopeItemType::Attachment => Ok(EnvelopeItem::Attachment(Attachment {
807                buffer: payload.to_owned(),
808                filename: header.filename.unwrap_or_default(),
809                content_type: header.content_type,
810                ty: header.attachment_type,
811            })),
812            EnvelopeItemType::MonitorCheckIn => {
813                serde_json::from_slice(payload).map(EnvelopeItem::MonitorCheckIn)
814            }
815            EnvelopeItemType::ClientReport => {
816                serde_json::from_slice(payload).map(EnvelopeItem::ClientReport)
817            }
818            EnvelopeItemType::LogsContainer => {
819                serde_json::from_slice::<ItemsSerdeWrapper<_>>(payload)
820                    .map(|x| EnvelopeItem::ItemContainer(ItemContainer::Logs(x.items.into())))
821            }
822            EnvelopeItemType::MetricsContainer => {
823                serde_json::from_slice::<ItemsSerdeWrapper<_>>(payload)
824                    .map(|x| EnvelopeItem::ItemContainer(ItemContainer::Metrics(x.items.into())))
825            }
826        }
827        .map_err(EnvelopeError::InvalidItemPayload)?;
828
829        // Valid slices cannot be larger than `isize::MAX` bytes:
830        // https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety
831        let byte_after_payload = payload_end
832            .checked_add(1)
833            .expect("payload_end <= slice.len() <= isize::MAX, so adding 1 cannot overflow usize");
834
835        Ok((item, byte_after_payload))
836    }
837
838    fn require_termination(slice: &[u8], offset: usize) -> Result<(), EnvelopeError> {
839        match slice.get(offset) {
840            Some(&b'\n') | None => Ok(()),
841            Some(_) => Err(EnvelopeError::MissingNewline),
842        }
843    }
844}
845
846impl<T> From<T> for Envelope
847where
848    T: Into<EnvelopeItem>,
849{
850    fn from(item: T) -> Self {
851        let mut envelope = Self::default();
852        envelope.add_item(item.into());
853        envelope
854    }
855}
856
857impl<F, C> EnvelopeFilter for EnvelopeFilterCallbacks<F, C>
858where
859    F: FnMut(&EnvelopeItem) -> bool,
860    C: FnMut(EnvelopeItem),
861{
862    fn filter(&mut self, item: &EnvelopeItem) -> bool {
863        (self.filter)(item)
864    }
865
866    fn on_filtered(&mut self, item: EnvelopeItem) {
867        (self.on_filtered)(item);
868    }
869}
870
871impl<F> EnvelopeFilter for F
872where
873    F: FnMut(&EnvelopeItem) -> bool,
874{
875    fn filter(&mut self, item: &EnvelopeItem) -> bool {
876        self(item)
877    }
878}
879
880mod private {
881    use super::*;
882
883    pub trait Sealed {}
884
885    impl<F, C> Sealed for EnvelopeFilterCallbacks<F, C>
886    where
887        F: FnMut(&EnvelopeItem) -> bool,
888        C: FnMut(EnvelopeItem),
889    {
890    }
891
892    impl<F> Sealed for F where F: FnMut(&EnvelopeItem) -> bool {}
893}
894
895#[cfg(test)]
896mod test {
897    use std::borrow::Cow;
898    use std::str::FromStr;
899    use std::time::{Duration, SystemTime};
900
901    use protocol::Map;
902    use serde_json::Value;
903    use time::format_description::well_known::Rfc3339;
904    use time::OffsetDateTime;
905    use uuid::Uuid;
906
907    use super::*;
908    use crate::protocol::client_report::{Item, LossSource};
909    use crate::protocol::v7::client_report::Category;
910    use crate::protocol::v7::{
911        ClientReport, Level, LogLevel, MetricType, MonitorCheckInStatus, MonitorConfig,
912        MonitorSchedule, SampleRand, SessionAggregateItem, SessionAttributes, SessionStatus, Span,
913    };
914
915    fn to_str(envelope: Envelope) -> String {
916        let mut vec = Vec::new();
917        envelope.to_writer(&mut vec).unwrap();
918        String::from_utf8_lossy(&vec).to_string()
919    }
920
921    fn timestamp(s: &str) -> SystemTime {
922        let dt = OffsetDateTime::parse(s, &Rfc3339).unwrap();
923        let secs = dt.unix_timestamp() as u64;
924        let nanos = dt.nanosecond();
925        let duration = Duration::new(secs, nanos);
926        SystemTime::UNIX_EPOCH.checked_add(duration).unwrap()
927    }
928
929    fn session_attributes() -> SessionAttributes<'static> {
930        SessionAttributes {
931            release: Cow::Borrowed("release@1.0.0"),
932            environment: Some(Cow::Borrowed("production")),
933            ip_address: None,
934            user_agent: None,
935        }
936    }
937
938    fn collect_losses(envelope: &Envelope) -> Vec<(Category, u64)> {
939        envelope
940            .losses()
941            .map(|loss| (loss.category, loss.quantity))
942            .collect()
943    }
944
945    #[test]
946    fn test_empty() {
947        assert_eq!(to_str(Envelope::new()), "{}\n");
948    }
949
950    #[test]
951    fn raw_roundtrip() {
952        let buf = r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
953{"type":"event","length":74}
954{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
955"#;
956        let envelope = Envelope::from_bytes_raw(buf.to_string().into_bytes()).unwrap();
957        let serialized = to_str(envelope);
958        assert_eq!(&serialized, buf);
959
960        let random_invalid_bytes = b"oh stahp!\0\x01\x02";
961        let envelope = Envelope::from_bytes_raw(random_invalid_bytes.to_vec()).unwrap();
962        let mut serialized = Vec::new();
963        envelope.to_writer(&mut serialized).unwrap();
964        assert_eq!(&serialized, random_invalid_bytes);
965    }
966
967    #[test]
968    fn test_event() {
969        let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
970        let timestamp = timestamp("2020-07-20T14:51:14.296Z");
971        let event = Event {
972            event_id,
973            timestamp,
974            ..Default::default()
975        };
976        let envelope: Envelope = event.into();
977        assert_eq!(
978            to_str(envelope),
979            r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
980{"type":"event","length":74}
981{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
982"#
983        )
984    }
985
986    #[test]
987    fn test_session() {
988        let session_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
989        let started = timestamp("2020-07-20T14:51:14.296Z");
990        let session = SessionUpdate {
991            session_id,
992            distinct_id: Some("foo@bar.baz".to_owned()),
993            sequence: None,
994            timestamp: None,
995            started,
996            init: true,
997            duration: Some(1.234),
998            status: SessionStatus::Ok,
999            errors: 123,
1000            attributes: SessionAttributes {
1001                release: "foo-bar@1.2.3".into(),
1002                environment: Some("production".into()),
1003                ip_address: None,
1004                user_agent: None,
1005            },
1006        };
1007        let mut envelope = Envelope::new();
1008        envelope.add_item(session);
1009        assert_eq!(
1010            to_str(envelope),
1011            r#"{}
1012{"type":"session","length":222}
1013{"sid":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c","did":"foo@bar.baz","started":"2020-07-20T14:51:14.296Z","init":true,"duration":1.234,"status":"ok","errors":123,"attrs":{"release":"foo-bar@1.2.3","environment":"production"}}
1014"#
1015        )
1016    }
1017
1018    #[test]
1019    fn test_transaction() {
1020        let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
1021        let span_id = "d42cee9fc3e74f5c".parse().unwrap();
1022        let trace_id = "335e53d614474acc9f89e632b776cc28".parse().unwrap();
1023        let start_timestamp = timestamp("2020-07-20T14:51:14.296Z");
1024        let spans = vec![Span {
1025            span_id,
1026            trace_id,
1027            start_timestamp,
1028            ..Default::default()
1029        }];
1030        let transaction = Transaction {
1031            event_id,
1032            start_timestamp,
1033            spans,
1034            ..Default::default()
1035        };
1036        let envelope: Envelope = transaction.into();
1037        assert_eq!(
1038            to_str(envelope),
1039            r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
1040{"type":"transaction","length":200}
1041{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","start_timestamp":1595256674.296,"spans":[{"span_id":"d42cee9fc3e74f5c","trace_id":"335e53d614474acc9f89e632b776cc28","start_timestamp":1595256674.296}]}
1042"#
1043        )
1044    }
1045
1046    #[test]
1047    fn test_monitor_checkin() {
1048        let check_in_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
1049
1050        let check_in = MonitorCheckIn {
1051            check_in_id,
1052            monitor_slug: "my-monitor".into(),
1053            status: MonitorCheckInStatus::Ok,
1054            duration: Some(123.4),
1055            environment: Some("production".into()),
1056            monitor_config: Some(MonitorConfig {
1057                schedule: MonitorSchedule::Crontab {
1058                    value: "12 0 * * *".into(),
1059                },
1060                checkin_margin: Some(5),
1061                max_runtime: Some(30),
1062                timezone: Some("UTC".into()),
1063                failure_issue_threshold: None,
1064                recovery_threshold: None,
1065            }),
1066        };
1067        let envelope: Envelope = check_in.into();
1068        assert_eq!(
1069            to_str(envelope),
1070            r#"{}
1071{"type":"check_in","length":259}
1072{"check_in_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","monitor_slug":"my-monitor","status":"ok","environment":"production","duration":123.4,"monitor_config":{"schedule":{"type":"crontab","value":"12 0 * * *"},"checkin_margin":5,"max_runtime":30,"timezone":"UTC"}}
1073"#
1074        )
1075    }
1076
1077    #[test]
1078    fn test_monitor_checkin_with_thresholds() {
1079        let check_in_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
1080
1081        let check_in = MonitorCheckIn {
1082            check_in_id,
1083            monitor_slug: "my-monitor".into(),
1084            status: MonitorCheckInStatus::Ok,
1085            duration: Some(123.4),
1086            environment: Some("production".into()),
1087            monitor_config: Some(MonitorConfig {
1088                schedule: MonitorSchedule::Crontab {
1089                    value: "12 0 * * *".into(),
1090                },
1091                checkin_margin: Some(5),
1092                max_runtime: Some(30),
1093                timezone: Some("UTC".into()),
1094                failure_issue_threshold: Some(4),
1095                recovery_threshold: Some(7),
1096            }),
1097        };
1098        let envelope: Envelope = check_in.into();
1099        assert_eq!(
1100            to_str(envelope),
1101            r#"{}
1102{"type":"check_in","length":310}
1103{"check_in_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","monitor_slug":"my-monitor","status":"ok","environment":"production","duration":123.4,"monitor_config":{"schedule":{"type":"crontab","value":"12 0 * * *"},"checkin_margin":5,"max_runtime":30,"timezone":"UTC","failure_issue_threshold":4,"recovery_threshold":7}}
1104"#
1105        )
1106    }
1107
1108    #[test]
1109    fn test_event_with_attachment() {
1110        let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
1111        let timestamp = timestamp("2020-07-20T14:51:14.296Z");
1112        let event = Event {
1113            event_id,
1114            timestamp,
1115            ..Default::default()
1116        };
1117        let mut envelope: Envelope = event.into();
1118
1119        envelope.add_item(Attachment {
1120            buffer: "some content".as_bytes().to_vec(),
1121            filename: "file.txt".to_string(),
1122            ..Default::default()
1123        });
1124
1125        assert_eq!(
1126            to_str(envelope),
1127            r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
1128{"type":"event","length":74}
1129{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
1130{"type":"attachment","length":12,"filename":"file.txt","attachment_type":"event.attachment","content_type":"application/octet-stream"}
1131some content
1132"#
1133        )
1134    }
1135
1136    #[test]
1137    fn test_deserialize_envelope_empty() {
1138        // Without terminating newline after header
1139        let bytes = b"{\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}";
1140        let envelope = Envelope::from_slice(bytes).unwrap();
1141
1142        let event_id = Uuid::from_str("9ec79c33ec9942ab8353589fcb2e04dc").unwrap();
1143        assert_eq!(envelope.headers.event_id, Some(event_id));
1144        assert_eq!(envelope.items().count(), 0);
1145    }
1146
1147    #[test]
1148    fn test_deserialize_envelope_empty_newline() {
1149        // With terminating newline after header
1150        let bytes = b"{\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n";
1151        let envelope = Envelope::from_slice(bytes).unwrap();
1152        assert_eq!(envelope.items().count(), 0);
1153    }
1154
1155    #[test]
1156    fn test_deserialize_envelope_empty_item_newline() {
1157        // With terminating newline after item payload
1158        let bytes = b"\
1159             {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1160             {\"type\":\"attachment\",\"length\":0}\n\
1161             \n\
1162             {\"type\":\"attachment\",\"length\":0}\n\
1163             ";
1164
1165        let envelope = Envelope::from_slice(bytes).unwrap();
1166        assert_eq!(envelope.items().count(), 2);
1167
1168        let mut items = envelope.items();
1169
1170        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1171            assert_eq!(attachment.buffer.len(), 0);
1172        } else {
1173            panic!("invalid item type");
1174        }
1175
1176        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1177            assert_eq!(attachment.buffer.len(), 0);
1178        } else {
1179            panic!("invalid item type");
1180        }
1181    }
1182
1183    #[test]
1184    fn test_deserialize_envelope_empty_item_eof() {
1185        // With terminating newline after item payload
1186        let bytes = b"\
1187             {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1188             {\"type\":\"attachment\",\"length\":0}\n\
1189             \n\
1190             {\"type\":\"attachment\",\"length\":0}\
1191             ";
1192
1193        let envelope = Envelope::from_slice(bytes).unwrap();
1194        assert_eq!(envelope.items().count(), 2);
1195
1196        let mut items = envelope.items();
1197
1198        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1199            assert_eq!(attachment.buffer.len(), 0);
1200        } else {
1201            panic!("invalid item type");
1202        }
1203
1204        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1205            assert_eq!(attachment.buffer.len(), 0);
1206        } else {
1207            panic!("invalid item type");
1208        }
1209    }
1210
1211    #[test]
1212    fn test_deserialize_envelope_implicit_length() {
1213        // With terminating newline after item payload
1214        let bytes = b"\
1215             {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1216             {\"type\":\"attachment\"}\n\
1217             helloworld\n\
1218             ";
1219
1220        let envelope = Envelope::from_slice(bytes).unwrap();
1221        assert_eq!(envelope.items().count(), 1);
1222
1223        let mut items = envelope.items();
1224
1225        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1226            assert_eq!(attachment.buffer.len(), 10);
1227        } else {
1228            panic!("invalid item type");
1229        }
1230    }
1231
1232    #[test]
1233    fn test_deserialize_envelope_implicit_length_eof() {
1234        // With item ending the envelope
1235        let bytes = b"\
1236             {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1237             {\"type\":\"attachment\"}\n\
1238             helloworld\
1239             ";
1240
1241        let envelope = Envelope::from_slice(bytes).unwrap();
1242        assert_eq!(envelope.items().count(), 1);
1243
1244        let mut items = envelope.items();
1245
1246        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1247            assert_eq!(attachment.buffer.len(), 10);
1248        } else {
1249            panic!("invalid item type");
1250        }
1251    }
1252
1253    #[test]
1254    fn test_deserialize_envelope_implicit_length_empty_eof() {
1255        // Empty item with implicit length ending the envelope
1256        let bytes = b"\
1257             {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1258             {\"type\":\"attachment\"}\
1259             ";
1260
1261        let envelope = Envelope::from_slice(bytes).unwrap();
1262        assert_eq!(envelope.items().count(), 1);
1263
1264        let mut items = envelope.items();
1265
1266        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1267            assert_eq!(attachment.buffer.len(), 0);
1268        } else {
1269            panic!("invalid item type");
1270        }
1271    }
1272
1273    #[test]
1274    fn test_deserialize_envelope_multiple_items() {
1275        // With terminating newline
1276        let bytes = b"\
1277            {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1278            {\"type\":\"attachment\",\"length\":10,\"content_type\":\"text/plain\",\"filename\":\"hello.txt\"}\n\
1279            \xef\xbb\xbfHello\r\n\n\
1280            {\"type\":\"event\",\"length\":41,\"content_type\":\"application/json\",\"filename\":\"application.log\"}\n\
1281            {\"message\":\"hello world\",\"level\":\"error\"}\n\
1282            ";
1283
1284        let envelope = Envelope::from_slice(bytes).unwrap();
1285        assert_eq!(envelope.items().count(), 2);
1286
1287        let mut items = envelope.items();
1288
1289        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1290            assert_eq!(attachment.buffer.len(), 10);
1291            assert_eq!(attachment.buffer, b"\xef\xbb\xbfHello\r\n");
1292            assert_eq!(attachment.filename, "hello.txt");
1293            assert_eq!(attachment.content_type, Some("text/plain".to_string()));
1294        } else {
1295            panic!("invalid item type");
1296        }
1297
1298        if let EnvelopeItem::Event(event) = items.next().unwrap() {
1299            assert_eq!(event.message, Some("hello world".to_string()));
1300            assert_eq!(event.level, Level::Error);
1301        } else {
1302            panic!("invalid item type");
1303        }
1304    }
1305
1306    #[test]
1307    fn test_all_envelope_headers_roundtrip() {
1308        let bytes = br#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c","sdk":{"name":"3e934135-3f2b-49bc-8756-9f025b55143e","version":"3e31738e-4106-42d0-8be2-4a3a1bc648d3","integrations":["daec50ae-8729-49b5-82f7-991446745cd5","8fc94968-3499-4a2c-b4d7-ecc058d9c1b0"],"packages":[{"name":"b59a1949-9950-4203-b394-ddd8d02c9633","version":"3d7790f3-7f32-43f7-b82f-9f5bc85205a8"}]},"sent_at":"2020-02-07T14:16:00Z","trace":{"trace_id":"65bcd18546c942069ed957b15b4ace7c","public_key":"5d593cac-f833-4845-bb23-4eabdf720da2","sample_rate":"0.00000021","sample_rand":"0.123456","sampled":"true","environment":"0666ab02-6364-4135-aa59-02e8128ce052","transaction":"0252ec25-cd0a-4230-bd2f-936a4585637e"}}
1309{"type":"event","length":74}
1310{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
1311"#;
1312
1313        let envelope = Envelope::from_slice(bytes);
1314        assert!(envelope.is_ok());
1315        let envelope = envelope.unwrap();
1316        let serialized = to_str(envelope);
1317        assert_eq!(bytes, serialized.as_bytes());
1318    }
1319
1320    #[test]
1321    fn test_sample_rand_rounding() {
1322        let envelope = Envelope::new().with_headers(
1323            EnvelopeHeaders::new().with_trace(
1324                DynamicSamplingContext::new()
1325                    .with_sample_rand(SampleRand::try_from(0.999_999_9).unwrap()),
1326            ),
1327        );
1328        let expected = br#"{"trace":{"sample_rand":"0.999999"}}
1329"#;
1330
1331        let serialized = to_str(envelope);
1332        assert_eq!(expected, serialized.as_bytes());
1333    }
1334
1335    #[test]
1336    fn test_metric_container_header() {
1337        let metrics: EnvelopeItem = vec![Metric {
1338            r#type: protocol::MetricType::Counter,
1339            name: "api.requests".into(),
1340            value: 1.0,
1341            timestamp: timestamp("2026-03-02T13:36:02.000Z"),
1342            trace_id: "335e53d614474acc9f89e632b776cc28".parse().unwrap(),
1343            span_id: None,
1344            unit: None,
1345            attributes: Map::new(),
1346        }]
1347        .into();
1348
1349        let mut envelope = Envelope::new();
1350        envelope.add_item(metrics);
1351
1352        let expected = [
1353            serde_json::json!({}),
1354            serde_json::json!({
1355                "type": "trace_metric",
1356                "item_count": 1,
1357                "content_type": "application/vnd.sentry.items.trace-metric+json"
1358            }),
1359            serde_json::json!({
1360                "items": [{
1361                    "type": "counter",
1362                    "name": "api.requests",
1363                    "value": 1.0,
1364                    "timestamp": 1772458562,
1365                    "trace_id": "335e53d614474acc9f89e632b776cc28"
1366                }]
1367            }),
1368        ];
1369
1370        let serialized = to_str(envelope);
1371        let actual = serialized
1372            .lines()
1373            .map(|line| serde_json::from_str::<Value>(line).expect("envelope has invalid JSON"));
1374
1375        assert!(actual.eq(expected.into_iter()));
1376    }
1377
1378    // Test all possible item types in a single envelope
1379    #[test]
1380    fn test_deserialize_serialized() {
1381        // Event
1382        let event = Event {
1383            event_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap(),
1384            timestamp: timestamp("2020-07-20T14:51:14.296Z"),
1385            ..Default::default()
1386        };
1387
1388        // Transaction
1389        let transaction = Transaction {
1390            event_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9d").unwrap(),
1391            start_timestamp: timestamp("2020-07-20T14:51:14.296Z"),
1392            spans: vec![Span {
1393                span_id: "d42cee9fc3e74f5c".parse().unwrap(),
1394                trace_id: "335e53d614474acc9f89e632b776cc28".parse().unwrap(),
1395                start_timestamp: timestamp("2020-07-20T14:51:14.296Z"),
1396                ..Default::default()
1397            }],
1398            ..Default::default()
1399        };
1400
1401        // Session
1402        let session = SessionUpdate {
1403            session_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap(),
1404            distinct_id: Some("foo@bar.baz".to_owned()),
1405            sequence: None,
1406            timestamp: None,
1407            started: timestamp("2020-07-20T14:51:14.296Z"),
1408            init: true,
1409            duration: Some(1.234),
1410            status: SessionStatus::Ok,
1411            errors: 123,
1412            attributes: SessionAttributes {
1413                release: "foo-bar@1.2.3".into(),
1414                environment: Some("production".into()),
1415                ip_address: None,
1416                user_agent: None,
1417            },
1418        };
1419
1420        // Attachment
1421        let attachment = Attachment {
1422            buffer: "some content".as_bytes().to_vec(),
1423            filename: "file.txt".to_string(),
1424            ..Default::default()
1425        };
1426
1427        let mut attributes = Map::new();
1428        attributes.insert("key".into(), "value".into());
1429        attributes.insert("num".into(), 10.into());
1430        attributes.insert("val".into(), 10.2.into());
1431        attributes.insert("bool".into(), false.into());
1432        let mut attributes_2 = attributes.clone();
1433        attributes_2.insert("more".into(), true.into());
1434        let logs: EnvelopeItem = vec![
1435            Log {
1436                level: protocol::LogLevel::Warn,
1437                body: "test".to_owned(),
1438                trace_id: Some("335e53d614474acc9f89e632b776cc28".parse().unwrap()),
1439                timestamp: timestamp("2022-07-25T14:51:14.296Z"),
1440                severity_number: Some(1.try_into().unwrap()),
1441                attributes,
1442            },
1443            Log {
1444                level: protocol::LogLevel::Error,
1445                body: "a body".to_owned(),
1446                trace_id: Some("332253d614472a2c9f89e232b7762c28".parse().unwrap()),
1447                timestamp: timestamp("2021-07-21T14:51:14.296Z"),
1448                severity_number: Some(1.try_into().unwrap()),
1449                attributes: attributes_2,
1450            },
1451        ]
1452        .into();
1453
1454        let mut metric_attributes = Map::new();
1455        metric_attributes.insert("route".into(), "/users".into());
1456        let metrics: EnvelopeItem = vec![Metric {
1457            r#type: protocol::MetricType::Distribution,
1458            name: "response.time".into(),
1459            value: 123.4,
1460            timestamp: timestamp("2022-07-26T14:51:14.296Z"),
1461            trace_id: "335e53d614474acc9f89e632b776cc28".parse().unwrap(),
1462            span_id: Some("d42cee9fc3e74f5c".parse().unwrap()),
1463            unit: Some("millisecond".into()),
1464            attributes: metric_attributes,
1465        }]
1466        .into();
1467
1468        let mut envelope: Envelope = Envelope::new();
1469        envelope.add_item(event);
1470        envelope.add_item(transaction);
1471        envelope.add_item(session);
1472        envelope.add_item(attachment);
1473        envelope.add_item(logs);
1474        envelope.add_item(metrics);
1475
1476        let serialized = to_str(envelope);
1477        let deserialized = Envelope::from_slice(serialized.as_bytes()).unwrap();
1478        assert_eq!(serialized, to_str(deserialized))
1479    }
1480
1481    #[test]
1482    fn losses_on_drop_maps_event_to_error() {
1483        let envelope: Envelope = Event::default().into();
1484
1485        assert_eq!(collect_losses(&envelope), vec![(Category::Error, 1)]);
1486    }
1487
1488    #[test]
1489    fn losses_on_drop_maps_session_update_to_session() {
1490        let envelope: Envelope = SessionUpdate {
1491            session_id: Uuid::nil(),
1492            distinct_id: None,
1493            sequence: None,
1494            timestamp: None,
1495            started: SystemTime::UNIX_EPOCH,
1496            init: false,
1497            duration: None,
1498            status: SessionStatus::Ok,
1499            errors: 0,
1500            attributes: session_attributes(),
1501        }
1502        .into();
1503
1504        assert_eq!(collect_losses(&envelope), vec![(Category::Session, 1)]);
1505    }
1506
1507    #[test]
1508    fn losses_on_drop_maps_attachment_to_buffer_bytes() {
1509        let envelope: Envelope = Attachment {
1510            buffer: b"attachment".to_vec(),
1511            filename: "attachment.bin".to_owned(),
1512            ..Default::default()
1513        }
1514        .into();
1515
1516        assert_eq!(collect_losses(&envelope), vec![(Category::Attachment, 10)]);
1517    }
1518
1519    #[test]
1520    fn losses_on_drop_maps_monitor_check_in_to_monitor() {
1521        let envelope: Envelope = MonitorCheckIn {
1522            check_in_id: Uuid::nil(),
1523            monitor_slug: "monitor".to_owned(),
1524            status: MonitorCheckInStatus::Ok,
1525            environment: None,
1526            duration: None,
1527            monitor_config: None,
1528        }
1529        .into();
1530
1531        assert_eq!(collect_losses(&envelope), vec![(Category::Monitor, 1)]);
1532    }
1533
1534    #[test]
1535    fn losses_on_drop_sums_session_aggregate_status_counts() {
1536        let envelope: Envelope = SessionAggregates {
1537            aggregates: vec![
1538                SessionAggregateItem {
1539                    started: SystemTime::UNIX_EPOCH,
1540                    distinct_id: None,
1541                    exited: 2,
1542                    errored: 3,
1543                    abnormal: 5,
1544                    crashed: 7,
1545                },
1546                SessionAggregateItem {
1547                    started: SystemTime::UNIX_EPOCH,
1548                    distinct_id: None,
1549                    exited: 11,
1550                    errored: 13,
1551                    abnormal: 17,
1552                    crashed: 19,
1553                },
1554            ],
1555            attributes: session_attributes(),
1556        }
1557        .into();
1558
1559        assert_eq!(collect_losses(&envelope), vec![(Category::Session, 77)]);
1560    }
1561
1562    #[test]
1563    fn losses_on_drop_counts_transaction_root_span() {
1564        let envelope: Envelope = Transaction {
1565            spans: vec![],
1566            ..Default::default()
1567        }
1568        .into();
1569
1570        assert_eq!(
1571            collect_losses(&envelope),
1572            vec![(Category::Transaction, 1), (Category::Span, 1)]
1573        );
1574    }
1575
1576    #[test]
1577    fn losses_on_drop_counts_transaction_child_spans() {
1578        let envelope: Envelope = Transaction {
1579            spans: vec![Span::default(), Span::default(), Span::default()],
1580            ..Default::default()
1581        }
1582        .into();
1583
1584        assert_eq!(
1585            collect_losses(&envelope),
1586            vec![(Category::Transaction, 1), (Category::Span, 4)]
1587        );
1588    }
1589
1590    #[test]
1591    fn losses_on_drop_counts_minimal_log_bytes() {
1592        let envelope: Envelope = vec![Log {
1593            level: LogLevel::Info,
1594            body: String::new(),
1595            trace_id: None,
1596            timestamp: SystemTime::UNIX_EPOCH,
1597            severity_number: None,
1598            attributes: Map::new(),
1599        }]
1600        .into();
1601
1602        assert_eq!(
1603            collect_losses(&envelope),
1604            vec![(Category::LogItem, 1), (Category::LogByte, 1)]
1605        );
1606    }
1607
1608    #[test]
1609    fn losses_on_drop_counts_complex_log_bytes() {
1610        let mut attributes = Map::new();
1611        attributes.insert("k1".to_owned(), "string value".into());
1612        attributes.insert("k2".to_owned(), u64::MAX.into());
1613        attributes.insert("k3".to_owned(), 42.0.into());
1614        attributes.insert("k4".to_owned(), false.into());
1615        attributes.insert(
1616            "k5".to_owned(),
1617            serde_json::json!({
1618                "nested": {
1619                    "array": [1.0, 2, -12, "7 bytes", false]
1620                }
1621            })
1622            .into(),
1623        );
1624        let envelope: Envelope = vec![Log {
1625            level: LogLevel::Info,
1626            body: "7 bytes".to_owned(),
1627            trace_id: None,
1628            timestamp: SystemTime::UNIX_EPOCH,
1629            severity_number: None,
1630            attributes,
1631        }]
1632        .into();
1633
1634        assert_eq!(
1635            collect_losses(&envelope),
1636            vec![(Category::LogItem, 1), (Category::LogByte, 89)]
1637        );
1638    }
1639
1640    #[test]
1641    fn losses_on_drop_counts_minimal_metric_bytes() {
1642        let envelope: Envelope = vec![Metric {
1643            r#type: MetricType::Counter,
1644            name: Cow::Borrowed(""),
1645            value: 1.0,
1646            timestamp: SystemTime::UNIX_EPOCH,
1647            trace_id: Default::default(),
1648            span_id: None,
1649            unit: None,
1650            attributes: Map::new(),
1651        }]
1652        .into();
1653
1654        assert_eq!(
1655            collect_losses(&envelope),
1656            vec![(Category::TraceMetric, 1), (Category::TraceMetricByte, 8)]
1657        );
1658    }
1659
1660    #[test]
1661    fn losses_on_drop_counts_complex_metric_bytes() {
1662        let mut attributes = Map::new();
1663        attributes.insert(
1664            Cow::Borrowed("foo"),
1665            "ඞ and some more equals 33 bytes".into(),
1666        );
1667        let envelope: Envelope = vec![Metric {
1668            r#type: MetricType::Counter,
1669            name: Cow::Borrowed("counter"),
1670            value: 1.0,
1671            timestamp: SystemTime::UNIX_EPOCH,
1672            trace_id: Default::default(),
1673            span_id: None,
1674            unit: None,
1675            attributes,
1676        }]
1677        .into();
1678
1679        assert_eq!(
1680            collect_losses(&envelope),
1681            vec![(Category::TraceMetric, 1), (Category::TraceMetricByte, 51)]
1682        );
1683    }
1684
1685    #[test]
1686    fn losses_on_drop_skips_client_reports() {
1687        let envelope: Envelope = ClientReport::new(<[Item; 0]>::default()).into();
1688
1689        assert!(collect_losses(&envelope).is_empty());
1690    }
1691
1692    #[test]
1693    fn losses_on_drop_skips_raw_envelopes() {
1694        let envelope = Envelope::from_bytes_raw(b"not parsed".to_vec()).unwrap();
1695
1696        assert!(collect_losses(&envelope).is_empty());
1697    }
1698
1699    #[test]
1700    fn losses_on_drop_flattens_losses_from_all_envelope_items() {
1701        let log = Log {
1702            level: LogLevel::Warn,
1703            body: "flattened".to_owned(),
1704            trace_id: None,
1705            timestamp: SystemTime::UNIX_EPOCH,
1706            severity_number: None,
1707            attributes: Map::new(),
1708        };
1709        let mut envelope = Envelope::new();
1710        envelope.add_item(Event::default());
1711        envelope.add_item(Transaction {
1712            spans: vec![Span::default(), Span::default()],
1713            ..Default::default()
1714        });
1715        envelope.add_item(vec![log]);
1716        envelope.add_item(vec![Metric {
1717            r#type: MetricType::Counter,
1718            name: Cow::Borrowed("flattened.metric"),
1719            value: 1.0,
1720            timestamp: SystemTime::UNIX_EPOCH,
1721            trace_id: Default::default(),
1722            span_id: None,
1723            unit: None,
1724            attributes: Map::new(),
1725        }]);
1726
1727        assert_eq!(
1728            collect_losses(&envelope),
1729            vec![
1730                (Category::Error, 1),
1731                (Category::Transaction, 1),
1732                (Category::Span, 3),
1733                (Category::LogItem, 1),
1734                (Category::LogByte, 9),
1735                (Category::TraceMetric, 1),
1736                (Category::TraceMetricByte, 24),
1737            ]
1738        );
1739    }
1740}