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, MonitorCheckIn,
14 SessionAggregates, SessionUpdate, Transaction,
15};
16
17#[derive(Debug, Error)]
19pub enum EnvelopeError {
20 #[error("unexpected end of file")]
22 UnexpectedEof,
23 #[error("missing envelope header")]
25 MissingHeader,
26 #[error("missing item header")]
28 MissingItemHeader,
29 #[error("missing newline after header or payload")]
31 MissingNewline,
32 #[error("invalid envelope header")]
34 InvalidHeader(#[source] serde_json::Error),
35 #[error("invalid item header")]
37 InvalidItemHeader(#[source] serde_json::Error),
38 #[error("invalid item payload")]
40 InvalidItemPayload(#[source] serde_json::Error),
41}
42
43#[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 pub fn new() -> EnvelopeHeaders {
65 Default::default()
66 }
67
68 #[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 #[must_use]
77 pub fn with_dsn(mut self, dsn: Dsn) -> Self {
78 self.dsn = Some(dsn);
79 self
80 }
81
82 #[must_use]
84 pub fn with_sdk(mut self, sdk: ClientSdkInfo) -> Self {
85 self.sdk = Some(sdk);
86 self
87 }
88
89 #[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 #[must_use]
99 pub fn with_trace(mut self, trace: DynamicSamplingContext) -> Self {
100 self.trace = Some(trace);
101 self
102 }
103}
104
105#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
107#[non_exhaustive]
108enum EnvelopeItemType {
109 #[serde(rename = "event")]
111 Event,
112 #[serde(rename = "session")]
114 SessionUpdate,
115 #[serde(rename = "sessions")]
117 SessionAggregates,
118 #[serde(rename = "transaction")]
120 Transaction,
121 #[serde(rename = "attachment")]
123 Attachment,
124 #[serde(rename = "check_in")]
126 MonitorCheckIn,
127 #[serde(rename = "log")]
129 LogsContainer,
130}
131
132#[derive(Clone, Debug, Deserialize)]
134struct EnvelopeItemHeader {
135 r#type: EnvelopeItemType,
136 length: Option<usize>,
137 content_type: Option<String>,
139 filename: Option<String>,
141 attachment_type: Option<AttachmentType>,
142}
143
144#[derive(Clone, Debug, PartialEq)]
149#[non_exhaustive]
150#[allow(clippy::large_enum_variant)]
151pub enum EnvelopeItem {
152 Event(Event<'static>),
157 SessionUpdate(SessionUpdate<'static>),
162 SessionAggregates(SessionAggregates<'static>),
167 Transaction(Transaction<'static>),
172 Attachment(Attachment),
177 MonitorCheckIn(MonitorCheckIn),
179 ItemContainer(ItemContainer),
181 Raw,
183 }
186
187#[derive(Clone, Debug, PartialEq)]
191#[non_exhaustive]
192pub enum ItemContainer {
193 Logs(Vec<Log>),
195}
196
197#[allow(clippy::len_without_is_empty, reason = "is_empty is not needed")]
198impl ItemContainer {
199 pub fn len(&self) -> usize {
201 match self {
202 Self::Logs(logs) => logs.len(),
203 }
204 }
205
206 pub fn ty(&self) -> &'static str {
208 match self {
209 Self::Logs(_) => "log",
210 }
211 }
212
213 pub fn content_type(&self) -> &'static str {
215 match self {
216 Self::Logs(_) => "application/vnd.sentry.items.log+json",
217 }
218 }
219}
220
221impl From<Vec<Log>> for ItemContainer {
222 fn from(logs: Vec<Log>) -> Self {
223 Self::Logs(logs)
224 }
225}
226
227#[derive(Deserialize, Serialize)]
234struct ItemsSerdeWrapper<'a, T: Clone> {
235 items: Cow<'a, [T]>,
236}
237
238impl From<Event<'static>> for EnvelopeItem {
239 fn from(event: Event<'static>) -> Self {
240 EnvelopeItem::Event(event)
241 }
242}
243
244impl From<SessionUpdate<'static>> for EnvelopeItem {
245 fn from(session: SessionUpdate<'static>) -> Self {
246 EnvelopeItem::SessionUpdate(session)
247 }
248}
249
250impl From<SessionAggregates<'static>> for EnvelopeItem {
251 fn from(aggregates: SessionAggregates<'static>) -> Self {
252 EnvelopeItem::SessionAggregates(aggregates)
253 }
254}
255
256impl From<Transaction<'static>> for EnvelopeItem {
257 fn from(transaction: Transaction<'static>) -> Self {
258 EnvelopeItem::Transaction(transaction)
259 }
260}
261
262impl From<Attachment> for EnvelopeItem {
263 fn from(attachment: Attachment) -> Self {
264 EnvelopeItem::Attachment(attachment)
265 }
266}
267
268impl From<MonitorCheckIn> for EnvelopeItem {
269 fn from(check_in: MonitorCheckIn) -> Self {
270 EnvelopeItem::MonitorCheckIn(check_in)
271 }
272}
273
274impl From<ItemContainer> for EnvelopeItem {
275 fn from(container: ItemContainer) -> Self {
276 EnvelopeItem::ItemContainer(container)
277 }
278}
279
280impl From<Vec<Log>> for EnvelopeItem {
281 fn from(logs: Vec<Log>) -> Self {
282 EnvelopeItem::ItemContainer(logs.into())
283 }
284}
285
286#[derive(Clone)]
288pub struct EnvelopeItemIter<'s> {
289 inner: std::slice::Iter<'s, EnvelopeItem>,
290}
291
292impl<'s> Iterator for EnvelopeItemIter<'s> {
293 type Item = &'s EnvelopeItem;
294
295 fn next(&mut self) -> Option<Self::Item> {
296 self.inner.next()
297 }
298}
299
300#[derive(Debug, Clone, PartialEq)]
305enum Items {
306 EnvelopeItems(Vec<EnvelopeItem>),
307 Raw(Vec<u8>),
308}
309
310impl Default for Items {
311 fn default() -> Self {
312 Self::EnvelopeItems(Default::default())
313 }
314}
315
316impl Items {
317 fn is_empty(&self) -> bool {
318 match self {
319 Items::EnvelopeItems(items) => items.is_empty(),
320 Items::Raw(bytes) => bytes.is_empty(),
321 }
322 }
323}
324
325#[derive(Clone, Default, Debug, PartialEq)]
334pub struct Envelope {
335 headers: EnvelopeHeaders,
336 items: Items,
337}
338
339impl Envelope {
340 pub fn new() -> Envelope {
342 Default::default()
343 }
344
345 pub fn add_item<I>(&mut self, item: I)
347 where
348 I: Into<EnvelopeItem>,
349 {
350 let item = item.into();
351
352 let Items::EnvelopeItems(ref mut items) = self.items else {
353 if item != EnvelopeItem::Raw {
354 eprintln!(
355 "WARNING: This envelope contains raw items. Adding an item is not supported."
356 );
357 }
358 return;
359 };
360
361 if self.headers.event_id.is_none() {
362 if let EnvelopeItem::Event(ref event) = item {
363 self.headers.event_id = Some(event.event_id);
364 } else if let EnvelopeItem::Transaction(ref transaction) = item {
365 self.headers.event_id = Some(transaction.event_id);
366 }
367 }
368 items.push(item);
369 }
370
371 pub fn items(&self) -> EnvelopeItemIter<'_> {
373 let inner = match &self.items {
374 Items::EnvelopeItems(items) => items.iter(),
375 Items::Raw(_) => [].iter(),
376 };
377
378 EnvelopeItemIter { inner }
379 }
380
381 pub fn into_items(self) -> impl Iterator<Item = EnvelopeItem> {
386 match self.items {
387 Items::EnvelopeItems(items) => items.into_iter(),
388 Items::Raw(_) => Default::default(),
389 }
390 }
391
392 pub fn headers(&self) -> &EnvelopeHeaders {
394 &self.headers
395 }
396
397 #[must_use]
399 pub fn with_headers(mut self, headers: EnvelopeHeaders) -> Self {
400 self.headers = headers;
401 self
402 }
403
404 pub fn uuid(&self) -> Option<&Uuid> {
406 self.headers.event_id.as_ref()
407 }
408
409 pub fn event(&self) -> Option<&Event<'static>> {
413 let Items::EnvelopeItems(ref items) = self.items else {
414 return None;
415 };
416
417 items.iter().find_map(|item| match item {
418 EnvelopeItem::Event(event) => Some(event),
419 _ => None,
420 })
421 }
422
423 pub fn filter<P>(self, mut predicate: P) -> Option<Self>
432 where
433 P: FnMut(&EnvelopeItem) -> bool,
434 {
435 let Items::EnvelopeItems(items) = self.items else {
436 return if predicate(&EnvelopeItem::Raw) {
437 Some(self)
438 } else {
439 None
440 };
441 };
442
443 let mut filtered = Envelope::new();
444 for item in items {
445 if predicate(&item) {
446 filtered.add_item(item);
447 }
448 }
449
450 if filtered.uuid().is_none() {
453 if let Items::EnvelopeItems(ref mut items) = filtered.items {
454 items.retain(|item| !matches!(item, EnvelopeItem::Attachment(..)))
455 }
456 }
457
458 if filtered.items.is_empty() {
459 None
460 } else {
461 Some(filtered)
462 }
463 }
464
465 pub fn to_writer<W>(&self, mut writer: W) -> std::io::Result<()>
469 where
470 W: Write,
471 {
472 let items = match &self.items {
473 Items::Raw(bytes) => return writer.write_all(bytes).map(|_| ()),
474 Items::EnvelopeItems(items) => items,
475 };
476
477 serde_json::to_writer(&mut writer, &self.headers)?;
479 writeln!(writer)?;
480
481 let mut item_buf = Vec::new();
482 for item in items {
484 match item {
486 EnvelopeItem::Event(event) => serde_json::to_writer(&mut item_buf, event)?,
487 EnvelopeItem::SessionUpdate(session) => {
488 serde_json::to_writer(&mut item_buf, session)?
489 }
490 EnvelopeItem::SessionAggregates(aggregates) => {
491 serde_json::to_writer(&mut item_buf, aggregates)?
492 }
493 EnvelopeItem::Transaction(transaction) => {
494 serde_json::to_writer(&mut item_buf, transaction)?
495 }
496 EnvelopeItem::Attachment(attachment) => {
497 attachment.to_writer(&mut writer)?;
498 writeln!(writer)?;
499 continue;
500 }
501 EnvelopeItem::MonitorCheckIn(check_in) => {
502 serde_json::to_writer(&mut item_buf, check_in)?
503 }
504 EnvelopeItem::ItemContainer(container) => match container {
505 ItemContainer::Logs(logs) => {
506 let wrapper = ItemsSerdeWrapper { items: logs.into() };
507 serde_json::to_writer(&mut item_buf, &wrapper)?
508 }
509 },
510 EnvelopeItem::Raw => {
511 continue;
512 }
513 }
514 let item_type = match item {
515 EnvelopeItem::Event(_) => "event",
516 EnvelopeItem::SessionUpdate(_) => "session",
517 EnvelopeItem::SessionAggregates(_) => "sessions",
518 EnvelopeItem::Transaction(_) => "transaction",
519 EnvelopeItem::MonitorCheckIn(_) => "check_in",
520 EnvelopeItem::ItemContainer(container) => container.ty(),
521 EnvelopeItem::Attachment(_) | EnvelopeItem::Raw => unreachable!(),
522 };
523
524 if let EnvelopeItem::ItemContainer(container) = item {
525 writeln!(
526 writer,
527 r#"{{"type":"{}","item_count":{},"content_type":"{}"}}"#,
528 item_type,
529 container.len(),
530 container.content_type()
531 )?;
532 } else {
533 writeln!(
534 writer,
535 r#"{{"type":"{}","length":{}}}"#,
536 item_type,
537 item_buf.len()
538 )?;
539 }
540 writer.write_all(&item_buf)?;
541 writeln!(writer)?;
542 item_buf.clear();
543 }
544
545 Ok(())
546 }
547
548 pub fn from_slice(slice: &[u8]) -> Result<Envelope, EnvelopeError> {
550 let (headers, offset) = Self::parse_headers(slice)?;
551 let items = Self::parse_items(slice, offset)?;
552
553 let mut envelope = Envelope {
554 headers,
555 ..Default::default()
556 };
557
558 for item in items {
559 envelope.add_item(item);
560 }
561
562 Ok(envelope)
563 }
564
565 pub fn from_bytes_raw(bytes: Vec<u8>) -> Result<Self, EnvelopeError> {
567 Ok(Self {
568 items: Items::Raw(bytes),
569 ..Default::default()
570 })
571 }
572
573 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Envelope, EnvelopeError> {
575 let bytes = std::fs::read(path).map_err(|_| EnvelopeError::UnexpectedEof)?;
576 Envelope::from_slice(&bytes)
577 }
578
579 pub fn from_path_raw<P: AsRef<Path>>(path: P) -> Result<Self, EnvelopeError> {
584 let bytes = std::fs::read(path).map_err(|_| EnvelopeError::UnexpectedEof)?;
585 Self::from_bytes_raw(bytes)
586 }
587
588 fn parse_headers(slice: &[u8]) -> Result<(EnvelopeHeaders, usize), EnvelopeError> {
589 let first_line = slice
590 .split(|b| *b == b'\n')
591 .next()
592 .ok_or(EnvelopeError::MissingHeader)?;
593
594 let headers: EnvelopeHeaders =
595 serde_json::from_slice(first_line).map_err(EnvelopeError::InvalidHeader)?;
596
597 let offset = first_line.len();
598 Self::require_termination(slice, offset)?;
599
600 Ok((headers, offset + 1))
601 }
602
603 fn parse_items(slice: &[u8], mut offset: usize) -> Result<Vec<EnvelopeItem>, EnvelopeError> {
604 let mut items = Vec::new();
605
606 while offset < slice.len() {
607 let bytes = slice
608 .get(offset..)
609 .ok_or(EnvelopeError::MissingItemHeader)?;
610 let (item, item_size) = Self::parse_item(bytes)?;
611 offset += item_size;
612 items.push(item);
613 }
614
615 Ok(items)
616 }
617
618 fn parse_item(slice: &[u8]) -> Result<(EnvelopeItem, usize), EnvelopeError> {
619 let mut stream = serde_json::Deserializer::from_slice(slice).into_iter();
620
621 let header: EnvelopeItemHeader = match stream.next() {
622 None => return Err(EnvelopeError::UnexpectedEof),
623 Some(Err(error)) => return Err(EnvelopeError::InvalidItemHeader(error)),
624 Some(Ok(header)) => header,
625 };
626
627 let header_end = stream.byte_offset();
629 Self::require_termination(slice, header_end)?;
630
631 let payload_start = std::cmp::min(header_end + 1, slice.len());
634 let payload_end = match header.length {
635 Some(len) => {
636 let payload_end = payload_start + len;
637 if slice.len() < payload_end {
638 return Err(EnvelopeError::UnexpectedEof);
639 }
640
641 Self::require_termination(slice, payload_end)?;
643 payload_end
644 }
645 None => match slice.get(payload_start..) {
646 Some(range) => match range.iter().position(|&b| b == b'\n') {
647 Some(relative_end) => payload_start + relative_end,
648 None => slice.len(),
649 },
650 None => slice.len(),
651 },
652 };
653
654 let payload = slice.get(payload_start..payload_end).unwrap();
655
656 let item = match header.r#type {
657 EnvelopeItemType::Event => serde_json::from_slice(payload).map(EnvelopeItem::Event),
658 EnvelopeItemType::Transaction => {
659 serde_json::from_slice(payload).map(EnvelopeItem::Transaction)
660 }
661 EnvelopeItemType::SessionUpdate => {
662 serde_json::from_slice(payload).map(EnvelopeItem::SessionUpdate)
663 }
664 EnvelopeItemType::SessionAggregates => {
665 serde_json::from_slice(payload).map(EnvelopeItem::SessionAggregates)
666 }
667 EnvelopeItemType::Attachment => Ok(EnvelopeItem::Attachment(Attachment {
668 buffer: payload.to_owned(),
669 filename: header.filename.unwrap_or_default(),
670 content_type: header.content_type,
671 ty: header.attachment_type,
672 })),
673 EnvelopeItemType::MonitorCheckIn => {
674 serde_json::from_slice(payload).map(EnvelopeItem::MonitorCheckIn)
675 }
676 EnvelopeItemType::LogsContainer => {
677 serde_json::from_slice::<ItemsSerdeWrapper<_>>(payload)
678 .map(|x| EnvelopeItem::ItemContainer(ItemContainer::Logs(x.items.into())))
679 }
680 }
681 .map_err(EnvelopeError::InvalidItemPayload)?;
682
683 Ok((item, payload_end + 1))
684 }
685
686 fn require_termination(slice: &[u8], offset: usize) -> Result<(), EnvelopeError> {
687 match slice.get(offset) {
688 Some(&b'\n') | None => Ok(()),
689 Some(_) => Err(EnvelopeError::MissingNewline),
690 }
691 }
692}
693
694impl<T> From<T> for Envelope
695where
696 T: Into<EnvelopeItem>,
697{
698 fn from(item: T) -> Self {
699 let mut envelope = Self::default();
700 envelope.add_item(item.into());
701 envelope
702 }
703}
704
705#[cfg(test)]
706mod test {
707 use std::str::FromStr;
708 use std::time::{Duration, SystemTime};
709
710 use protocol::Map;
711 use time::format_description::well_known::Rfc3339;
712 use time::OffsetDateTime;
713
714 use super::*;
715 use crate::protocol::v7::{
716 Level, MonitorCheckInStatus, MonitorConfig, MonitorSchedule, SampleRand, SessionAttributes,
717 SessionStatus, Span,
718 };
719
720 fn to_str(envelope: Envelope) -> String {
721 let mut vec = Vec::new();
722 envelope.to_writer(&mut vec).unwrap();
723 String::from_utf8_lossy(&vec).to_string()
724 }
725
726 fn timestamp(s: &str) -> SystemTime {
727 let dt = OffsetDateTime::parse(s, &Rfc3339).unwrap();
728 let secs = dt.unix_timestamp() as u64;
729 let nanos = dt.nanosecond();
730 let duration = Duration::new(secs, nanos);
731 SystemTime::UNIX_EPOCH.checked_add(duration).unwrap()
732 }
733
734 #[test]
735 fn test_empty() {
736 assert_eq!(to_str(Envelope::new()), "{}\n");
737 }
738
739 #[test]
740 fn raw_roundtrip() {
741 let buf = r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
742{"type":"event","length":74}
743{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
744"#;
745 let envelope = Envelope::from_bytes_raw(buf.to_string().into_bytes()).unwrap();
746 let serialized = to_str(envelope);
747 assert_eq!(&serialized, buf);
748
749 let random_invalid_bytes = b"oh stahp!\0\x01\x02";
750 let envelope = Envelope::from_bytes_raw(random_invalid_bytes.to_vec()).unwrap();
751 let mut serialized = Vec::new();
752 envelope.to_writer(&mut serialized).unwrap();
753 assert_eq!(&serialized, random_invalid_bytes);
754 }
755
756 #[test]
757 fn test_event() {
758 let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
759 let timestamp = timestamp("2020-07-20T14:51:14.296Z");
760 let event = Event {
761 event_id,
762 timestamp,
763 ..Default::default()
764 };
765 let envelope: Envelope = event.into();
766 assert_eq!(
767 to_str(envelope),
768 r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
769{"type":"event","length":74}
770{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
771"#
772 )
773 }
774
775 #[test]
776 fn test_session() {
777 let session_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
778 let started = timestamp("2020-07-20T14:51:14.296Z");
779 let session = SessionUpdate {
780 session_id,
781 distinct_id: Some("foo@bar.baz".to_owned()),
782 sequence: None,
783 timestamp: None,
784 started,
785 init: true,
786 duration: Some(1.234),
787 status: SessionStatus::Ok,
788 errors: 123,
789 attributes: SessionAttributes {
790 release: "foo-bar@1.2.3".into(),
791 environment: Some("production".into()),
792 ip_address: None,
793 user_agent: None,
794 },
795 };
796 let mut envelope = Envelope::new();
797 envelope.add_item(session);
798 assert_eq!(
799 to_str(envelope),
800 r#"{}
801{"type":"session","length":222}
802{"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"}}
803"#
804 )
805 }
806
807 #[test]
808 fn test_transaction() {
809 let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
810 let span_id = "d42cee9fc3e74f5c".parse().unwrap();
811 let trace_id = "335e53d614474acc9f89e632b776cc28".parse().unwrap();
812 let start_timestamp = timestamp("2020-07-20T14:51:14.296Z");
813 let spans = vec![Span {
814 span_id,
815 trace_id,
816 start_timestamp,
817 ..Default::default()
818 }];
819 let transaction = Transaction {
820 event_id,
821 start_timestamp,
822 spans,
823 ..Default::default()
824 };
825 let envelope: Envelope = transaction.into();
826 assert_eq!(
827 to_str(envelope),
828 r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
829{"type":"transaction","length":200}
830{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","start_timestamp":1595256674.296,"spans":[{"span_id":"d42cee9fc3e74f5c","trace_id":"335e53d614474acc9f89e632b776cc28","start_timestamp":1595256674.296}]}
831"#
832 )
833 }
834
835 #[test]
836 fn test_monitor_checkin() {
837 let check_in_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
838
839 let check_in = MonitorCheckIn {
840 check_in_id,
841 monitor_slug: "my-monitor".into(),
842 status: MonitorCheckInStatus::Ok,
843 duration: Some(123.4),
844 environment: Some("production".into()),
845 monitor_config: Some(MonitorConfig {
846 schedule: MonitorSchedule::Crontab {
847 value: "12 0 * * *".into(),
848 },
849 checkin_margin: Some(5),
850 max_runtime: Some(30),
851 timezone: Some("UTC".into()),
852 failure_issue_threshold: None,
853 recovery_threshold: None,
854 }),
855 };
856 let envelope: Envelope = check_in.into();
857 assert_eq!(
858 to_str(envelope),
859 r#"{}
860{"type":"check_in","length":259}
861{"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"}}
862"#
863 )
864 }
865
866 #[test]
867 fn test_monitor_checkin_with_thresholds() {
868 let check_in_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
869
870 let check_in = MonitorCheckIn {
871 check_in_id,
872 monitor_slug: "my-monitor".into(),
873 status: MonitorCheckInStatus::Ok,
874 duration: Some(123.4),
875 environment: Some("production".into()),
876 monitor_config: Some(MonitorConfig {
877 schedule: MonitorSchedule::Crontab {
878 value: "12 0 * * *".into(),
879 },
880 checkin_margin: Some(5),
881 max_runtime: Some(30),
882 timezone: Some("UTC".into()),
883 failure_issue_threshold: Some(4),
884 recovery_threshold: Some(7),
885 }),
886 };
887 let envelope: Envelope = check_in.into();
888 assert_eq!(
889 to_str(envelope),
890 r#"{}
891{"type":"check_in","length":310}
892{"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}}
893"#
894 )
895 }
896
897 #[test]
898 fn test_event_with_attachment() {
899 let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
900 let timestamp = timestamp("2020-07-20T14:51:14.296Z");
901 let event = Event {
902 event_id,
903 timestamp,
904 ..Default::default()
905 };
906 let mut envelope: Envelope = event.into();
907
908 envelope.add_item(Attachment {
909 buffer: "some content".as_bytes().to_vec(),
910 filename: "file.txt".to_string(),
911 ..Default::default()
912 });
913
914 assert_eq!(
915 to_str(envelope),
916 r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
917{"type":"event","length":74}
918{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
919{"type":"attachment","length":12,"filename":"file.txt","attachment_type":"event.attachment","content_type":"application/octet-stream"}
920some content
921"#
922 )
923 }
924
925 #[test]
926 fn test_deserialize_envelope_empty() {
927 let bytes = b"{\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}";
929 let envelope = Envelope::from_slice(bytes).unwrap();
930
931 let event_id = Uuid::from_str("9ec79c33ec9942ab8353589fcb2e04dc").unwrap();
932 assert_eq!(envelope.headers.event_id, Some(event_id));
933 assert_eq!(envelope.items().count(), 0);
934 }
935
936 #[test]
937 fn test_deserialize_envelope_empty_newline() {
938 let bytes = b"{\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n";
940 let envelope = Envelope::from_slice(bytes).unwrap();
941 assert_eq!(envelope.items().count(), 0);
942 }
943
944 #[test]
945 fn test_deserialize_envelope_empty_item_newline() {
946 let bytes = b"\
948 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
949 {\"type\":\"attachment\",\"length\":0}\n\
950 \n\
951 {\"type\":\"attachment\",\"length\":0}\n\
952 ";
953
954 let envelope = Envelope::from_slice(bytes).unwrap();
955 assert_eq!(envelope.items().count(), 2);
956
957 let mut items = envelope.items();
958
959 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
960 assert_eq!(attachment.buffer.len(), 0);
961 } else {
962 panic!("invalid item type");
963 }
964
965 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
966 assert_eq!(attachment.buffer.len(), 0);
967 } else {
968 panic!("invalid item type");
969 }
970 }
971
972 #[test]
973 fn test_deserialize_envelope_empty_item_eof() {
974 let bytes = b"\
976 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
977 {\"type\":\"attachment\",\"length\":0}\n\
978 \n\
979 {\"type\":\"attachment\",\"length\":0}\
980 ";
981
982 let envelope = Envelope::from_slice(bytes).unwrap();
983 assert_eq!(envelope.items().count(), 2);
984
985 let mut items = envelope.items();
986
987 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
988 assert_eq!(attachment.buffer.len(), 0);
989 } else {
990 panic!("invalid item type");
991 }
992
993 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
994 assert_eq!(attachment.buffer.len(), 0);
995 } else {
996 panic!("invalid item type");
997 }
998 }
999
1000 #[test]
1001 fn test_deserialize_envelope_implicit_length() {
1002 let bytes = b"\
1004 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1005 {\"type\":\"attachment\"}\n\
1006 helloworld\n\
1007 ";
1008
1009 let envelope = Envelope::from_slice(bytes).unwrap();
1010 assert_eq!(envelope.items().count(), 1);
1011
1012 let mut items = envelope.items();
1013
1014 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1015 assert_eq!(attachment.buffer.len(), 10);
1016 } else {
1017 panic!("invalid item type");
1018 }
1019 }
1020
1021 #[test]
1022 fn test_deserialize_envelope_implicit_length_eof() {
1023 let bytes = b"\
1025 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1026 {\"type\":\"attachment\"}\n\
1027 helloworld\
1028 ";
1029
1030 let envelope = Envelope::from_slice(bytes).unwrap();
1031 assert_eq!(envelope.items().count(), 1);
1032
1033 let mut items = envelope.items();
1034
1035 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1036 assert_eq!(attachment.buffer.len(), 10);
1037 } else {
1038 panic!("invalid item type");
1039 }
1040 }
1041
1042 #[test]
1043 fn test_deserialize_envelope_implicit_length_empty_eof() {
1044 let bytes = b"\
1046 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1047 {\"type\":\"attachment\"}\
1048 ";
1049
1050 let envelope = Envelope::from_slice(bytes).unwrap();
1051 assert_eq!(envelope.items().count(), 1);
1052
1053 let mut items = envelope.items();
1054
1055 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1056 assert_eq!(attachment.buffer.len(), 0);
1057 } else {
1058 panic!("invalid item type");
1059 }
1060 }
1061
1062 #[test]
1063 fn test_deserialize_envelope_multiple_items() {
1064 let bytes = b"\
1066 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
1067 {\"type\":\"attachment\",\"length\":10,\"content_type\":\"text/plain\",\"filename\":\"hello.txt\"}\n\
1068 \xef\xbb\xbfHello\r\n\n\
1069 {\"type\":\"event\",\"length\":41,\"content_type\":\"application/json\",\"filename\":\"application.log\"}\n\
1070 {\"message\":\"hello world\",\"level\":\"error\"}\n\
1071 ";
1072
1073 let envelope = Envelope::from_slice(bytes).unwrap();
1074 assert_eq!(envelope.items().count(), 2);
1075
1076 let mut items = envelope.items();
1077
1078 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
1079 assert_eq!(attachment.buffer.len(), 10);
1080 assert_eq!(attachment.buffer, b"\xef\xbb\xbfHello\r\n");
1081 assert_eq!(attachment.filename, "hello.txt");
1082 assert_eq!(attachment.content_type, Some("text/plain".to_string()));
1083 } else {
1084 panic!("invalid item type");
1085 }
1086
1087 if let EnvelopeItem::Event(event) = items.next().unwrap() {
1088 assert_eq!(event.message, Some("hello world".to_string()));
1089 assert_eq!(event.level, Level::Error);
1090 } else {
1091 panic!("invalid item type");
1092 }
1093 }
1094
1095 #[test]
1096 fn test_all_envelope_headers_roundtrip() {
1097 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"}}
1098{"type":"event","length":74}
1099{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
1100"#;
1101
1102 let envelope = Envelope::from_slice(bytes);
1103 assert!(envelope.is_ok());
1104 let envelope = envelope.unwrap();
1105 let serialized = to_str(envelope);
1106 assert_eq!(bytes, serialized.as_bytes());
1107 }
1108
1109 #[test]
1110 fn test_sample_rand_rounding() {
1111 let envelope = Envelope::new().with_headers(
1112 EnvelopeHeaders::new().with_trace(
1113 DynamicSamplingContext::new()
1114 .with_sample_rand(SampleRand::try_from(0.999_999_9).unwrap()),
1115 ),
1116 );
1117 let expected = br#"{"trace":{"sample_rand":"0.999999"}}
1118"#;
1119
1120 let serialized = to_str(envelope);
1121 assert_eq!(expected, serialized.as_bytes());
1122 }
1123
1124 #[test]
1126 fn test_deserialize_serialized() {
1127 let event = Event {
1129 event_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap(),
1130 timestamp: timestamp("2020-07-20T14:51:14.296Z"),
1131 ..Default::default()
1132 };
1133
1134 let transaction = Transaction {
1136 event_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9d").unwrap(),
1137 start_timestamp: timestamp("2020-07-20T14:51:14.296Z"),
1138 spans: vec![Span {
1139 span_id: "d42cee9fc3e74f5c".parse().unwrap(),
1140 trace_id: "335e53d614474acc9f89e632b776cc28".parse().unwrap(),
1141 start_timestamp: timestamp("2020-07-20T14:51:14.296Z"),
1142 ..Default::default()
1143 }],
1144 ..Default::default()
1145 };
1146
1147 let session = SessionUpdate {
1149 session_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap(),
1150 distinct_id: Some("foo@bar.baz".to_owned()),
1151 sequence: None,
1152 timestamp: None,
1153 started: timestamp("2020-07-20T14:51:14.296Z"),
1154 init: true,
1155 duration: Some(1.234),
1156 status: SessionStatus::Ok,
1157 errors: 123,
1158 attributes: SessionAttributes {
1159 release: "foo-bar@1.2.3".into(),
1160 environment: Some("production".into()),
1161 ip_address: None,
1162 user_agent: None,
1163 },
1164 };
1165
1166 let attachment = Attachment {
1168 buffer: "some content".as_bytes().to_vec(),
1169 filename: "file.txt".to_string(),
1170 ..Default::default()
1171 };
1172
1173 let mut attributes = Map::new();
1174 attributes.insert("key".into(), "value".into());
1175 attributes.insert("num".into(), 10.into());
1176 attributes.insert("val".into(), 10.2.into());
1177 attributes.insert("bool".into(), false.into());
1178 let mut attributes_2 = attributes.clone();
1179 attributes_2.insert("more".into(), true.into());
1180 let logs: EnvelopeItem = vec![
1181 Log {
1182 level: protocol::LogLevel::Warn,
1183 body: "test".to_owned(),
1184 trace_id: Some("335e53d614474acc9f89e632b776cc28".parse().unwrap()),
1185 timestamp: timestamp("2022-07-25T14:51:14.296Z"),
1186 severity_number: Some(1.try_into().unwrap()),
1187 attributes,
1188 },
1189 Log {
1190 level: protocol::LogLevel::Error,
1191 body: "a body".to_owned(),
1192 trace_id: Some("332253d614472a2c9f89e232b7762c28".parse().unwrap()),
1193 timestamp: timestamp("2021-07-21T14:51:14.296Z"),
1194 severity_number: Some(1.try_into().unwrap()),
1195 attributes: attributes_2,
1196 },
1197 ]
1198 .into();
1199
1200 let mut envelope: Envelope = Envelope::new();
1201 envelope.add_item(event);
1202 envelope.add_item(transaction);
1203 envelope.add_item(session);
1204 envelope.add_item(attachment);
1205 envelope.add_item(logs);
1206
1207 let serialized = to_str(envelope);
1208 let deserialized = Envelope::from_slice(serialized.as_bytes()).unwrap();
1209 assert_eq!(serialized, to_str(deserialized))
1210 }
1211}