1use std::{io::Write, path::Path};
2
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5use uuid::Uuid;
6
7use super::v7 as protocol;
8
9use protocol::{
10 Attachment, AttachmentType, Event, Log, MonitorCheckIn, SessionAggregates, SessionUpdate,
11 Transaction,
12};
13
14#[derive(Debug, Error)]
16pub enum EnvelopeError {
17 #[error("unexpected end of file")]
19 UnexpectedEof,
20 #[error("missing envelope header")]
22 MissingHeader,
23 #[error("missing item header")]
25 MissingItemHeader,
26 #[error("missing newline after header or payload")]
28 MissingNewline,
29 #[error("invalid envelope header")]
31 InvalidHeader(#[source] serde_json::Error),
32 #[error("invalid item header")]
34 InvalidItemHeader(#[source] serde_json::Error),
35 #[error("invalid item payload")]
37 InvalidItemPayload(#[source] serde_json::Error),
38}
39
40#[derive(Deserialize)]
41struct EnvelopeHeader {
42 event_id: Option<Uuid>,
43}
44
45#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
47#[non_exhaustive]
48enum EnvelopeItemType {
49 #[serde(rename = "event")]
51 Event,
52 #[serde(rename = "session")]
54 SessionUpdate,
55 #[serde(rename = "sessions")]
57 SessionAggregates,
58 #[serde(rename = "transaction")]
60 Transaction,
61 #[serde(rename = "attachment")]
63 Attachment,
64 #[serde(rename = "check_in")]
66 MonitorCheckIn,
67 #[serde(rename = "log")]
69 LogsContainer,
70}
71
72#[derive(Clone, Debug, Deserialize)]
74struct EnvelopeItemHeader {
75 r#type: EnvelopeItemType,
76 length: Option<usize>,
77 content_type: Option<String>,
79 filename: Option<String>,
81 attachment_type: Option<AttachmentType>,
82}
83
84#[derive(Clone, Debug, PartialEq)]
89#[non_exhaustive]
90#[allow(clippy::large_enum_variant)]
91pub enum EnvelopeItem {
92 Event(Event<'static>),
97 SessionUpdate(SessionUpdate<'static>),
102 SessionAggregates(SessionAggregates<'static>),
107 Transaction(Transaction<'static>),
112 Attachment(Attachment),
117 MonitorCheckIn(MonitorCheckIn),
119 ItemContainer(ItemContainer),
121 Raw,
123 }
126
127#[derive(Clone, Debug, PartialEq)]
131#[non_exhaustive]
132pub enum ItemContainer {
133 Logs(Vec<Log>),
135}
136
137#[allow(clippy::len_without_is_empty, reason = "is_empty is not needed")]
138impl ItemContainer {
139 pub fn len(&self) -> usize {
141 match self {
142 Self::Logs(logs) => logs.len(),
143 }
144 }
145
146 pub fn ty(&self) -> &'static str {
148 match self {
149 Self::Logs(_) => "log",
150 }
151 }
152
153 pub fn content_type(&self) -> &'static str {
155 match self {
156 Self::Logs(_) => "application/vnd.sentry.items.log+json",
157 }
158 }
159}
160
161impl From<Vec<Log>> for ItemContainer {
162 fn from(logs: Vec<Log>) -> Self {
163 Self::Logs(logs)
164 }
165}
166
167#[derive(Serialize)]
168struct LogsSerializationWrapper<'a> {
169 items: &'a [Log],
170}
171
172#[derive(Deserialize)]
173struct LogsDeserializationWrapper {
174 items: Vec<Log>,
175}
176
177impl From<Event<'static>> for EnvelopeItem {
178 fn from(event: Event<'static>) -> Self {
179 EnvelopeItem::Event(event)
180 }
181}
182
183impl From<SessionUpdate<'static>> for EnvelopeItem {
184 fn from(session: SessionUpdate<'static>) -> Self {
185 EnvelopeItem::SessionUpdate(session)
186 }
187}
188
189impl From<SessionAggregates<'static>> for EnvelopeItem {
190 fn from(aggregates: SessionAggregates<'static>) -> Self {
191 EnvelopeItem::SessionAggregates(aggregates)
192 }
193}
194
195impl From<Transaction<'static>> for EnvelopeItem {
196 fn from(transaction: Transaction<'static>) -> Self {
197 EnvelopeItem::Transaction(transaction)
198 }
199}
200
201impl From<Attachment> for EnvelopeItem {
202 fn from(attachment: Attachment) -> Self {
203 EnvelopeItem::Attachment(attachment)
204 }
205}
206
207impl From<MonitorCheckIn> for EnvelopeItem {
208 fn from(check_in: MonitorCheckIn) -> Self {
209 EnvelopeItem::MonitorCheckIn(check_in)
210 }
211}
212
213impl From<ItemContainer> for EnvelopeItem {
214 fn from(container: ItemContainer) -> Self {
215 EnvelopeItem::ItemContainer(container)
216 }
217}
218
219impl From<Vec<Log>> for EnvelopeItem {
220 fn from(logs: Vec<Log>) -> Self {
221 EnvelopeItem::ItemContainer(logs.into())
222 }
223}
224
225#[derive(Clone)]
227pub struct EnvelopeItemIter<'s> {
228 inner: std::slice::Iter<'s, EnvelopeItem>,
229}
230
231impl<'s> Iterator for EnvelopeItemIter<'s> {
232 type Item = &'s EnvelopeItem;
233
234 fn next(&mut self) -> Option<Self::Item> {
235 self.inner.next()
236 }
237}
238
239#[derive(Debug, Clone, PartialEq)]
244enum Items {
245 EnvelopeItems(Vec<EnvelopeItem>),
246 Raw(Vec<u8>),
247}
248
249impl Default for Items {
250 fn default() -> Self {
251 Self::EnvelopeItems(Default::default())
252 }
253}
254
255impl Items {
256 fn is_empty(&self) -> bool {
257 match self {
258 Items::EnvelopeItems(items) => items.is_empty(),
259 Items::Raw(bytes) => bytes.is_empty(),
260 }
261 }
262}
263
264#[derive(Clone, Default, Debug, PartialEq)]
273pub struct Envelope {
274 event_id: Option<Uuid>,
275 items: Items,
276}
277
278impl Envelope {
279 pub fn new() -> Envelope {
281 Default::default()
282 }
283
284 pub fn add_item<I>(&mut self, item: I)
286 where
287 I: Into<EnvelopeItem>,
288 {
289 let item = item.into();
290
291 let Items::EnvelopeItems(ref mut items) = self.items else {
292 if item != EnvelopeItem::Raw {
293 eprintln!(
294 "WARNING: This envelope contains raw items. Adding an item is not supported."
295 );
296 }
297 return;
298 };
299
300 if self.event_id.is_none() {
301 if let EnvelopeItem::Event(ref event) = item {
302 self.event_id = Some(event.event_id);
303 } else if let EnvelopeItem::Transaction(ref transaction) = item {
304 self.event_id = Some(transaction.event_id);
305 }
306 }
307 items.push(item);
308 }
309
310 pub fn items(&self) -> EnvelopeItemIter<'_> {
312 let inner = match &self.items {
313 Items::EnvelopeItems(items) => items.iter(),
314 Items::Raw(_) => [].iter(),
315 };
316
317 EnvelopeItemIter { inner }
318 }
319
320 pub fn uuid(&self) -> Option<&Uuid> {
322 self.event_id.as_ref()
323 }
324
325 pub fn event(&self) -> Option<&Event<'static>> {
329 let Items::EnvelopeItems(ref items) = self.items else {
330 return None;
331 };
332
333 items.iter().find_map(|item| match item {
334 EnvelopeItem::Event(event) => Some(event),
335 _ => None,
336 })
337 }
338
339 pub fn filter<P>(self, mut predicate: P) -> Option<Self>
348 where
349 P: FnMut(&EnvelopeItem) -> bool,
350 {
351 let Items::EnvelopeItems(items) = self.items else {
352 return if predicate(&EnvelopeItem::Raw) {
353 Some(self)
354 } else {
355 None
356 };
357 };
358
359 let mut filtered = Envelope::new();
360 for item in items {
361 if predicate(&item) {
362 filtered.add_item(item);
363 }
364 }
365
366 if filtered.uuid().is_none() {
369 if let Items::EnvelopeItems(ref mut items) = filtered.items {
370 items.retain(|item| !matches!(item, EnvelopeItem::Attachment(..)))
371 }
372 }
373
374 if filtered.items.is_empty() {
375 None
376 } else {
377 Some(filtered)
378 }
379 }
380
381 pub fn to_writer<W>(&self, mut writer: W) -> std::io::Result<()>
385 where
386 W: Write,
387 {
388 let items = match &self.items {
389 Items::Raw(bytes) => return writer.write_all(bytes).map(|_| ()),
390 Items::EnvelopeItems(items) => items,
391 };
392
393 let event_id = self.uuid();
395 match event_id {
396 Some(uuid) => writeln!(writer, r#"{{"event_id":"{uuid}"}}"#)?,
397 _ => writeln!(writer, "{{}}")?,
398 }
399
400 let mut item_buf = Vec::new();
401 for item in items {
403 match item {
405 EnvelopeItem::Event(event) => serde_json::to_writer(&mut item_buf, event)?,
406 EnvelopeItem::SessionUpdate(session) => {
407 serde_json::to_writer(&mut item_buf, session)?
408 }
409 EnvelopeItem::SessionAggregates(aggregates) => {
410 serde_json::to_writer(&mut item_buf, aggregates)?
411 }
412 EnvelopeItem::Transaction(transaction) => {
413 serde_json::to_writer(&mut item_buf, transaction)?
414 }
415 EnvelopeItem::Attachment(attachment) => {
416 attachment.to_writer(&mut writer)?;
417 writeln!(writer)?;
418 continue;
419 }
420 EnvelopeItem::MonitorCheckIn(check_in) => {
421 serde_json::to_writer(&mut item_buf, check_in)?
422 }
423 EnvelopeItem::ItemContainer(container) => match container {
424 ItemContainer::Logs(logs) => {
425 let wrapper = LogsSerializationWrapper { items: logs };
426 serde_json::to_writer(&mut item_buf, &wrapper)?
427 }
428 },
429 EnvelopeItem::Raw => {
430 continue;
431 }
432 }
433 let item_type = match item {
434 EnvelopeItem::Event(_) => "event",
435 EnvelopeItem::SessionUpdate(_) => "session",
436 EnvelopeItem::SessionAggregates(_) => "sessions",
437 EnvelopeItem::Transaction(_) => "transaction",
438 EnvelopeItem::MonitorCheckIn(_) => "check_in",
439 EnvelopeItem::ItemContainer(container) => container.ty(),
440 EnvelopeItem::Attachment(_) | EnvelopeItem::Raw => unreachable!(),
441 };
442
443 if let EnvelopeItem::ItemContainer(container) = item {
444 writeln!(
445 writer,
446 r#"{{"type":"{}","item_count":{},"content_type":"{}"}}"#,
447 item_type,
448 container.len(),
449 container.content_type()
450 )?;
451 } else {
452 writeln!(
453 writer,
454 r#"{{"type":"{}","length":{}}}"#,
455 item_type,
456 item_buf.len()
457 )?;
458 }
459 writer.write_all(&item_buf)?;
460 writeln!(writer)?;
461 item_buf.clear();
462 }
463
464 Ok(())
465 }
466
467 pub fn from_slice(slice: &[u8]) -> Result<Envelope, EnvelopeError> {
469 let (header, offset) = Self::parse_header(slice)?;
470 let items = Self::parse_items(slice, offset)?;
471
472 let mut envelope = Envelope {
473 event_id: header.event_id,
474 ..Default::default()
475 };
476
477 for item in items {
478 envelope.add_item(item);
479 }
480
481 Ok(envelope)
482 }
483
484 pub fn from_bytes_raw(bytes: Vec<u8>) -> Result<Self, EnvelopeError> {
486 Ok(Self {
487 event_id: None,
488 items: Items::Raw(bytes),
489 })
490 }
491
492 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Envelope, EnvelopeError> {
494 let bytes = std::fs::read(path).map_err(|_| EnvelopeError::UnexpectedEof)?;
495 Envelope::from_slice(&bytes)
496 }
497
498 pub fn from_path_raw<P: AsRef<Path>>(path: P) -> Result<Self, EnvelopeError> {
503 let bytes = std::fs::read(path).map_err(|_| EnvelopeError::UnexpectedEof)?;
504 Self::from_bytes_raw(bytes)
505 }
506
507 fn parse_header(slice: &[u8]) -> Result<(EnvelopeHeader, usize), EnvelopeError> {
508 let mut stream = serde_json::Deserializer::from_slice(slice).into_iter();
509
510 let header: EnvelopeHeader = match stream.next() {
511 None => return Err(EnvelopeError::MissingHeader),
512 Some(Err(error)) => return Err(EnvelopeError::InvalidHeader(error)),
513 Some(Ok(header)) => header,
514 };
515
516 Self::require_termination(slice, stream.byte_offset())?;
518
519 Ok((header, stream.byte_offset() + 1))
520 }
521
522 fn parse_items(slice: &[u8], mut offset: usize) -> Result<Vec<EnvelopeItem>, EnvelopeError> {
523 let mut items = Vec::new();
524
525 while offset < slice.len() {
526 let bytes = slice
527 .get(offset..)
528 .ok_or(EnvelopeError::MissingItemHeader)?;
529 let (item, item_size) = Self::parse_item(bytes)?;
530 offset += item_size;
531 items.push(item);
532 }
533
534 Ok(items)
535 }
536
537 fn parse_item(slice: &[u8]) -> Result<(EnvelopeItem, usize), EnvelopeError> {
538 let mut stream = serde_json::Deserializer::from_slice(slice).into_iter();
539
540 let header: EnvelopeItemHeader = match stream.next() {
541 None => return Err(EnvelopeError::UnexpectedEof),
542 Some(Err(error)) => return Err(EnvelopeError::InvalidItemHeader(error)),
543 Some(Ok(header)) => header,
544 };
545
546 let header_end = stream.byte_offset();
548 Self::require_termination(slice, header_end)?;
549
550 let payload_start = std::cmp::min(header_end + 1, slice.len());
553 let payload_end = match header.length {
554 Some(len) => {
555 let payload_end = payload_start + len;
556 if slice.len() < payload_end {
557 return Err(EnvelopeError::UnexpectedEof);
558 }
559
560 Self::require_termination(slice, payload_end)?;
562 payload_end
563 }
564 None => match slice.get(payload_start..) {
565 Some(range) => match range.iter().position(|&b| b == b'\n') {
566 Some(relative_end) => payload_start + relative_end,
567 None => slice.len(),
568 },
569 None => slice.len(),
570 },
571 };
572
573 let payload = slice.get(payload_start..payload_end).unwrap();
574
575 let item = match header.r#type {
576 EnvelopeItemType::Event => serde_json::from_slice(payload).map(EnvelopeItem::Event),
577 EnvelopeItemType::Transaction => {
578 serde_json::from_slice(payload).map(EnvelopeItem::Transaction)
579 }
580 EnvelopeItemType::SessionUpdate => {
581 serde_json::from_slice(payload).map(EnvelopeItem::SessionUpdate)
582 }
583 EnvelopeItemType::SessionAggregates => {
584 serde_json::from_slice(payload).map(EnvelopeItem::SessionAggregates)
585 }
586 EnvelopeItemType::Attachment => Ok(EnvelopeItem::Attachment(Attachment {
587 buffer: payload.to_owned(),
588 filename: header.filename.unwrap_or_default(),
589 content_type: header.content_type,
590 ty: header.attachment_type,
591 })),
592 EnvelopeItemType::MonitorCheckIn => {
593 serde_json::from_slice(payload).map(EnvelopeItem::MonitorCheckIn)
594 }
595 EnvelopeItemType::LogsContainer => {
596 serde_json::from_slice::<LogsDeserializationWrapper>(payload)
597 .map(|x| EnvelopeItem::ItemContainer(ItemContainer::Logs(x.items)))
598 }
599 }
600 .map_err(EnvelopeError::InvalidItemPayload)?;
601
602 Ok((item, payload_end + 1))
603 }
604
605 fn require_termination(slice: &[u8], offset: usize) -> Result<(), EnvelopeError> {
606 match slice.get(offset) {
607 Some(&b'\n') | None => Ok(()),
608 Some(_) => Err(EnvelopeError::MissingNewline),
609 }
610 }
611}
612
613impl<T> From<T> for Envelope
614where
615 T: Into<EnvelopeItem>,
616{
617 fn from(item: T) -> Self {
618 let mut envelope = Self::default();
619 envelope.add_item(item.into());
620 envelope
621 }
622}
623
624#[cfg(test)]
625mod test {
626 use std::str::FromStr;
627 use std::time::{Duration, SystemTime};
628
629 use protocol::Map;
630 use time::format_description::well_known::Rfc3339;
631 use time::OffsetDateTime;
632
633 use super::*;
634 use crate::protocol::v7::{
635 Level, MonitorCheckInStatus, MonitorConfig, MonitorSchedule, SessionAttributes,
636 SessionStatus, Span,
637 };
638
639 fn to_str(envelope: Envelope) -> String {
640 let mut vec = Vec::new();
641 envelope.to_writer(&mut vec).unwrap();
642 String::from_utf8_lossy(&vec).to_string()
643 }
644
645 fn timestamp(s: &str) -> SystemTime {
646 let dt = OffsetDateTime::parse(s, &Rfc3339).unwrap();
647 let secs = dt.unix_timestamp() as u64;
648 let nanos = dt.nanosecond();
649 let duration = Duration::new(secs, nanos);
650 SystemTime::UNIX_EPOCH.checked_add(duration).unwrap()
651 }
652
653 #[test]
654 fn test_empty() {
655 assert_eq!(to_str(Envelope::new()), "{}\n");
656 }
657
658 #[test]
659 fn raw_roundtrip() {
660 let buf = r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
661{"type":"event","length":74}
662{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
663"#;
664 let envelope = Envelope::from_bytes_raw(buf.to_string().into_bytes()).unwrap();
665 let serialized = to_str(envelope);
666 assert_eq!(&serialized, buf);
667
668 let random_invalid_bytes = b"oh stahp!\0\x01\x02";
669 let envelope = Envelope::from_bytes_raw(random_invalid_bytes.to_vec()).unwrap();
670 let mut serialized = Vec::new();
671 envelope.to_writer(&mut serialized).unwrap();
672 assert_eq!(&serialized, random_invalid_bytes);
673 }
674
675 #[test]
676 fn test_event() {
677 let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
678 let timestamp = timestamp("2020-07-20T14:51:14.296Z");
679 let event = Event {
680 event_id,
681 timestamp,
682 ..Default::default()
683 };
684 let envelope: Envelope = event.into();
685 assert_eq!(
686 to_str(envelope),
687 r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
688{"type":"event","length":74}
689{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
690"#
691 )
692 }
693
694 #[test]
695 fn test_session() {
696 let session_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
697 let started = timestamp("2020-07-20T14:51:14.296Z");
698 let session = SessionUpdate {
699 session_id,
700 distinct_id: Some("foo@bar.baz".to_owned()),
701 sequence: None,
702 timestamp: None,
703 started,
704 init: true,
705 duration: Some(1.234),
706 status: SessionStatus::Ok,
707 errors: 123,
708 attributes: SessionAttributes {
709 release: "foo-bar@1.2.3".into(),
710 environment: Some("production".into()),
711 ip_address: None,
712 user_agent: None,
713 },
714 };
715 let mut envelope = Envelope::new();
716 envelope.add_item(session);
717 assert_eq!(
718 to_str(envelope),
719 r#"{}
720{"type":"session","length":222}
721{"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"}}
722"#
723 )
724 }
725
726 #[test]
727 fn test_transaction() {
728 let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
729 let span_id = "d42cee9fc3e74f5c".parse().unwrap();
730 let trace_id = "335e53d614474acc9f89e632b776cc28".parse().unwrap();
731 let start_timestamp = timestamp("2020-07-20T14:51:14.296Z");
732 let spans = vec![Span {
733 span_id,
734 trace_id,
735 start_timestamp,
736 ..Default::default()
737 }];
738 let transaction = Transaction {
739 event_id,
740 start_timestamp,
741 spans,
742 ..Default::default()
743 };
744 let envelope: Envelope = transaction.into();
745 assert_eq!(
746 to_str(envelope),
747 r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
748{"type":"transaction","length":200}
749{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","start_timestamp":1595256674.296,"spans":[{"span_id":"d42cee9fc3e74f5c","trace_id":"335e53d614474acc9f89e632b776cc28","start_timestamp":1595256674.296}]}
750"#
751 )
752 }
753
754 #[test]
755 fn test_monitor_checkin() {
756 let check_in_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
757
758 let check_in = MonitorCheckIn {
759 check_in_id,
760 monitor_slug: "my-monitor".into(),
761 status: MonitorCheckInStatus::Ok,
762 duration: Some(123.4),
763 environment: Some("production".into()),
764 monitor_config: Some(MonitorConfig {
765 schedule: MonitorSchedule::Crontab {
766 value: "12 0 * * *".into(),
767 },
768 checkin_margin: Some(5),
769 max_runtime: Some(30),
770 timezone: Some("UTC".into()),
771 failure_issue_threshold: None,
772 recovery_threshold: None,
773 }),
774 };
775 let envelope: Envelope = check_in.into();
776 assert_eq!(
777 to_str(envelope),
778 r#"{}
779{"type":"check_in","length":259}
780{"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"}}
781"#
782 )
783 }
784
785 #[test]
786 fn test_monitor_checkin_with_thresholds() {
787 let check_in_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
788
789 let check_in = MonitorCheckIn {
790 check_in_id,
791 monitor_slug: "my-monitor".into(),
792 status: MonitorCheckInStatus::Ok,
793 duration: Some(123.4),
794 environment: Some("production".into()),
795 monitor_config: Some(MonitorConfig {
796 schedule: MonitorSchedule::Crontab {
797 value: "12 0 * * *".into(),
798 },
799 checkin_margin: Some(5),
800 max_runtime: Some(30),
801 timezone: Some("UTC".into()),
802 failure_issue_threshold: Some(4),
803 recovery_threshold: Some(7),
804 }),
805 };
806 let envelope: Envelope = check_in.into();
807 assert_eq!(
808 to_str(envelope),
809 r#"{}
810{"type":"check_in","length":310}
811{"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}}
812"#
813 )
814 }
815
816 #[test]
817 fn test_event_with_attachment() {
818 let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
819 let timestamp = timestamp("2020-07-20T14:51:14.296Z");
820 let event = Event {
821 event_id,
822 timestamp,
823 ..Default::default()
824 };
825 let mut envelope: Envelope = event.into();
826
827 envelope.add_item(Attachment {
828 buffer: "some content".as_bytes().to_vec(),
829 filename: "file.txt".to_string(),
830 ..Default::default()
831 });
832
833 assert_eq!(
834 to_str(envelope),
835 r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
836{"type":"event","length":74}
837{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
838{"type":"attachment","length":12,"filename":"file.txt","attachment_type":"event.attachment","content_type":"application/octet-stream"}
839some content
840"#
841 )
842 }
843
844 #[test]
845 fn test_deserialize_envelope_empty() {
846 let bytes = b"{\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}";
848 let envelope = Envelope::from_slice(bytes).unwrap();
849
850 let event_id = Uuid::from_str("9ec79c33ec9942ab8353589fcb2e04dc").unwrap();
851 assert_eq!(envelope.event_id, Some(event_id));
852 assert_eq!(envelope.items().count(), 0);
853 }
854
855 #[test]
856 fn test_deserialize_envelope_empty_newline() {
857 let bytes = b"{\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n";
859 let envelope = Envelope::from_slice(bytes).unwrap();
860 assert_eq!(envelope.items().count(), 0);
861 }
862
863 #[test]
864 fn test_deserialize_envelope_empty_item_newline() {
865 let bytes = b"\
867 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
868 {\"type\":\"attachment\",\"length\":0}\n\
869 \n\
870 {\"type\":\"attachment\",\"length\":0}\n\
871 ";
872
873 let envelope = Envelope::from_slice(bytes).unwrap();
874 assert_eq!(envelope.items().count(), 2);
875
876 let mut items = envelope.items();
877
878 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
879 assert_eq!(attachment.buffer.len(), 0);
880 } else {
881 panic!("invalid item type");
882 }
883
884 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
885 assert_eq!(attachment.buffer.len(), 0);
886 } else {
887 panic!("invalid item type");
888 }
889 }
890
891 #[test]
892 fn test_deserialize_envelope_empty_item_eof() {
893 let bytes = b"\
895 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
896 {\"type\":\"attachment\",\"length\":0}\n\
897 \n\
898 {\"type\":\"attachment\",\"length\":0}\
899 ";
900
901 let envelope = Envelope::from_slice(bytes).unwrap();
902 assert_eq!(envelope.items().count(), 2);
903
904 let mut items = envelope.items();
905
906 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
907 assert_eq!(attachment.buffer.len(), 0);
908 } else {
909 panic!("invalid item type");
910 }
911
912 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
913 assert_eq!(attachment.buffer.len(), 0);
914 } else {
915 panic!("invalid item type");
916 }
917 }
918
919 #[test]
920 fn test_deserialize_envelope_implicit_length() {
921 let bytes = b"\
923 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
924 {\"type\":\"attachment\"}\n\
925 helloworld\n\
926 ";
927
928 let envelope = Envelope::from_slice(bytes).unwrap();
929 assert_eq!(envelope.items().count(), 1);
930
931 let mut items = envelope.items();
932
933 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
934 assert_eq!(attachment.buffer.len(), 10);
935 } else {
936 panic!("invalid item type");
937 }
938 }
939
940 #[test]
941 fn test_deserialize_envelope_implicit_length_eof() {
942 let bytes = b"\
944 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
945 {\"type\":\"attachment\"}\n\
946 helloworld\
947 ";
948
949 let envelope = Envelope::from_slice(bytes).unwrap();
950 assert_eq!(envelope.items().count(), 1);
951
952 let mut items = envelope.items();
953
954 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
955 assert_eq!(attachment.buffer.len(), 10);
956 } else {
957 panic!("invalid item type");
958 }
959 }
960
961 #[test]
962 fn test_deserialize_envelope_implicit_length_empty_eof() {
963 let bytes = b"\
965 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
966 {\"type\":\"attachment\"}\
967 ";
968
969 let envelope = Envelope::from_slice(bytes).unwrap();
970 assert_eq!(envelope.items().count(), 1);
971
972 let mut items = envelope.items();
973
974 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
975 assert_eq!(attachment.buffer.len(), 0);
976 } else {
977 panic!("invalid item type");
978 }
979 }
980
981 #[test]
982 fn test_deserialize_envelope_multiple_items() {
983 let bytes = b"\
985 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
986 {\"type\":\"attachment\",\"length\":10,\"content_type\":\"text/plain\",\"filename\":\"hello.txt\"}\n\
987 \xef\xbb\xbfHello\r\n\n\
988 {\"type\":\"event\",\"length\":41,\"content_type\":\"application/json\",\"filename\":\"application.log\"}\n\
989 {\"message\":\"hello world\",\"level\":\"error\"}\n\
990 ";
991
992 let envelope = Envelope::from_slice(bytes).unwrap();
993 assert_eq!(envelope.items().count(), 2);
994
995 let mut items = envelope.items();
996
997 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
998 assert_eq!(attachment.buffer.len(), 10);
999 assert_eq!(attachment.buffer, b"\xef\xbb\xbfHello\r\n");
1000 assert_eq!(attachment.filename, "hello.txt");
1001 assert_eq!(attachment.content_type, Some("text/plain".to_string()));
1002 } else {
1003 panic!("invalid item type");
1004 }
1005
1006 if let EnvelopeItem::Event(event) = items.next().unwrap() {
1007 assert_eq!(event.message, Some("hello world".to_string()));
1008 assert_eq!(event.level, Level::Error);
1009 } else {
1010 panic!("invalid item type");
1011 }
1012 }
1013
1014 #[test]
1016 fn test_deserialize_serialized() {
1017 let event = Event {
1019 event_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap(),
1020 timestamp: timestamp("2020-07-20T14:51:14.296Z"),
1021 ..Default::default()
1022 };
1023
1024 let transaction = Transaction {
1026 event_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9d").unwrap(),
1027 start_timestamp: timestamp("2020-07-20T14:51:14.296Z"),
1028 spans: vec![Span {
1029 span_id: "d42cee9fc3e74f5c".parse().unwrap(),
1030 trace_id: "335e53d614474acc9f89e632b776cc28".parse().unwrap(),
1031 start_timestamp: timestamp("2020-07-20T14:51:14.296Z"),
1032 ..Default::default()
1033 }],
1034 ..Default::default()
1035 };
1036
1037 let session = SessionUpdate {
1039 session_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap(),
1040 distinct_id: Some("foo@bar.baz".to_owned()),
1041 sequence: None,
1042 timestamp: None,
1043 started: timestamp("2020-07-20T14:51:14.296Z"),
1044 init: true,
1045 duration: Some(1.234),
1046 status: SessionStatus::Ok,
1047 errors: 123,
1048 attributes: SessionAttributes {
1049 release: "foo-bar@1.2.3".into(),
1050 environment: Some("production".into()),
1051 ip_address: None,
1052 user_agent: None,
1053 },
1054 };
1055
1056 let attachment = Attachment {
1058 buffer: "some content".as_bytes().to_vec(),
1059 filename: "file.txt".to_string(),
1060 ..Default::default()
1061 };
1062
1063 let mut attributes = Map::new();
1064 attributes.insert("key".into(), "value".into());
1065 attributes.insert("num".into(), 10.into());
1066 attributes.insert("val".into(), 10.2.into());
1067 attributes.insert("bool".into(), false.into());
1068 let mut attributes_2 = attributes.clone();
1069 attributes_2.insert("more".into(), true.into());
1070 let logs: EnvelopeItem = vec![
1071 Log {
1072 level: protocol::LogLevel::Warn,
1073 body: "test".to_owned(),
1074 trace_id: Some("335e53d614474acc9f89e632b776cc28".parse().unwrap()),
1075 timestamp: timestamp("2022-07-25T14:51:14.296Z"),
1076 severity_number: Some(1.try_into().unwrap()),
1077 attributes,
1078 },
1079 Log {
1080 level: protocol::LogLevel::Error,
1081 body: "a body".to_owned(),
1082 trace_id: Some("332253d614472a2c9f89e232b7762c28".parse().unwrap()),
1083 timestamp: timestamp("2021-07-21T14:51:14.296Z"),
1084 severity_number: Some(1.try_into().unwrap()),
1085 attributes: attributes_2,
1086 },
1087 ]
1088 .into();
1089
1090 let mut envelope: Envelope = Envelope::new();
1091
1092 envelope.add_item(event);
1093 envelope.add_item(transaction);
1094 envelope.add_item(session);
1095 envelope.add_item(attachment);
1096 envelope.add_item(logs);
1097
1098 let serialized = to_str(envelope);
1099 let deserialized = Envelope::from_slice(serialized.as_bytes()).unwrap();
1100 assert_eq!(serialized, to_str(deserialized))
1101 }
1102}