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 based on a predicate,
522    /// and returns a new Envelope containing only the filtered items.
523    ///
524    /// Retains the [`EnvelopeItem`]s for which the predicate returns `true`.
525    /// Additionally, [`EnvelopeItem::Attachment`]s are only kept if the Envelope
526    /// contains an [`EnvelopeItem::Event`] or [`EnvelopeItem::Transaction`].
527    ///
528    /// [`None`] is returned if no items remain in the Envelope after filtering.
529    pub fn filter<F>(self, mut filter: F) -> Option<Self>
530    where
531        F: EnvelopeFilter,
532    {
533        let Items::EnvelopeItems(items) = self.items else {
534            return if filter.filter(&EnvelopeItem::Raw) {
535                Some(self)
536            } else {
537                filter.on_filtered(EnvelopeItem::Raw);
538                None
539            };
540        };
541
542        let mut filtered = Envelope::new();
543        for item in items {
544            if filter.filter(&item) {
545                filtered.add_item(item);
546            } else {
547                filter.on_filtered(item);
548            }
549        }
550
551        // filter again, removing attachments which do not make any sense without
552        // an event/transaction
553        if filtered.uuid().is_none() {
554            if let Items::EnvelopeItems(ref mut items) = filtered.items {
555                let old_items = mem::take(items);
556                *items = old_items
557                    .into_iter()
558                    .filter_map(|item| match item {
559                        EnvelopeItem::Attachment(..) => {
560                            filter.on_filtered(item);
561                            None
562                        }
563                        _ => Some(item),
564                    })
565                    .collect();
566            }
567        }
568
569        if filtered.items.is_empty() {
570            None
571        } else {
572            Some(filtered)
573        }
574    }
575
576    /// Serialize the Envelope into the given [`Write`].
577    ///
578    /// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
579    pub fn to_writer<W>(&self, mut writer: W) -> std::io::Result<()>
580    where
581        W: Write,
582    {
583        let items = match &self.items {
584            Items::Raw(bytes) => return writer.write_all(bytes).map(|_| ()),
585            Items::EnvelopeItems(items) => items,
586        };
587
588        // write the headers:
589        serde_json::to_writer(&mut writer, &self.headers)?;
590        writeln!(writer)?;
591
592        let mut item_buf = Vec::new();
593        // write each item:
594        for item in items {
595            // we write them to a temporary buffer first, since we need their length
596            match item {
597                EnvelopeItem::Event(event) => serde_json::to_writer(&mut item_buf, event)?,
598                EnvelopeItem::SessionUpdate(session) => {
599                    serde_json::to_writer(&mut item_buf, session)?
600                }
601                EnvelopeItem::SessionAggregates(aggregates) => {
602                    serde_json::to_writer(&mut item_buf, aggregates)?
603                }
604                EnvelopeItem::Transaction(transaction) => {
605                    serde_json::to_writer(&mut item_buf, transaction)?
606                }
607                EnvelopeItem::Attachment(attachment) => {
608                    attachment.to_writer(&mut writer)?;
609                    writeln!(writer)?;
610                    continue;
611                }
612                EnvelopeItem::MonitorCheckIn(check_in) => {
613                    serde_json::to_writer(&mut item_buf, check_in)?
614                }
615                EnvelopeItem::ClientReport(client_report) => {
616                    serde_json::to_writer(&mut item_buf, client_report)?
617                }
618                EnvelopeItem::ItemContainer(container) => match container {
619                    ItemContainer::Logs(logs) => {
620                        let wrapper = ItemsSerdeWrapper { items: logs.into() };
621                        serde_json::to_writer(&mut item_buf, &wrapper)?
622                    }
623                    ItemContainer::Metrics(metrics) => {
624                        let wrapper = ItemsSerdeWrapper {
625                            items: metrics.into(),
626                        };
627                        serde_json::to_writer(&mut item_buf, &wrapper)?
628                    }
629                },
630                EnvelopeItem::Raw => {
631                    continue;
632                }
633            }
634            let item_type = item
635                .item_type()
636                .expect("raw envelope items are skipped during serialization");
637            let item_type = serde_json::to_string(&item_type)?;
638
639            if let EnvelopeItem::ItemContainer(container) = item {
640                writeln!(
641                    writer,
642                    r#"{{"type":{},"item_count":{},"content_type":"{}"}}"#,
643                    item_type,
644                    container.len(),
645                    container.content_type()
646                )?;
647            } else {
648                writeln!(
649                    writer,
650                    r#"{{"type":{},"length":{}}}"#,
651                    item_type,
652                    item_buf.len()
653                )?;
654            }
655            writer.write_all(&item_buf)?;
656            writeln!(writer)?;
657            item_buf.clear();
658        }
659
660        Ok(())
661    }
662
663    /// Creates a new Envelope from slice.
664    pub fn from_slice(slice: &[u8]) -> Result<Envelope, EnvelopeError> {
665        let (headers, offset) = Self::parse_headers(slice)?;
666        let items = Self::parse_items(slice, offset)?;
667
668        let mut envelope = Envelope {
669            headers,
670            ..Default::default()
671        };
672
673        for item in items {
674            envelope.add_item(item);
675        }
676
677        Ok(envelope)
678    }
679
680    /// Creates a new raw Envelope from the given buffer.
681    pub fn from_bytes_raw(bytes: Vec<u8>) -> Result<Self, EnvelopeError> {
682        Ok(Self {
683            items: Items::Raw(bytes),
684            ..Default::default()
685        })
686    }
687
688    /// Creates a new Envelope from path.
689    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Envelope, EnvelopeError> {
690        let bytes = std::fs::read(path).map_err(|_| EnvelopeError::UnexpectedEof)?;
691        Envelope::from_slice(&bytes)
692    }
693
694    /// Creates a new Envelope from path without attempting to parse anything.
695    ///
696    /// The resulting Envelope will have no `event_id` and the file contents will
697    /// be contained verbatim in the `items` field.
698    pub fn from_path_raw<P: AsRef<Path>>(path: P) -> Result<Self, EnvelopeError> {
699        let bytes = std::fs::read(path).map_err(|_| EnvelopeError::UnexpectedEof)?;
700        Self::from_bytes_raw(bytes)
701    }
702
703    fn parse_headers(slice: &[u8]) -> Result<(EnvelopeHeaders, usize), EnvelopeError> {
704        let first_line = slice
705            .split(|b| *b == b'\n')
706            .next()
707            .ok_or(EnvelopeError::MissingHeader)?;
708
709        let headers: EnvelopeHeaders =
710            serde_json::from_slice(first_line).map_err(EnvelopeError::InvalidHeader)?;
711
712        let offset = first_line.len();
713        Self::require_termination(slice, offset)?;
714
715        // Valid slices cannot be larger than `isize::MAX` bytes:
716        // https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety
717        let byte_after_header = offset
718            .checked_add(1)
719            .expect("offset is at most isize::MAX; adding 1 cannot overflow usize");
720
721        Ok((headers, byte_after_header))
722    }
723
724    fn parse_items(slice: &[u8], mut offset: usize) -> Result<Vec<EnvelopeItem>, EnvelopeError> {
725        let mut items = Vec::new();
726
727        while offset < slice.len() {
728            let bytes = slice
729                .get(offset..)
730                .ok_or(EnvelopeError::MissingItemHeader)?;
731            let (item, item_offset) = Self::parse_item(bytes)?;
732            // `bytes` is `slice[offset..]`, so `bytes.len() == slice.len() - offset`.
733            // Since `item_offset <= bytes.len() + 1`, `offset + item_offset <= slice.len() + 1`.
734            // Valid slices cannot exceed `isize::MAX` bytes, so `slice.len() + 1` cannot overflow `usize`.
735            offset = offset
736                .checked_add(item_offset)
737                .expect("offset + item_offset is at most slice.len() + 1");
738            items.push(item);
739        }
740
741        Ok(items)
742    }
743
744    /// Parses one envelope item from the beginning of `slice`.
745    ///
746    /// Returns the parsed item and the offset at which parsing should continue.
747    ///
748    /// The offset is relative to `slice`. It points just after the payload terminator if one is
749    /// present, or one byte past the end of `slice` if the payload ends at the end of `slice`.
750    /// The offset therefore satisfies `1 <= offset <= slice.len() + 1`.
751    fn parse_item(slice: &[u8]) -> Result<(EnvelopeItem, usize), EnvelopeError> {
752        let mut stream = serde_json::Deserializer::from_slice(slice).into_iter();
753
754        let header: EnvelopeItemHeader = match stream.next() {
755            None => return Err(EnvelopeError::UnexpectedEof),
756            Some(Err(error)) => return Err(EnvelopeError::InvalidItemHeader(error)),
757            Some(Ok(header)) => header,
758        };
759
760        // Each header is terminated by a UNIX newline.
761        let header_end = stream.byte_offset();
762        Self::require_termination(slice, header_end)?;
763
764        // The last header does not require a trailing newline, so `payload_start` may point
765        // past the end of the buffer.
766        // The checked_add never overflows since slices cannot exceed isize::MAX bytes:
767        // https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety
768        let payload_start = std::cmp::min(header_end, slice.len().saturating_sub(1))
769            .checked_add(1)
770            .expect("&[u8] length is at most isize::MAX; adding one cannot overflow usize");
771        let payload_end = match header.length {
772            Some(len) => {
773                let payload_end = payload_start.saturating_add(len);
774                if slice.len() < payload_end {
775                    return Err(EnvelopeError::UnexpectedEof);
776                }
777
778                // Each payload is terminated by a UNIX newline.
779                Self::require_termination(slice, payload_end)?;
780                payload_end
781            }
782            None => match slice.get(payload_start..) {
783                Some(range) => match range.iter().position(|&b| b == b'\n') {
784                    Some(relative_end) => payload_start.checked_add(relative_end).expect(
785                        "relative_end is an index into slice[payload_start..]; \
786                        so the sum is within slice and cannot overflow usize",
787                    ),
788                    None => slice.len(),
789                },
790                None => slice.len(),
791            },
792        };
793
794        let payload = slice.get(payload_start..payload_end).unwrap();
795
796        let item = match header.r#type {
797            EnvelopeItemType::Event => serde_json::from_slice(payload).map(EnvelopeItem::Event),
798            EnvelopeItemType::Transaction => {
799                serde_json::from_slice(payload).map(EnvelopeItem::Transaction)
800            }
801            EnvelopeItemType::SessionUpdate => {
802                serde_json::from_slice(payload).map(EnvelopeItem::SessionUpdate)
803            }
804            EnvelopeItemType::SessionAggregates => {
805                serde_json::from_slice(payload).map(EnvelopeItem::SessionAggregates)
806            }
807            EnvelopeItemType::Attachment => Ok(EnvelopeItem::Attachment(Attachment {
808                buffer: payload.to_owned(),
809                filename: header.filename.unwrap_or_default(),
810                content_type: header.content_type,
811                ty: header.attachment_type,
812            })),
813            EnvelopeItemType::MonitorCheckIn => {
814                serde_json::from_slice(payload).map(EnvelopeItem::MonitorCheckIn)
815            }
816            EnvelopeItemType::ClientReport => {
817                serde_json::from_slice(payload).map(EnvelopeItem::ClientReport)
818            }
819            EnvelopeItemType::LogsContainer => {
820                serde_json::from_slice::<ItemsSerdeWrapper<_>>(payload)
821                    .map(|x| EnvelopeItem::ItemContainer(ItemContainer::Logs(x.items.into())))
822            }
823            EnvelopeItemType::MetricsContainer => {
824                serde_json::from_slice::<ItemsSerdeWrapper<_>>(payload)
825                    .map(|x| EnvelopeItem::ItemContainer(ItemContainer::Metrics(x.items.into())))
826            }
827        }
828        .map_err(EnvelopeError::InvalidItemPayload)?;
829
830        // Valid slices cannot be larger than `isize::MAX` bytes:
831        // https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety
832        let byte_after_payload = payload_end
833            .checked_add(1)
834            .expect("payload_end <= slice.len() <= isize::MAX, so adding 1 cannot overflow usize");
835
836        Ok((item, byte_after_payload))
837    }
838
839    fn require_termination(slice: &[u8], offset: usize) -> Result<(), EnvelopeError> {
840        match slice.get(offset) {
841            Some(&b'\n') | None => Ok(()),
842            Some(_) => Err(EnvelopeError::MissingNewline),
843        }
844    }
845}
846
847impl<T> From<T> for Envelope
848where
849    T: Into<EnvelopeItem>,
850{
851    fn from(item: T) -> Self {
852        let mut envelope = Self::default();
853        envelope.add_item(item.into());
854        envelope
855    }
856}
857
858impl<F, C> EnvelopeFilter for EnvelopeFilterCallbacks<F, C>
859where
860    F: FnMut(&EnvelopeItem) -> bool,
861    C: FnMut(EnvelopeItem),
862{
863    fn filter(&mut self, item: &EnvelopeItem) -> bool {
864        (self.filter)(item)
865    }
866
867    fn on_filtered(&mut self, item: EnvelopeItem) {
868        (self.on_filtered)(item);
869    }
870}
871
872impl<F> EnvelopeFilter for F
873where
874    F: FnMut(&EnvelopeItem) -> bool,
875{
876    fn filter(&mut self, item: &EnvelopeItem) -> bool {
877        self(item)
878    }
879}
880
881mod private {
882    use super::*;
883
884    pub trait Sealed {}
885
886    impl<F, C> Sealed for EnvelopeFilterCallbacks<F, C>
887    where
888        F: FnMut(&EnvelopeItem) -> bool,
889        C: FnMut(EnvelopeItem),
890    {
891    }
892
893    impl<F> Sealed for F where F: FnMut(&EnvelopeItem) -> bool {}
894}
895
896#[cfg(test)]
897mod test {
898    use std::borrow::Cow;
899    use std::str::FromStr;
900    use std::time::{Duration, SystemTime};
901
902    use protocol::Map;
903    use serde_json::Value;
904    use time::format_description::well_known::Rfc3339;
905    use time::OffsetDateTime;
906    use uuid::Uuid;
907
908    use super::*;
909    use crate::protocol::client_report::{Item, LossSource};
910    use crate::protocol::v7::client_report::Category;
911    use crate::protocol::v7::{
912        ClientReport, Level, LogLevel, MetricType, MonitorCheckInStatus, MonitorConfig,
913        MonitorSchedule, SampleRand, SessionAggregateItem, SessionAttributes, SessionStatus, Span,
914    };
915
916    fn to_str(envelope: Envelope) -> String {
917        let mut vec = Vec::new();
918        envelope.to_writer(&mut vec).unwrap();
919        String::from_utf8_lossy(&vec).to_string()
920    }
921
922    fn timestamp(s: &str) -> SystemTime {
923        let dt = OffsetDateTime::parse(s, &Rfc3339).unwrap();
924        let secs = dt.unix_timestamp() as u64;
925        let nanos = dt.nanosecond();
926        let duration = Duration::new(secs, nanos);
927        SystemTime::UNIX_EPOCH.checked_add(duration).unwrap()
928    }
929
930    fn session_attributes() -> SessionAttributes<'static> {
931        SessionAttributes {
932            release: Cow::Borrowed("release@1.0.0"),
933            environment: Some(Cow::Borrowed("production")),
934            ip_address: None,
935            user_agent: None,
936        }
937    }
938
939    fn collect_losses(envelope: &Envelope) -> Vec<(Category, u64)> {
940        envelope
941            .losses()
942            .map(|loss| (loss.category, loss.quantity))
943            .collect()
944    }
945
946    #[test]
947    fn test_empty() {
948        assert_eq!(to_str(Envelope::new()), "{}\n");
949    }
950
951    #[test]
952    fn raw_roundtrip() {
953        let buf = r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
954{"type":"event","length":74}
955{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
956"#;
957        let envelope = Envelope::from_bytes_raw(buf.to_string().into_bytes()).unwrap();
958        let serialized = to_str(envelope);
959        assert_eq!(&serialized, buf);
960
961        let random_invalid_bytes = b"oh stahp!\0\x01\x02";
962        let envelope = Envelope::from_bytes_raw(random_invalid_bytes.to_vec()).unwrap();
963        let mut serialized = Vec::new();
964        envelope.to_writer(&mut serialized).unwrap();
965        assert_eq!(&serialized, random_invalid_bytes);
966    }
967
968    #[test]
969    fn test_event() {
970        let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
971        let timestamp = timestamp("2020-07-20T14:51:14.296Z");
972        let event = Event {
973            event_id,
974            timestamp,
975            ..Default::default()
976        };
977        let envelope: Envelope = event.into();
978        assert_eq!(
979            to_str(envelope),
980            r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
981{"type":"event","length":74}
982{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
983"#
984        )
985    }
986
987    #[test]
988    fn test_session() {
989        let session_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
990        let started = timestamp("2020-07-20T14:51:14.296Z");
991        let session = SessionUpdate {
992            session_id,
993            distinct_id: Some("foo@bar.baz".to_owned()),
994            sequence: None,
995            timestamp: None,
996            started,
997            init: true,
998            duration: Some(1.234),
999            status: SessionStatus::Ok,
1000            errors: 123,
1001            attributes: SessionAttributes {
1002                release: "foo-bar@1.2.3".into(),
1003                environment: Some("production".into()),
1004                ip_address: None,
1005                user_agent: None,
1006            },
1007        };
1008        let mut envelope = Envelope::new();
1009        envelope.add_item(session);
1010        assert_eq!(
1011            to_str(envelope),
1012            r#"{}
1013{"type":"session","length":222}
1014{"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"}}
1015"#
1016        )
1017    }
1018
1019    #[test]
1020    fn test_transaction() {
1021        let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
1022        let span_id = "d42cee9fc3e74f5c".parse().unwrap();
1023        let trace_id = "335e53d614474acc9f89e632b776cc28".parse().unwrap();
1024        let start_timestamp = timestamp("2020-07-20T14:51:14.296Z");
1025        let spans = vec![Span {
1026            span_id,
1027            trace_id,
1028            start_timestamp,
1029            ..Default::default()
1030        }];
1031        let transaction = Transaction {
1032            event_id,
1033            start_timestamp,
1034            spans,
1035            ..Default::default()
1036        };
1037        let envelope: Envelope = transaction.into();
1038        assert_eq!(
1039            to_str(envelope),
1040            r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
1041{"type":"transaction","length":200}
1042{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","start_timestamp":1595256674.296,"spans":[{"span_id":"d42cee9fc3e74f5c","trace_id":"335e53d614474acc9f89e632b776cc28","start_timestamp":1595256674.296}]}
1043"#
1044        )
1045    }
1046
1047    #[test]
1048    fn test_monitor_checkin() {
1049        let check_in_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
1050
1051        let check_in = MonitorCheckIn {
1052            check_in_id,
1053            monitor_slug: "my-monitor".into(),
1054            status: MonitorCheckInStatus::Ok,
1055            duration: Some(123.4),
1056            environment: Some("production".into()),
1057            monitor_config: Some(MonitorConfig {
1058                schedule: MonitorSchedule::Crontab {
1059                    value: "12 0 * * *".into(),
1060                },
1061                checkin_margin: Some(5),
1062                max_runtime: Some(30),
1063                timezone: Some("UTC".into()),
1064                failure_issue_threshold: None,
1065                recovery_threshold: None,
1066            }),
1067        };
1068        let envelope: Envelope = check_in.into();
1069        assert_eq!(
1070            to_str(envelope),
1071            r#"{}
1072{"type":"check_in","length":259}
1073{"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"}}
1074"#
1075        )
1076    }
1077
1078    #[test]
1079    fn test_monitor_checkin_with_thresholds() {
1080        let check_in_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
1081
1082        let check_in = MonitorCheckIn {
1083            check_in_id,
1084            monitor_slug: "my-monitor".into(),
1085            status: MonitorCheckInStatus::Ok,
1086            duration: Some(123.4),
1087            environment: Some("production".into()),
1088            monitor_config: Some(MonitorConfig {
1089                schedule: MonitorSchedule::Crontab {
1090                    value: "12 0 * * *".into(),
1091                },
1092                checkin_margin: Some(5),
1093                max_runtime: Some(30),
1094                timezone: Some("UTC".into()),
1095                failure_issue_threshold: Some(4),
1096                recovery_threshold: Some(7),
1097            }),
1098        };
1099        let envelope: Envelope = check_in.into();
1100        assert_eq!(
1101            to_str(envelope),
1102            r#"{}
1103{"type":"check_in","length":310}
1104{"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}}
1105"#
1106        )
1107    }
1108
1109    #[test]
1110    fn test_event_with_attachment() {
1111        let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
1112        let timestamp = timestamp("2020-07-20T14:51:14.296Z");
1113        let event = Event {
1114            event_id,
1115            timestamp,
1116            ..Default::default()
1117        };
1118        let mut envelope: Envelope = event.into();
1119
1120        envelope.add_item(Attachment {
1121            buffer: "some content".as_bytes().to_vec(),
1122            filename: "file.txt".to_string(),
1123            ..Default::default()
1124        });
1125
1126        assert_eq!(
1127            to_str(envelope),
1128            r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
1129{"type":"event","length":74}
1130{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
1131{"type":"attachment","length":12,"filename":"file.txt","attachment_type":"event.attachment","content_type":"application/octet-stream"}
1132some content
1133"#
1134        )
1135    }
1136
1137    #[test]
1138    fn test_deserialize_envelope_empty() {
1139        // Without terminating newline after header
1140        let bytes = b"{\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}";
1141        let envelope = Envelope::from_slice(bytes).unwrap();
1142
1143        let event_id = Uuid::from_str("9ec79c33ec9942ab8353589fcb2e04dc").unwrap();
1144        assert_eq!(envelope.headers.event_id, Some(event_id));
1145        assert_eq!(envelope.items().count(), 0);
1146    }
1147
1148    #[test]
1149    fn test_deserialize_envelope_empty_newline() {
1150        // With terminating newline after header
1151        let bytes = b"{\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n";
1152        let envelope = Envelope::from_slice(bytes).unwrap();
1153        assert_eq!(envelope.items().count(), 0);
1154    }
1155
1156    #[test]
1157    fn test_deserialize_envelope_empty_item_newline() {
1158        // With terminating newline after item payload
1159        let bytes = b"\
1160             {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1161             {\"type\":\"attachment\",\"length\":0}\n\
1162             \n\
1163             {\"type\":\"attachment\",\"length\":0}\n\
1164             ";
1165
1166        let envelope = Envelope::from_slice(bytes).unwrap();
1167        assert_eq!(envelope.items().count(), 2);
1168
1169        let mut items = envelope.items();
1170
1171        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1172            assert_eq!(attachment.buffer.len(), 0);
1173        } else {
1174            panic!("invalid item type");
1175        }
1176
1177        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1178            assert_eq!(attachment.buffer.len(), 0);
1179        } else {
1180            panic!("invalid item type");
1181        }
1182    }
1183
1184    #[test]
1185    fn test_deserialize_envelope_empty_item_eof() {
1186        // With terminating newline after item payload
1187        let bytes = b"\
1188             {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1189             {\"type\":\"attachment\",\"length\":0}\n\
1190             \n\
1191             {\"type\":\"attachment\",\"length\":0}\
1192             ";
1193
1194        let envelope = Envelope::from_slice(bytes).unwrap();
1195        assert_eq!(envelope.items().count(), 2);
1196
1197        let mut items = envelope.items();
1198
1199        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1200            assert_eq!(attachment.buffer.len(), 0);
1201        } else {
1202            panic!("invalid item type");
1203        }
1204
1205        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1206            assert_eq!(attachment.buffer.len(), 0);
1207        } else {
1208            panic!("invalid item type");
1209        }
1210    }
1211
1212    #[test]
1213    fn test_deserialize_envelope_implicit_length() {
1214        // With terminating newline after item payload
1215        let bytes = b"\
1216             {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1217             {\"type\":\"attachment\"}\n\
1218             helloworld\n\
1219             ";
1220
1221        let envelope = Envelope::from_slice(bytes).unwrap();
1222        assert_eq!(envelope.items().count(), 1);
1223
1224        let mut items = envelope.items();
1225
1226        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1227            assert_eq!(attachment.buffer.len(), 10);
1228        } else {
1229            panic!("invalid item type");
1230        }
1231    }
1232
1233    #[test]
1234    fn test_deserialize_envelope_implicit_length_eof() {
1235        // With item ending the envelope
1236        let bytes = b"\
1237             {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1238             {\"type\":\"attachment\"}\n\
1239             helloworld\
1240             ";
1241
1242        let envelope = Envelope::from_slice(bytes).unwrap();
1243        assert_eq!(envelope.items().count(), 1);
1244
1245        let mut items = envelope.items();
1246
1247        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1248            assert_eq!(attachment.buffer.len(), 10);
1249        } else {
1250            panic!("invalid item type");
1251        }
1252    }
1253
1254    #[test]
1255    fn test_deserialize_envelope_implicit_length_empty_eof() {
1256        // Empty item with implicit length ending the envelope
1257        let bytes = b"\
1258             {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1259             {\"type\":\"attachment\"}\
1260             ";
1261
1262        let envelope = Envelope::from_slice(bytes).unwrap();
1263        assert_eq!(envelope.items().count(), 1);
1264
1265        let mut items = envelope.items();
1266
1267        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1268            assert_eq!(attachment.buffer.len(), 0);
1269        } else {
1270            panic!("invalid item type");
1271        }
1272    }
1273
1274    #[test]
1275    fn test_deserialize_envelope_multiple_items() {
1276        // With terminating newline
1277        let bytes = b"\
1278            {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1279            {\"type\":\"attachment\",\"length\":10,\"content_type\":\"text/plain\",\"filename\":\"hello.txt\"}\n\
1280            \xef\xbb\xbfHello\r\n\n\
1281            {\"type\":\"event\",\"length\":41,\"content_type\":\"application/json\",\"filename\":\"application.log\"}\n\
1282            {\"message\":\"hello world\",\"level\":\"error\"}\n\
1283            ";
1284
1285        let envelope = Envelope::from_slice(bytes).unwrap();
1286        assert_eq!(envelope.items().count(), 2);
1287
1288        let mut items = envelope.items();
1289
1290        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1291            assert_eq!(attachment.buffer.len(), 10);
1292            assert_eq!(attachment.buffer, b"\xef\xbb\xbfHello\r\n");
1293            assert_eq!(attachment.filename, "hello.txt");
1294            assert_eq!(attachment.content_type, Some("text/plain".to_string()));
1295        } else {
1296            panic!("invalid item type");
1297        }
1298
1299        if let EnvelopeItem::Event(event) = items.next().unwrap() {
1300            assert_eq!(event.message, Some("hello world".to_string()));
1301            assert_eq!(event.level, Level::Error);
1302        } else {
1303            panic!("invalid item type");
1304        }
1305    }
1306
1307    #[test]
1308    fn test_all_envelope_headers_roundtrip() {
1309        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"}}
1310{"type":"event","length":74}
1311{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
1312"#;
1313
1314        let envelope = Envelope::from_slice(bytes);
1315        assert!(envelope.is_ok());
1316        let envelope = envelope.unwrap();
1317        let serialized = to_str(envelope);
1318        assert_eq!(bytes, serialized.as_bytes());
1319    }
1320
1321    #[test]
1322    fn test_sample_rand_rounding() {
1323        let envelope = Envelope::new().with_headers(
1324            EnvelopeHeaders::new().with_trace(
1325                DynamicSamplingContext::new()
1326                    .with_sample_rand(SampleRand::try_from(0.999_999_9).unwrap()),
1327            ),
1328        );
1329        let expected = br#"{"trace":{"sample_rand":"0.999999"}}
1330"#;
1331
1332        let serialized = to_str(envelope);
1333        assert_eq!(expected, serialized.as_bytes());
1334    }
1335
1336    #[test]
1337    fn test_metric_container_header() {
1338        let metrics: EnvelopeItem = vec![Metric {
1339            r#type: protocol::MetricType::Counter,
1340            name: "api.requests".into(),
1341            value: 1.0,
1342            timestamp: timestamp("2026-03-02T13:36:02.000Z"),
1343            trace_id: "335e53d614474acc9f89e632b776cc28".parse().unwrap(),
1344            span_id: None,
1345            unit: None,
1346            attributes: Map::new(),
1347        }]
1348        .into();
1349
1350        let mut envelope = Envelope::new();
1351        envelope.add_item(metrics);
1352
1353        let expected = [
1354            serde_json::json!({}),
1355            serde_json::json!({
1356                "type": "trace_metric",
1357                "item_count": 1,
1358                "content_type": "application/vnd.sentry.items.trace-metric+json"
1359            }),
1360            serde_json::json!({
1361                "items": [{
1362                    "type": "counter",
1363                    "name": "api.requests",
1364                    "value": 1.0,
1365                    "timestamp": 1772458562,
1366                    "trace_id": "335e53d614474acc9f89e632b776cc28"
1367                }]
1368            }),
1369        ];
1370
1371        let serialized = to_str(envelope);
1372        let actual = serialized
1373            .lines()
1374            .map(|line| serde_json::from_str::<Value>(line).expect("envelope has invalid JSON"));
1375
1376        assert!(actual.eq(expected.into_iter()));
1377    }
1378
1379    // Test all possible item types in a single envelope
1380    #[test]
1381    fn test_deserialize_serialized() {
1382        // Event
1383        let event = Event {
1384            event_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap(),
1385            timestamp: timestamp("2020-07-20T14:51:14.296Z"),
1386            ..Default::default()
1387        };
1388
1389        // Transaction
1390        let transaction = Transaction {
1391            event_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9d").unwrap(),
1392            start_timestamp: timestamp("2020-07-20T14:51:14.296Z"),
1393            spans: vec![Span {
1394                span_id: "d42cee9fc3e74f5c".parse().unwrap(),
1395                trace_id: "335e53d614474acc9f89e632b776cc28".parse().unwrap(),
1396                start_timestamp: timestamp("2020-07-20T14:51:14.296Z"),
1397                ..Default::default()
1398            }],
1399            ..Default::default()
1400        };
1401
1402        // Session
1403        let session = SessionUpdate {
1404            session_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap(),
1405            distinct_id: Some("foo@bar.baz".to_owned()),
1406            sequence: None,
1407            timestamp: None,
1408            started: timestamp("2020-07-20T14:51:14.296Z"),
1409            init: true,
1410            duration: Some(1.234),
1411            status: SessionStatus::Ok,
1412            errors: 123,
1413            attributes: SessionAttributes {
1414                release: "foo-bar@1.2.3".into(),
1415                environment: Some("production".into()),
1416                ip_address: None,
1417                user_agent: None,
1418            },
1419        };
1420
1421        // Attachment
1422        let attachment = Attachment {
1423            buffer: "some content".as_bytes().to_vec(),
1424            filename: "file.txt".to_string(),
1425            ..Default::default()
1426        };
1427
1428        let mut attributes = Map::new();
1429        attributes.insert("key".into(), "value".into());
1430        attributes.insert("num".into(), 10.into());
1431        attributes.insert("val".into(), 10.2.into());
1432        attributes.insert("bool".into(), false.into());
1433        let mut attributes_2 = attributes.clone();
1434        attributes_2.insert("more".into(), true.into());
1435        let logs: EnvelopeItem = vec![
1436            Log {
1437                level: protocol::LogLevel::Warn,
1438                body: "test".to_owned(),
1439                trace_id: Some("335e53d614474acc9f89e632b776cc28".parse().unwrap()),
1440                timestamp: timestamp("2022-07-25T14:51:14.296Z"),
1441                severity_number: Some(1.try_into().unwrap()),
1442                attributes,
1443            },
1444            Log {
1445                level: protocol::LogLevel::Error,
1446                body: "a body".to_owned(),
1447                trace_id: Some("332253d614472a2c9f89e232b7762c28".parse().unwrap()),
1448                timestamp: timestamp("2021-07-21T14:51:14.296Z"),
1449                severity_number: Some(1.try_into().unwrap()),
1450                attributes: attributes_2,
1451            },
1452        ]
1453        .into();
1454
1455        let mut metric_attributes = Map::new();
1456        metric_attributes.insert("route".into(), "/users".into());
1457        let metrics: EnvelopeItem = vec![Metric {
1458            r#type: protocol::MetricType::Distribution,
1459            name: "response.time".into(),
1460            value: 123.4,
1461            timestamp: timestamp("2022-07-26T14:51:14.296Z"),
1462            trace_id: "335e53d614474acc9f89e632b776cc28".parse().unwrap(),
1463            span_id: Some("d42cee9fc3e74f5c".parse().unwrap()),
1464            unit: Some("millisecond".into()),
1465            attributes: metric_attributes,
1466        }]
1467        .into();
1468
1469        let mut envelope: Envelope = Envelope::new();
1470        envelope.add_item(event);
1471        envelope.add_item(transaction);
1472        envelope.add_item(session);
1473        envelope.add_item(attachment);
1474        envelope.add_item(logs);
1475        envelope.add_item(metrics);
1476
1477        let serialized = to_str(envelope);
1478        let deserialized = Envelope::from_slice(serialized.as_bytes()).unwrap();
1479        assert_eq!(serialized, to_str(deserialized))
1480    }
1481
1482    #[test]
1483    fn losses_on_drop_maps_event_to_error() {
1484        let envelope: Envelope = Event::default().into();
1485
1486        assert_eq!(collect_losses(&envelope), vec![(Category::Error, 1)]);
1487    }
1488
1489    #[test]
1490    fn losses_on_drop_maps_session_update_to_session() {
1491        let envelope: Envelope = SessionUpdate {
1492            session_id: Uuid::nil(),
1493            distinct_id: None,
1494            sequence: None,
1495            timestamp: None,
1496            started: SystemTime::UNIX_EPOCH,
1497            init: false,
1498            duration: None,
1499            status: SessionStatus::Ok,
1500            errors: 0,
1501            attributes: session_attributes(),
1502        }
1503        .into();
1504
1505        assert_eq!(collect_losses(&envelope), vec![(Category::Session, 1)]);
1506    }
1507
1508    #[test]
1509    fn losses_on_drop_maps_attachment_to_buffer_bytes() {
1510        let envelope: Envelope = Attachment {
1511            buffer: b"attachment".to_vec(),
1512            filename: "attachment.bin".to_owned(),
1513            ..Default::default()
1514        }
1515        .into();
1516
1517        assert_eq!(collect_losses(&envelope), vec![(Category::Attachment, 10)]);
1518    }
1519
1520    #[test]
1521    fn losses_on_drop_maps_monitor_check_in_to_monitor() {
1522        let envelope: Envelope = MonitorCheckIn {
1523            check_in_id: Uuid::nil(),
1524            monitor_slug: "monitor".to_owned(),
1525            status: MonitorCheckInStatus::Ok,
1526            environment: None,
1527            duration: None,
1528            monitor_config: None,
1529        }
1530        .into();
1531
1532        assert_eq!(collect_losses(&envelope), vec![(Category::Monitor, 1)]);
1533    }
1534
1535    #[test]
1536    fn losses_on_drop_sums_session_aggregate_status_counts() {
1537        let envelope: Envelope = SessionAggregates {
1538            aggregates: vec![
1539                SessionAggregateItem {
1540                    started: SystemTime::UNIX_EPOCH,
1541                    distinct_id: None,
1542                    exited: 2,
1543                    errored: 3,
1544                    abnormal: 5,
1545                    crashed: 7,
1546                },
1547                SessionAggregateItem {
1548                    started: SystemTime::UNIX_EPOCH,
1549                    distinct_id: None,
1550                    exited: 11,
1551                    errored: 13,
1552                    abnormal: 17,
1553                    crashed: 19,
1554                },
1555            ],
1556            attributes: session_attributes(),
1557        }
1558        .into();
1559
1560        assert_eq!(collect_losses(&envelope), vec![(Category::Session, 77)]);
1561    }
1562
1563    #[test]
1564    fn losses_on_drop_counts_transaction_root_span() {
1565        let envelope: Envelope = Transaction {
1566            spans: vec![],
1567            ..Default::default()
1568        }
1569        .into();
1570
1571        assert_eq!(
1572            collect_losses(&envelope),
1573            vec![(Category::Transaction, 1), (Category::Span, 1)]
1574        );
1575    }
1576
1577    #[test]
1578    fn losses_on_drop_counts_transaction_child_spans() {
1579        let envelope: Envelope = Transaction {
1580            spans: vec![Span::default(), Span::default(), Span::default()],
1581            ..Default::default()
1582        }
1583        .into();
1584
1585        assert_eq!(
1586            collect_losses(&envelope),
1587            vec![(Category::Transaction, 1), (Category::Span, 4)]
1588        );
1589    }
1590
1591    #[test]
1592    fn losses_on_drop_counts_minimal_log_bytes() {
1593        let envelope: Envelope = vec![Log {
1594            level: LogLevel::Info,
1595            body: String::new(),
1596            trace_id: None,
1597            timestamp: SystemTime::UNIX_EPOCH,
1598            severity_number: None,
1599            attributes: Map::new(),
1600        }]
1601        .into();
1602
1603        assert_eq!(
1604            collect_losses(&envelope),
1605            vec![(Category::LogItem, 1), (Category::LogByte, 1)]
1606        );
1607    }
1608
1609    #[test]
1610    fn losses_on_drop_counts_complex_log_bytes() {
1611        let mut attributes = Map::new();
1612        attributes.insert("k1".to_owned(), "string value".into());
1613        attributes.insert("k2".to_owned(), u64::MAX.into());
1614        attributes.insert("k3".to_owned(), 42.0.into());
1615        attributes.insert("k4".to_owned(), false.into());
1616        attributes.insert(
1617            "k5".to_owned(),
1618            serde_json::json!({
1619                "nested": {
1620                    "array": [1.0, 2, -12, "7 bytes", false]
1621                }
1622            })
1623            .into(),
1624        );
1625        let envelope: Envelope = vec![Log {
1626            level: LogLevel::Info,
1627            body: "7 bytes".to_owned(),
1628            trace_id: None,
1629            timestamp: SystemTime::UNIX_EPOCH,
1630            severity_number: None,
1631            attributes,
1632        }]
1633        .into();
1634
1635        assert_eq!(
1636            collect_losses(&envelope),
1637            vec![(Category::LogItem, 1), (Category::LogByte, 89)]
1638        );
1639    }
1640
1641    #[test]
1642    fn losses_on_drop_counts_minimal_metric_bytes() {
1643        let envelope: Envelope = vec![Metric {
1644            r#type: MetricType::Counter,
1645            name: Cow::Borrowed(""),
1646            value: 1.0,
1647            timestamp: SystemTime::UNIX_EPOCH,
1648            trace_id: Default::default(),
1649            span_id: None,
1650            unit: None,
1651            attributes: Map::new(),
1652        }]
1653        .into();
1654
1655        assert_eq!(
1656            collect_losses(&envelope),
1657            vec![(Category::TraceMetric, 1), (Category::TraceMetricByte, 8)]
1658        );
1659    }
1660
1661    #[test]
1662    fn losses_on_drop_counts_complex_metric_bytes() {
1663        let mut attributes = Map::new();
1664        attributes.insert(
1665            Cow::Borrowed("foo"),
1666            "ඞ and some more equals 33 bytes".into(),
1667        );
1668        let envelope: Envelope = vec![Metric {
1669            r#type: MetricType::Counter,
1670            name: Cow::Borrowed("counter"),
1671            value: 1.0,
1672            timestamp: SystemTime::UNIX_EPOCH,
1673            trace_id: Default::default(),
1674            span_id: None,
1675            unit: None,
1676            attributes,
1677        }]
1678        .into();
1679
1680        assert_eq!(
1681            collect_losses(&envelope),
1682            vec![(Category::TraceMetric, 1), (Category::TraceMetricByte, 51)]
1683        );
1684    }
1685
1686    #[test]
1687    fn losses_on_drop_skips_client_reports() {
1688        let envelope: Envelope = ClientReport::new(<[Item; 0]>::default()).into();
1689
1690        assert!(collect_losses(&envelope).is_empty());
1691    }
1692
1693    #[test]
1694    fn losses_on_drop_skips_raw_envelopes() {
1695        let envelope = Envelope::from_bytes_raw(b"not parsed".to_vec()).unwrap();
1696
1697        assert!(collect_losses(&envelope).is_empty());
1698    }
1699
1700    #[test]
1701    fn losses_on_drop_flattens_losses_from_all_envelope_items() {
1702        let log = Log {
1703            level: LogLevel::Warn,
1704            body: "flattened".to_owned(),
1705            trace_id: None,
1706            timestamp: SystemTime::UNIX_EPOCH,
1707            severity_number: None,
1708            attributes: Map::new(),
1709        };
1710        let mut envelope = Envelope::new();
1711        envelope.add_item(Event::default());
1712        envelope.add_item(Transaction {
1713            spans: vec![Span::default(), Span::default()],
1714            ..Default::default()
1715        });
1716        envelope.add_item(vec![log]);
1717        envelope.add_item(vec![Metric {
1718            r#type: MetricType::Counter,
1719            name: Cow::Borrowed("flattened.metric"),
1720            value: 1.0,
1721            timestamp: SystemTime::UNIX_EPOCH,
1722            trace_id: Default::default(),
1723            span_id: None,
1724            unit: None,
1725            attributes: Map::new(),
1726        }]);
1727
1728        assert_eq!(
1729            collect_losses(&envelope),
1730            vec![
1731                (Category::Error, 1),
1732                (Category::Transaction, 1),
1733                (Category::Span, 3),
1734                (Category::LogItem, 1),
1735                (Category::LogByte, 9),
1736                (Category::TraceMetric, 1),
1737                (Category::TraceMetricByte, 24),
1738            ]
1739        );
1740    }
1741}