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