Skip to main content

sentry_types/protocol/
envelope.rs

1use std::{borrow::Cow, io::Write, path::Path, time::SystemTime};
2
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5use uuid::Uuid;
6
7use crate::utils::ts_rfc3339_opt;
8use crate::Dsn;
9
10use super::v7 as protocol;
11
12use protocol::{
13    Attachment, AttachmentType, ClientSdkInfo, DynamicSamplingContext, Event, Log, Metric,
14    MonitorCheckIn, SessionAggregates, SessionUpdate, Transaction,
15};
16
17/// Raised if a envelope cannot be parsed from a given input.
18#[derive(Debug, Error)]
19pub enum EnvelopeError {
20    /// Unexpected end of file
21    #[error("unexpected end of file")]
22    UnexpectedEof,
23    /// Missing envelope header
24    #[error("missing envelope header")]
25    MissingHeader,
26    /// Missing item header
27    #[error("missing item header")]
28    MissingItemHeader,
29    /// Missing newline after header or payload
30    #[error("missing newline after header or payload")]
31    MissingNewline,
32    /// Invalid envelope header
33    #[error("invalid envelope header")]
34    InvalidHeader(#[source] serde_json::Error),
35    /// Invalid item header
36    #[error("invalid item header")]
37    InvalidItemHeader(#[source] serde_json::Error),
38    /// Invalid item payload
39    #[error("invalid item payload")]
40    InvalidItemPayload(#[source] serde_json::Error),
41}
42
43/// The supported [Sentry Envelope Headers](https://develop.sentry.dev/sdk/data-model/envelopes/#headers).
44#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
45pub struct EnvelopeHeaders {
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    event_id: Option<Uuid>,
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    dsn: Option<Dsn>,
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    sdk: Option<ClientSdkInfo>,
52    #[serde(
53        default,
54        skip_serializing_if = "Option::is_none",
55        with = "ts_rfc3339_opt"
56    )]
57    sent_at: Option<SystemTime>,
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    trace: Option<DynamicSamplingContext>,
60}
61
62impl EnvelopeHeaders {
63    /// Creates empty Envelope headers.
64    pub fn new() -> EnvelopeHeaders {
65        Default::default()
66    }
67
68    /// Sets the Event ID.
69    #[must_use]
70    pub fn with_event_id(mut self, event_id: Uuid) -> Self {
71        self.event_id = Some(event_id);
72        self
73    }
74
75    /// Sets the DSN.
76    #[must_use]
77    pub fn with_dsn(mut self, dsn: Dsn) -> Self {
78        self.dsn = Some(dsn);
79        self
80    }
81
82    /// Sets the SDK information.
83    #[must_use]
84    pub fn with_sdk(mut self, sdk: ClientSdkInfo) -> Self {
85        self.sdk = Some(sdk);
86        self
87    }
88
89    /// Sets the time this envelope was sent at.
90    /// This timestamp should be generated as close as possible to the transmission of the event.
91    #[must_use]
92    pub fn with_sent_at(mut self, sent_at: SystemTime) -> Self {
93        self.sent_at = Some(sent_at);
94        self
95    }
96
97    /// Sets the Dynamic Sampling Context.
98    #[must_use]
99    pub fn with_trace(mut self, trace: DynamicSamplingContext) -> Self {
100        self.trace = Some(trace);
101        self
102    }
103}
104
105/// An Envelope Item Type.
106#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
107#[non_exhaustive]
108enum EnvelopeItemType {
109    /// An Event Item type.
110    #[serde(rename = "event")]
111    Event,
112    /// A Session Item type.
113    #[serde(rename = "session")]
114    SessionUpdate,
115    /// A Session Aggregates Item type.
116    #[serde(rename = "sessions")]
117    SessionAggregates,
118    /// A Transaction Item type.
119    #[serde(rename = "transaction")]
120    Transaction,
121    /// An Attachment Item type.
122    #[serde(rename = "attachment")]
123    Attachment,
124    /// A Monitor Check In Item Type.
125    #[serde(rename = "check_in")]
126    MonitorCheckIn,
127    /// A container of Log items.
128    #[serde(rename = "log")]
129    LogsContainer,
130    /// A container of Metric items.
131    /// Serialized to a `trace_metric` envelope item.
132    #[serde(rename = "trace_metric")]
133    MetricsContainer,
134}
135
136/// An Envelope Item Header.
137#[derive(Clone, Debug, Deserialize)]
138struct EnvelopeItemHeader {
139    r#type: EnvelopeItemType,
140    length: Option<usize>,
141    // Applies both to Attachment and ItemContainer Item type
142    content_type: Option<String>,
143    // Fields below apply only to Attachment Item types
144    filename: Option<String>,
145    attachment_type: Option<AttachmentType>,
146}
147
148/// An Envelope Item.
149///
150/// See the [documentation on Items](https://develop.sentry.dev/sdk/envelopes/#items)
151/// for more details.
152#[derive(Clone, Debug, PartialEq)]
153#[non_exhaustive]
154#[allow(clippy::large_enum_variant)]
155pub enum EnvelopeItem {
156    /// An Event Item.
157    ///
158    /// See the [Event Item documentation](https://develop.sentry.dev/sdk/envelopes/#event)
159    /// for more details.
160    Event(Event<'static>),
161    /// A Session Item.
162    ///
163    /// See the [Session Item documentation](https://develop.sentry.dev/sdk/envelopes/#session)
164    /// for more details.
165    SessionUpdate(SessionUpdate<'static>),
166    /// A Session Aggregates Item.
167    ///
168    /// See the [Session Aggregates Item documentation](https://develop.sentry.dev/sdk/envelopes/#sessions)
169    /// for more details.
170    SessionAggregates(SessionAggregates<'static>),
171    /// A Transaction Item.
172    ///
173    /// See the [Transaction Item documentation](https://develop.sentry.dev/sdk/envelopes/#transaction)
174    /// for more details.
175    Transaction(Transaction<'static>),
176    /// An Attachment Item.
177    ///
178    /// See the [Attachment Item documentation](https://develop.sentry.dev/sdk/envelopes/#attachment)
179    /// for more details.
180    Attachment(Attachment),
181    /// A MonitorCheckIn item.
182    MonitorCheckIn(MonitorCheckIn),
183    /// A container for a list of multiple items.
184    ItemContainer(ItemContainer),
185    /// This is a sentinel item used to `filter` raw envelopes.
186    Raw,
187    // TODO:
188    // etc…
189}
190
191/// A container for a list of multiple items.
192/// It's considered a single envelope item, with its `type` corresponding to the contained items'
193/// `type`.
194#[derive(Clone, Debug, PartialEq)]
195#[non_exhaustive]
196pub enum ItemContainer {
197    /// A list of logs.
198    Logs(Vec<Log>),
199    /// A list of metrics.
200    Metrics(Vec<Metric>),
201}
202
203#[allow(clippy::len_without_is_empty, reason = "is_empty is not needed")]
204impl ItemContainer {
205    /// The number of items in this item container.
206    pub fn len(&self) -> usize {
207        match self {
208            Self::Logs(logs) => logs.len(),
209            Self::Metrics(metrics) => metrics.len(),
210        }
211    }
212
213    /// The `type` of this item container, which corresponds to the `type` of the contained items.
214    pub fn ty(&self) -> &'static str {
215        match self {
216            Self::Logs(_) => "log",
217            Self::Metrics(_) => "trace_metric",
218        }
219    }
220
221    /// The `content-type` expected by Relay for this item container.
222    pub fn content_type(&self) -> &'static str {
223        match self {
224            Self::Logs(_) => "application/vnd.sentry.items.log+json",
225            Self::Metrics(_) => "application/vnd.sentry.items.trace-metric+json",
226        }
227    }
228}
229
230impl From<Vec<Log>> for ItemContainer {
231    fn from(logs: Vec<Log>) -> Self {
232        Self::Logs(logs)
233    }
234}
235
236/// A lightweight wrapper for serializing/deserializing a slice of items,
237/// so that it looks like:
238///
239/// ```json
240/// { items: [...] }
241/// ```
242#[derive(Deserialize, Serialize)]
243struct ItemsSerdeWrapper<'a, T: Clone> {
244    items: Cow<'a, [T]>,
245}
246
247impl From<Vec<Metric>> for ItemContainer {
248    fn from(metrics: Vec<Metric>) -> Self {
249        Self::Metrics(metrics)
250    }
251}
252
253impl From<Event<'static>> for EnvelopeItem {
254    fn from(event: Event<'static>) -> Self {
255        EnvelopeItem::Event(event)
256    }
257}
258
259impl From<SessionUpdate<'static>> for EnvelopeItem {
260    fn from(session: SessionUpdate<'static>) -> Self {
261        EnvelopeItem::SessionUpdate(session)
262    }
263}
264
265impl From<SessionAggregates<'static>> for EnvelopeItem {
266    fn from(aggregates: SessionAggregates<'static>) -> Self {
267        EnvelopeItem::SessionAggregates(aggregates)
268    }
269}
270
271impl From<Transaction<'static>> for EnvelopeItem {
272    fn from(transaction: Transaction<'static>) -> Self {
273        EnvelopeItem::Transaction(transaction)
274    }
275}
276
277impl From<Attachment> for EnvelopeItem {
278    fn from(attachment: Attachment) -> Self {
279        EnvelopeItem::Attachment(attachment)
280    }
281}
282
283impl From<MonitorCheckIn> for EnvelopeItem {
284    fn from(check_in: MonitorCheckIn) -> Self {
285        EnvelopeItem::MonitorCheckIn(check_in)
286    }
287}
288
289impl From<ItemContainer> for EnvelopeItem {
290    fn from(container: ItemContainer) -> Self {
291        EnvelopeItem::ItemContainer(container)
292    }
293}
294
295impl From<Vec<Log>> for EnvelopeItem {
296    fn from(logs: Vec<Log>) -> Self {
297        EnvelopeItem::ItemContainer(logs.into())
298    }
299}
300
301impl From<Vec<Metric>> for EnvelopeItem {
302    fn from(metrics: Vec<Metric>) -> Self {
303        EnvelopeItem::ItemContainer(metrics.into())
304    }
305}
306
307/// An Iterator over the items of an Envelope.
308#[derive(Clone)]
309pub struct EnvelopeItemIter<'s> {
310    inner: std::slice::Iter<'s, EnvelopeItem>,
311}
312
313impl<'s> Iterator for EnvelopeItemIter<'s> {
314    type Item = &'s EnvelopeItem;
315
316    fn next(&mut self) -> Option<Self::Item> {
317        self.inner.next()
318    }
319}
320
321/// The items contained in an [`Envelope`].
322///
323/// This may be a vector of [`EnvelopeItem`]s (the standard case)
324/// or a binary blob.
325#[derive(Debug, Clone, PartialEq)]
326enum Items {
327    EnvelopeItems(Vec<EnvelopeItem>),
328    Raw(Vec<u8>),
329}
330
331impl Default for Items {
332    fn default() -> Self {
333        Self::EnvelopeItems(Default::default())
334    }
335}
336
337impl Items {
338    fn is_empty(&self) -> bool {
339        match self {
340            Items::EnvelopeItems(items) => items.is_empty(),
341            Items::Raw(bytes) => bytes.is_empty(),
342        }
343    }
344}
345
346/// A Sentry Envelope.
347///
348/// An Envelope is the data format that Sentry uses for Ingestion. It can contain
349/// multiple Items, some of which are related, such as Events, and Event Attachments.
350/// Other Items, such as Sessions are independent.
351///
352/// See the [documentation on Envelopes](https://develop.sentry.dev/sdk/envelopes/)
353/// for more details.
354#[derive(Clone, Default, Debug, PartialEq)]
355pub struct Envelope {
356    headers: EnvelopeHeaders,
357    items: Items,
358}
359
360impl Envelope {
361    /// Creates a new empty Envelope.
362    pub fn new() -> Envelope {
363        Default::default()
364    }
365
366    /// Add a new Envelope Item.
367    pub fn add_item<I>(&mut self, item: I)
368    where
369        I: Into<EnvelopeItem>,
370    {
371        let item = item.into();
372
373        let Items::EnvelopeItems(ref mut items) = self.items else {
374            if item != EnvelopeItem::Raw {
375                eprintln!(
376                    "WARNING: This envelope contains raw items. Adding an item is not supported."
377                );
378            }
379            return;
380        };
381
382        if self.headers.event_id.is_none() {
383            if let EnvelopeItem::Event(ref event) = item {
384                self.headers.event_id = Some(event.event_id);
385            } else if let EnvelopeItem::Transaction(ref transaction) = item {
386                self.headers.event_id = Some(transaction.event_id);
387            }
388        }
389        items.push(item);
390    }
391
392    /// Create an [`Iterator`] over all the [`EnvelopeItem`]s.
393    pub fn items(&self) -> EnvelopeItemIter<'_> {
394        let inner = match &self.items {
395            Items::EnvelopeItems(items) => items.iter(),
396            Items::Raw(_) => [].iter(),
397        };
398
399        EnvelopeItemIter { inner }
400    }
401
402    /// Consume the Envelope and create an [`Iterator`] over all
403    /// owned [`EnvelopeItem`]s.
404    ///
405    /// Raw envelopes yield an empty iterator.
406    pub fn into_items(self) -> impl Iterator<Item = EnvelopeItem> {
407        match self.items {
408            Items::EnvelopeItems(items) => items.into_iter(),
409            Items::Raw(_) => Default::default(),
410        }
411    }
412
413    /// Returns the Envelope headers.
414    pub fn headers(&self) -> &EnvelopeHeaders {
415        &self.headers
416    }
417
418    /// Sets the Envelope headers.
419    #[must_use]
420    pub fn with_headers(mut self, headers: EnvelopeHeaders) -> Self {
421        self.headers = headers;
422        self
423    }
424
425    /// Returns the Envelopes Uuid, if any.
426    pub fn uuid(&self) -> Option<&Uuid> {
427        self.headers.event_id.as_ref()
428    }
429
430    /// Returns the [`Event`] contained in this Envelope, if any.
431    ///
432    /// [`Event`]: struct.Event.html
433    pub fn event(&self) -> Option<&Event<'static>> {
434        let Items::EnvelopeItems(ref items) = self.items else {
435            return None;
436        };
437
438        items.iter().find_map(|item| match item {
439            EnvelopeItem::Event(event) => Some(event),
440            _ => None,
441        })
442    }
443
444    /// Filters the Envelope's [`EnvelopeItem`]s based on a predicate,
445    /// and returns a new Envelope containing only the filtered items.
446    ///
447    /// Retains the [`EnvelopeItem`]s for which the predicate returns `true`.
448    /// Additionally, [`EnvelopeItem::Attachment`]s are only kept if the Envelope
449    /// contains an [`EnvelopeItem::Event`] or [`EnvelopeItem::Transaction`].
450    ///
451    /// [`None`] is returned if no items remain in the Envelope after filtering.
452    pub fn filter<P>(self, mut predicate: P) -> Option<Self>
453    where
454        P: FnMut(&EnvelopeItem) -> bool,
455    {
456        let Items::EnvelopeItems(items) = self.items else {
457            return if predicate(&EnvelopeItem::Raw) {
458                Some(self)
459            } else {
460                None
461            };
462        };
463
464        let mut filtered = Envelope::new();
465        for item in items {
466            if predicate(&item) {
467                filtered.add_item(item);
468            }
469        }
470
471        // filter again, removing attachments which do not make any sense without
472        // an event/transaction
473        if filtered.uuid().is_none() {
474            if let Items::EnvelopeItems(ref mut items) = filtered.items {
475                items.retain(|item| !matches!(item, EnvelopeItem::Attachment(..)))
476            }
477        }
478
479        if filtered.items.is_empty() {
480            None
481        } else {
482            Some(filtered)
483        }
484    }
485
486    /// Serialize the Envelope into the given [`Write`].
487    ///
488    /// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
489    pub fn to_writer<W>(&self, mut writer: W) -> std::io::Result<()>
490    where
491        W: Write,
492    {
493        let items = match &self.items {
494            Items::Raw(bytes) => return writer.write_all(bytes).map(|_| ()),
495            Items::EnvelopeItems(items) => items,
496        };
497
498        // write the headers:
499        serde_json::to_writer(&mut writer, &self.headers)?;
500        writeln!(writer)?;
501
502        let mut item_buf = Vec::new();
503        // write each item:
504        for item in items {
505            // we write them to a temporary buffer first, since we need their length
506            match item {
507                EnvelopeItem::Event(event) => serde_json::to_writer(&mut item_buf, event)?,
508                EnvelopeItem::SessionUpdate(session) => {
509                    serde_json::to_writer(&mut item_buf, session)?
510                }
511                EnvelopeItem::SessionAggregates(aggregates) => {
512                    serde_json::to_writer(&mut item_buf, aggregates)?
513                }
514                EnvelopeItem::Transaction(transaction) => {
515                    serde_json::to_writer(&mut item_buf, transaction)?
516                }
517                EnvelopeItem::Attachment(attachment) => {
518                    attachment.to_writer(&mut writer)?;
519                    writeln!(writer)?;
520                    continue;
521                }
522                EnvelopeItem::MonitorCheckIn(check_in) => {
523                    serde_json::to_writer(&mut item_buf, check_in)?
524                }
525                EnvelopeItem::ItemContainer(container) => match container {
526                    ItemContainer::Logs(logs) => {
527                        let wrapper = ItemsSerdeWrapper { items: logs.into() };
528                        serde_json::to_writer(&mut item_buf, &wrapper)?
529                    }
530                    ItemContainer::Metrics(metrics) => {
531                        let wrapper = ItemsSerdeWrapper {
532                            items: metrics.into(),
533                        };
534                        serde_json::to_writer(&mut item_buf, &wrapper)?
535                    }
536                },
537                EnvelopeItem::Raw => {
538                    continue;
539                }
540            }
541            let item_type = match item {
542                EnvelopeItem::Event(_) => "event",
543                EnvelopeItem::SessionUpdate(_) => "session",
544                EnvelopeItem::SessionAggregates(_) => "sessions",
545                EnvelopeItem::Transaction(_) => "transaction",
546                EnvelopeItem::MonitorCheckIn(_) => "check_in",
547                EnvelopeItem::ItemContainer(container) => container.ty(),
548                EnvelopeItem::Attachment(_) | EnvelopeItem::Raw => unreachable!(),
549            };
550
551            if let EnvelopeItem::ItemContainer(container) = item {
552                writeln!(
553                    writer,
554                    r#"{{"type":"{}","item_count":{},"content_type":"{}"}}"#,
555                    item_type,
556                    container.len(),
557                    container.content_type()
558                )?;
559            } else {
560                writeln!(
561                    writer,
562                    r#"{{"type":"{}","length":{}}}"#,
563                    item_type,
564                    item_buf.len()
565                )?;
566            }
567            writer.write_all(&item_buf)?;
568            writeln!(writer)?;
569            item_buf.clear();
570        }
571
572        Ok(())
573    }
574
575    /// Creates a new Envelope from slice.
576    pub fn from_slice(slice: &[u8]) -> Result<Envelope, EnvelopeError> {
577        let (headers, offset) = Self::parse_headers(slice)?;
578        let items = Self::parse_items(slice, offset)?;
579
580        let mut envelope = Envelope {
581            headers,
582            ..Default::default()
583        };
584
585        for item in items {
586            envelope.add_item(item);
587        }
588
589        Ok(envelope)
590    }
591
592    /// Creates a new raw Envelope from the given buffer.
593    pub fn from_bytes_raw(bytes: Vec<u8>) -> Result<Self, EnvelopeError> {
594        Ok(Self {
595            items: Items::Raw(bytes),
596            ..Default::default()
597        })
598    }
599
600    /// Creates a new Envelope from path.
601    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Envelope, EnvelopeError> {
602        let bytes = std::fs::read(path).map_err(|_| EnvelopeError::UnexpectedEof)?;
603        Envelope::from_slice(&bytes)
604    }
605
606    /// Creates a new Envelope from path without attempting to parse anything.
607    ///
608    /// The resulting Envelope will have no `event_id` and the file contents will
609    /// be contained verbatim in the `items` field.
610    pub fn from_path_raw<P: AsRef<Path>>(path: P) -> Result<Self, EnvelopeError> {
611        let bytes = std::fs::read(path).map_err(|_| EnvelopeError::UnexpectedEof)?;
612        Self::from_bytes_raw(bytes)
613    }
614
615    fn parse_headers(slice: &[u8]) -> Result<(EnvelopeHeaders, usize), EnvelopeError> {
616        let first_line = slice
617            .split(|b| *b == b'\n')
618            .next()
619            .ok_or(EnvelopeError::MissingHeader)?;
620
621        let headers: EnvelopeHeaders =
622            serde_json::from_slice(first_line).map_err(EnvelopeError::InvalidHeader)?;
623
624        let offset = first_line.len();
625        Self::require_termination(slice, offset)?;
626
627        // Valid slices cannot be larger than `isize::MAX` bytes:
628        // https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety
629        let byte_after_header = offset
630            .checked_add(1)
631            .expect("offset is at most isize::MAX; adding 1 cannot overflow usize");
632
633        Ok((headers, byte_after_header))
634    }
635
636    fn parse_items(slice: &[u8], mut offset: usize) -> Result<Vec<EnvelopeItem>, EnvelopeError> {
637        let mut items = Vec::new();
638
639        while offset < slice.len() {
640            let bytes = slice
641                .get(offset..)
642                .ok_or(EnvelopeError::MissingItemHeader)?;
643            let (item, item_offset) = Self::parse_item(bytes)?;
644            // `bytes` is `slice[offset..]`, so `bytes.len() == slice.len() - offset`.
645            // Since `item_offset <= bytes.len() + 1`, `offset + item_offset <= slice.len() + 1`.
646            // Valid slices cannot exceed `isize::MAX` bytes, so `slice.len() + 1` cannot overflow `usize`.
647            offset = offset
648                .checked_add(item_offset)
649                .expect("offset + item_offset is at most slice.len() + 1");
650            items.push(item);
651        }
652
653        Ok(items)
654    }
655
656    /// Parses one envelope item from the beginning of `slice`.
657    ///
658    /// Returns the parsed item and the offset at which parsing should continue.
659    ///
660    /// The offset is relative to `slice`. It points just after the payload terminator if one is
661    /// present, or one byte past the end of `slice` if the payload ends at the end of `slice`.
662    /// The offset therefore satisfies `1 <= offset <= slice.len() + 1`.
663    fn parse_item(slice: &[u8]) -> Result<(EnvelopeItem, usize), EnvelopeError> {
664        let mut stream = serde_json::Deserializer::from_slice(slice).into_iter();
665
666        let header: EnvelopeItemHeader = match stream.next() {
667            None => return Err(EnvelopeError::UnexpectedEof),
668            Some(Err(error)) => return Err(EnvelopeError::InvalidItemHeader(error)),
669            Some(Ok(header)) => header,
670        };
671
672        // Each header is terminated by a UNIX newline.
673        let header_end = stream.byte_offset();
674        Self::require_termination(slice, header_end)?;
675
676        // The last header does not require a trailing newline, so `payload_start` may point
677        // past the end of the buffer.
678        // The checked_add never overflows since slices cannot exceed isize::MAX bytes:
679        // https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety
680        let payload_start = std::cmp::min(header_end, slice.len().saturating_sub(1))
681            .checked_add(1)
682            .expect("&[u8] length is at most isize::MAX; adding one cannot overflow usize");
683        let payload_end = match header.length {
684            Some(len) => {
685                let payload_end = payload_start.saturating_add(len);
686                if slice.len() < payload_end {
687                    return Err(EnvelopeError::UnexpectedEof);
688                }
689
690                // Each payload is terminated by a UNIX newline.
691                Self::require_termination(slice, payload_end)?;
692                payload_end
693            }
694            None => match slice.get(payload_start..) {
695                Some(range) => match range.iter().position(|&b| b == b'\n') {
696                    Some(relative_end) => payload_start.checked_add(relative_end).expect(
697                        "relative_end is an index into slice[payload_start..]; \
698                        so the sum is within slice and cannot overflow usize",
699                    ),
700                    None => slice.len(),
701                },
702                None => slice.len(),
703            },
704        };
705
706        let payload = slice.get(payload_start..payload_end).unwrap();
707
708        let item = match header.r#type {
709            EnvelopeItemType::Event => serde_json::from_slice(payload).map(EnvelopeItem::Event),
710            EnvelopeItemType::Transaction => {
711                serde_json::from_slice(payload).map(EnvelopeItem::Transaction)
712            }
713            EnvelopeItemType::SessionUpdate => {
714                serde_json::from_slice(payload).map(EnvelopeItem::SessionUpdate)
715            }
716            EnvelopeItemType::SessionAggregates => {
717                serde_json::from_slice(payload).map(EnvelopeItem::SessionAggregates)
718            }
719            EnvelopeItemType::Attachment => Ok(EnvelopeItem::Attachment(Attachment {
720                buffer: payload.to_owned(),
721                filename: header.filename.unwrap_or_default(),
722                content_type: header.content_type,
723                ty: header.attachment_type,
724            })),
725            EnvelopeItemType::MonitorCheckIn => {
726                serde_json::from_slice(payload).map(EnvelopeItem::MonitorCheckIn)
727            }
728            EnvelopeItemType::LogsContainer => {
729                serde_json::from_slice::<ItemsSerdeWrapper<_>>(payload)
730                    .map(|x| EnvelopeItem::ItemContainer(ItemContainer::Logs(x.items.into())))
731            }
732            EnvelopeItemType::MetricsContainer => {
733                serde_json::from_slice::<ItemsSerdeWrapper<_>>(payload)
734                    .map(|x| EnvelopeItem::ItemContainer(ItemContainer::Metrics(x.items.into())))
735            }
736        }
737        .map_err(EnvelopeError::InvalidItemPayload)?;
738
739        // Valid slices cannot be larger than `isize::MAX` bytes:
740        // https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety
741        let byte_after_payload = payload_end
742            .checked_add(1)
743            .expect("payload_end <= slice.len() <= isize::MAX, so adding 1 cannot overflow usize");
744
745        Ok((item, byte_after_payload))
746    }
747
748    fn require_termination(slice: &[u8], offset: usize) -> Result<(), EnvelopeError> {
749        match slice.get(offset) {
750            Some(&b'\n') | None => Ok(()),
751            Some(_) => Err(EnvelopeError::MissingNewline),
752        }
753    }
754}
755
756impl<T> From<T> for Envelope
757where
758    T: Into<EnvelopeItem>,
759{
760    fn from(item: T) -> Self {
761        let mut envelope = Self::default();
762        envelope.add_item(item.into());
763        envelope
764    }
765}
766
767#[cfg(test)]
768mod test {
769    use std::str::FromStr;
770    use std::time::{Duration, SystemTime};
771
772    use protocol::Map;
773    use serde_json::Value;
774    use time::format_description::well_known::Rfc3339;
775    use time::OffsetDateTime;
776
777    use super::*;
778    use crate::protocol::v7::{
779        Level, MonitorCheckInStatus, MonitorConfig, MonitorSchedule, SampleRand, SessionAttributes,
780        SessionStatus, Span,
781    };
782
783    fn to_str(envelope: Envelope) -> String {
784        let mut vec = Vec::new();
785        envelope.to_writer(&mut vec).unwrap();
786        String::from_utf8_lossy(&vec).to_string()
787    }
788
789    fn timestamp(s: &str) -> SystemTime {
790        let dt = OffsetDateTime::parse(s, &Rfc3339).unwrap();
791        let secs = dt.unix_timestamp() as u64;
792        let nanos = dt.nanosecond();
793        let duration = Duration::new(secs, nanos);
794        SystemTime::UNIX_EPOCH.checked_add(duration).unwrap()
795    }
796
797    #[test]
798    fn test_empty() {
799        assert_eq!(to_str(Envelope::new()), "{}\n");
800    }
801
802    #[test]
803    fn raw_roundtrip() {
804        let buf = r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
805{"type":"event","length":74}
806{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
807"#;
808        let envelope = Envelope::from_bytes_raw(buf.to_string().into_bytes()).unwrap();
809        let serialized = to_str(envelope);
810        assert_eq!(&serialized, buf);
811
812        let random_invalid_bytes = b"oh stahp!\0\x01\x02";
813        let envelope = Envelope::from_bytes_raw(random_invalid_bytes.to_vec()).unwrap();
814        let mut serialized = Vec::new();
815        envelope.to_writer(&mut serialized).unwrap();
816        assert_eq!(&serialized, random_invalid_bytes);
817    }
818
819    #[test]
820    fn test_event() {
821        let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
822        let timestamp = timestamp("2020-07-20T14:51:14.296Z");
823        let event = Event {
824            event_id,
825            timestamp,
826            ..Default::default()
827        };
828        let envelope: Envelope = event.into();
829        assert_eq!(
830            to_str(envelope),
831            r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
832{"type":"event","length":74}
833{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
834"#
835        )
836    }
837
838    #[test]
839    fn test_session() {
840        let session_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
841        let started = timestamp("2020-07-20T14:51:14.296Z");
842        let session = SessionUpdate {
843            session_id,
844            distinct_id: Some("foo@bar.baz".to_owned()),
845            sequence: None,
846            timestamp: None,
847            started,
848            init: true,
849            duration: Some(1.234),
850            status: SessionStatus::Ok,
851            errors: 123,
852            attributes: SessionAttributes {
853                release: "foo-bar@1.2.3".into(),
854                environment: Some("production".into()),
855                ip_address: None,
856                user_agent: None,
857            },
858        };
859        let mut envelope = Envelope::new();
860        envelope.add_item(session);
861        assert_eq!(
862            to_str(envelope),
863            r#"{}
864{"type":"session","length":222}
865{"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"}}
866"#
867        )
868    }
869
870    #[test]
871    fn test_transaction() {
872        let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
873        let span_id = "d42cee9fc3e74f5c".parse().unwrap();
874        let trace_id = "335e53d614474acc9f89e632b776cc28".parse().unwrap();
875        let start_timestamp = timestamp("2020-07-20T14:51:14.296Z");
876        let spans = vec![Span {
877            span_id,
878            trace_id,
879            start_timestamp,
880            ..Default::default()
881        }];
882        let transaction = Transaction {
883            event_id,
884            start_timestamp,
885            spans,
886            ..Default::default()
887        };
888        let envelope: Envelope = transaction.into();
889        assert_eq!(
890            to_str(envelope),
891            r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
892{"type":"transaction","length":200}
893{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","start_timestamp":1595256674.296,"spans":[{"span_id":"d42cee9fc3e74f5c","trace_id":"335e53d614474acc9f89e632b776cc28","start_timestamp":1595256674.296}]}
894"#
895        )
896    }
897
898    #[test]
899    fn test_monitor_checkin() {
900        let check_in_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
901
902        let check_in = MonitorCheckIn {
903            check_in_id,
904            monitor_slug: "my-monitor".into(),
905            status: MonitorCheckInStatus::Ok,
906            duration: Some(123.4),
907            environment: Some("production".into()),
908            monitor_config: Some(MonitorConfig {
909                schedule: MonitorSchedule::Crontab {
910                    value: "12 0 * * *".into(),
911                },
912                checkin_margin: Some(5),
913                max_runtime: Some(30),
914                timezone: Some("UTC".into()),
915                failure_issue_threshold: None,
916                recovery_threshold: None,
917            }),
918        };
919        let envelope: Envelope = check_in.into();
920        assert_eq!(
921            to_str(envelope),
922            r#"{}
923{"type":"check_in","length":259}
924{"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"}}
925"#
926        )
927    }
928
929    #[test]
930    fn test_monitor_checkin_with_thresholds() {
931        let check_in_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
932
933        let check_in = MonitorCheckIn {
934            check_in_id,
935            monitor_slug: "my-monitor".into(),
936            status: MonitorCheckInStatus::Ok,
937            duration: Some(123.4),
938            environment: Some("production".into()),
939            monitor_config: Some(MonitorConfig {
940                schedule: MonitorSchedule::Crontab {
941                    value: "12 0 * * *".into(),
942                },
943                checkin_margin: Some(5),
944                max_runtime: Some(30),
945                timezone: Some("UTC".into()),
946                failure_issue_threshold: Some(4),
947                recovery_threshold: Some(7),
948            }),
949        };
950        let envelope: Envelope = check_in.into();
951        assert_eq!(
952            to_str(envelope),
953            r#"{}
954{"type":"check_in","length":310}
955{"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}}
956"#
957        )
958    }
959
960    #[test]
961    fn test_event_with_attachment() {
962        let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
963        let timestamp = timestamp("2020-07-20T14:51:14.296Z");
964        let event = Event {
965            event_id,
966            timestamp,
967            ..Default::default()
968        };
969        let mut envelope: Envelope = event.into();
970
971        envelope.add_item(Attachment {
972            buffer: "some content".as_bytes().to_vec(),
973            filename: "file.txt".to_string(),
974            ..Default::default()
975        });
976
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{"type":"attachment","length":12,"filename":"file.txt","attachment_type":"event.attachment","content_type":"application/octet-stream"}
983some content
984"#
985        )
986    }
987
988    #[test]
989    fn test_deserialize_envelope_empty() {
990        // Without terminating newline after header
991        let bytes = b"{\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}";
992        let envelope = Envelope::from_slice(bytes).unwrap();
993
994        let event_id = Uuid::from_str("9ec79c33ec9942ab8353589fcb2e04dc").unwrap();
995        assert_eq!(envelope.headers.event_id, Some(event_id));
996        assert_eq!(envelope.items().count(), 0);
997    }
998
999    #[test]
1000    fn test_deserialize_envelope_empty_newline() {
1001        // With terminating newline after header
1002        let bytes = b"{\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n";
1003        let envelope = Envelope::from_slice(bytes).unwrap();
1004        assert_eq!(envelope.items().count(), 0);
1005    }
1006
1007    #[test]
1008    fn test_deserialize_envelope_empty_item_newline() {
1009        // With terminating newline after item payload
1010        let bytes = b"\
1011             {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1012             {\"type\":\"attachment\",\"length\":0}\n\
1013             \n\
1014             {\"type\":\"attachment\",\"length\":0}\n\
1015             ";
1016
1017        let envelope = Envelope::from_slice(bytes).unwrap();
1018        assert_eq!(envelope.items().count(), 2);
1019
1020        let mut items = envelope.items();
1021
1022        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1023            assert_eq!(attachment.buffer.len(), 0);
1024        } else {
1025            panic!("invalid item type");
1026        }
1027
1028        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1029            assert_eq!(attachment.buffer.len(), 0);
1030        } else {
1031            panic!("invalid item type");
1032        }
1033    }
1034
1035    #[test]
1036    fn test_deserialize_envelope_empty_item_eof() {
1037        // With terminating newline after item payload
1038        let bytes = b"\
1039             {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1040             {\"type\":\"attachment\",\"length\":0}\n\
1041             \n\
1042             {\"type\":\"attachment\",\"length\":0}\
1043             ";
1044
1045        let envelope = Envelope::from_slice(bytes).unwrap();
1046        assert_eq!(envelope.items().count(), 2);
1047
1048        let mut items = envelope.items();
1049
1050        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1051            assert_eq!(attachment.buffer.len(), 0);
1052        } else {
1053            panic!("invalid item type");
1054        }
1055
1056        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1057            assert_eq!(attachment.buffer.len(), 0);
1058        } else {
1059            panic!("invalid item type");
1060        }
1061    }
1062
1063    #[test]
1064    fn test_deserialize_envelope_implicit_length() {
1065        // With terminating newline after item payload
1066        let bytes = b"\
1067             {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1068             {\"type\":\"attachment\"}\n\
1069             helloworld\n\
1070             ";
1071
1072        let envelope = Envelope::from_slice(bytes).unwrap();
1073        assert_eq!(envelope.items().count(), 1);
1074
1075        let mut items = envelope.items();
1076
1077        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1078            assert_eq!(attachment.buffer.len(), 10);
1079        } else {
1080            panic!("invalid item type");
1081        }
1082    }
1083
1084    #[test]
1085    fn test_deserialize_envelope_implicit_length_eof() {
1086        // With item ending the envelope
1087        let bytes = b"\
1088             {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1089             {\"type\":\"attachment\"}\n\
1090             helloworld\
1091             ";
1092
1093        let envelope = Envelope::from_slice(bytes).unwrap();
1094        assert_eq!(envelope.items().count(), 1);
1095
1096        let mut items = envelope.items();
1097
1098        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1099            assert_eq!(attachment.buffer.len(), 10);
1100        } else {
1101            panic!("invalid item type");
1102        }
1103    }
1104
1105    #[test]
1106    fn test_deserialize_envelope_implicit_length_empty_eof() {
1107        // Empty item with implicit length ending the envelope
1108        let bytes = b"\
1109             {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1110             {\"type\":\"attachment\"}\
1111             ";
1112
1113        let envelope = Envelope::from_slice(bytes).unwrap();
1114        assert_eq!(envelope.items().count(), 1);
1115
1116        let mut items = envelope.items();
1117
1118        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1119            assert_eq!(attachment.buffer.len(), 0);
1120        } else {
1121            panic!("invalid item type");
1122        }
1123    }
1124
1125    #[test]
1126    fn test_deserialize_envelope_multiple_items() {
1127        // With terminating newline
1128        let bytes = b"\
1129            {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1130            {\"type\":\"attachment\",\"length\":10,\"content_type\":\"text/plain\",\"filename\":\"hello.txt\"}\n\
1131            \xef\xbb\xbfHello\r\n\n\
1132            {\"type\":\"event\",\"length\":41,\"content_type\":\"application/json\",\"filename\":\"application.log\"}\n\
1133            {\"message\":\"hello world\",\"level\":\"error\"}\n\
1134            ";
1135
1136        let envelope = Envelope::from_slice(bytes).unwrap();
1137        assert_eq!(envelope.items().count(), 2);
1138
1139        let mut items = envelope.items();
1140
1141        if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1142            assert_eq!(attachment.buffer.len(), 10);
1143            assert_eq!(attachment.buffer, b"\xef\xbb\xbfHello\r\n");
1144            assert_eq!(attachment.filename, "hello.txt");
1145            assert_eq!(attachment.content_type, Some("text/plain".to_string()));
1146        } else {
1147            panic!("invalid item type");
1148        }
1149
1150        if let EnvelopeItem::Event(event) = items.next().unwrap() {
1151            assert_eq!(event.message, Some("hello world".to_string()));
1152            assert_eq!(event.level, Level::Error);
1153        } else {
1154            panic!("invalid item type");
1155        }
1156    }
1157
1158    #[test]
1159    fn test_all_envelope_headers_roundtrip() {
1160        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"}}
1161{"type":"event","length":74}
1162{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
1163"#;
1164
1165        let envelope = Envelope::from_slice(bytes);
1166        assert!(envelope.is_ok());
1167        let envelope = envelope.unwrap();
1168        let serialized = to_str(envelope);
1169        assert_eq!(bytes, serialized.as_bytes());
1170    }
1171
1172    #[test]
1173    fn test_sample_rand_rounding() {
1174        let envelope = Envelope::new().with_headers(
1175            EnvelopeHeaders::new().with_trace(
1176                DynamicSamplingContext::new()
1177                    .with_sample_rand(SampleRand::try_from(0.999_999_9).unwrap()),
1178            ),
1179        );
1180        let expected = br#"{"trace":{"sample_rand":"0.999999"}}
1181"#;
1182
1183        let serialized = to_str(envelope);
1184        assert_eq!(expected, serialized.as_bytes());
1185    }
1186
1187    #[test]
1188    fn test_metric_container_header() {
1189        let metrics: EnvelopeItem = vec![Metric {
1190            r#type: protocol::MetricType::Counter,
1191            name: "api.requests".into(),
1192            value: 1.0,
1193            timestamp: timestamp("2026-03-02T13:36:02.000Z"),
1194            trace_id: "335e53d614474acc9f89e632b776cc28".parse().unwrap(),
1195            span_id: None,
1196            unit: None,
1197            attributes: Map::new(),
1198        }]
1199        .into();
1200
1201        let mut envelope = Envelope::new();
1202        envelope.add_item(metrics);
1203
1204        let expected = [
1205            serde_json::json!({}),
1206            serde_json::json!({
1207                "type": "trace_metric",
1208                "item_count": 1,
1209                "content_type": "application/vnd.sentry.items.trace-metric+json"
1210            }),
1211            serde_json::json!({
1212                "items": [{
1213                    "type": "counter",
1214                    "name": "api.requests",
1215                    "value": 1.0,
1216                    "timestamp": 1772458562,
1217                    "trace_id": "335e53d614474acc9f89e632b776cc28"
1218                }]
1219            }),
1220        ];
1221
1222        let serialized = to_str(envelope);
1223        let actual = serialized
1224            .lines()
1225            .map(|line| serde_json::from_str::<Value>(line).expect("envelope has invalid JSON"));
1226
1227        assert!(actual.eq(expected.into_iter()));
1228    }
1229
1230    // Test all possible item types in a single envelope
1231    #[test]
1232    fn test_deserialize_serialized() {
1233        // Event
1234        let event = Event {
1235            event_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap(),
1236            timestamp: timestamp("2020-07-20T14:51:14.296Z"),
1237            ..Default::default()
1238        };
1239
1240        // Transaction
1241        let transaction = Transaction {
1242            event_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9d").unwrap(),
1243            start_timestamp: timestamp("2020-07-20T14:51:14.296Z"),
1244            spans: vec![Span {
1245                span_id: "d42cee9fc3e74f5c".parse().unwrap(),
1246                trace_id: "335e53d614474acc9f89e632b776cc28".parse().unwrap(),
1247                start_timestamp: timestamp("2020-07-20T14:51:14.296Z"),
1248                ..Default::default()
1249            }],
1250            ..Default::default()
1251        };
1252
1253        // Session
1254        let session = SessionUpdate {
1255            session_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap(),
1256            distinct_id: Some("foo@bar.baz".to_owned()),
1257            sequence: None,
1258            timestamp: None,
1259            started: timestamp("2020-07-20T14:51:14.296Z"),
1260            init: true,
1261            duration: Some(1.234),
1262            status: SessionStatus::Ok,
1263            errors: 123,
1264            attributes: SessionAttributes {
1265                release: "foo-bar@1.2.3".into(),
1266                environment: Some("production".into()),
1267                ip_address: None,
1268                user_agent: None,
1269            },
1270        };
1271
1272        // Attachment
1273        let attachment = Attachment {
1274            buffer: "some content".as_bytes().to_vec(),
1275            filename: "file.txt".to_string(),
1276            ..Default::default()
1277        };
1278
1279        let mut attributes = Map::new();
1280        attributes.insert("key".into(), "value".into());
1281        attributes.insert("num".into(), 10.into());
1282        attributes.insert("val".into(), 10.2.into());
1283        attributes.insert("bool".into(), false.into());
1284        let mut attributes_2 = attributes.clone();
1285        attributes_2.insert("more".into(), true.into());
1286        let logs: EnvelopeItem = vec![
1287            Log {
1288                level: protocol::LogLevel::Warn,
1289                body: "test".to_owned(),
1290                trace_id: Some("335e53d614474acc9f89e632b776cc28".parse().unwrap()),
1291                timestamp: timestamp("2022-07-25T14:51:14.296Z"),
1292                severity_number: Some(1.try_into().unwrap()),
1293                attributes,
1294            },
1295            Log {
1296                level: protocol::LogLevel::Error,
1297                body: "a body".to_owned(),
1298                trace_id: Some("332253d614472a2c9f89e232b7762c28".parse().unwrap()),
1299                timestamp: timestamp("2021-07-21T14:51:14.296Z"),
1300                severity_number: Some(1.try_into().unwrap()),
1301                attributes: attributes_2,
1302            },
1303        ]
1304        .into();
1305
1306        let mut metric_attributes = Map::new();
1307        metric_attributes.insert("route".into(), "/users".into());
1308        let metrics: EnvelopeItem = vec![Metric {
1309            r#type: protocol::MetricType::Distribution,
1310            name: "response.time".into(),
1311            value: 123.4,
1312            timestamp: timestamp("2022-07-26T14:51:14.296Z"),
1313            trace_id: "335e53d614474acc9f89e632b776cc28".parse().unwrap(),
1314            span_id: Some("d42cee9fc3e74f5c".parse().unwrap()),
1315            unit: Some("millisecond".into()),
1316            attributes: metric_attributes,
1317        }]
1318        .into();
1319
1320        let mut envelope: Envelope = Envelope::new();
1321        envelope.add_item(event);
1322        envelope.add_item(transaction);
1323        envelope.add_item(session);
1324        envelope.add_item(attachment);
1325        envelope.add_item(logs);
1326        envelope.add_item(metrics);
1327
1328        let serialized = to_str(envelope);
1329        let deserialized = Envelope::from_slice(serialized.as_bytes()).unwrap();
1330        assert_eq!(serialized, to_str(deserialized))
1331    }
1332}