1use std::{io::Write, path::Path};
2
3use serde::Deserialize;
4use thiserror::Error;
5use uuid::Uuid;
6
7use super::v7 as protocol;
8
9use protocol::{
10 Attachment, AttachmentType, Event, 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}
68
69#[derive(Clone, Debug, Deserialize)]
71struct EnvelopeItemHeader {
72 r#type: EnvelopeItemType,
73 length: Option<usize>,
74 filename: Option<String>,
76 attachment_type: Option<AttachmentType>,
77 content_type: Option<String>,
78}
79
80#[derive(Clone, Debug, PartialEq)]
85#[non_exhaustive]
86#[allow(clippy::large_enum_variant)]
87pub enum EnvelopeItem {
88 Event(Event<'static>),
93 SessionUpdate(SessionUpdate<'static>),
98 SessionAggregates(SessionAggregates<'static>),
103 Transaction(Transaction<'static>),
108 Attachment(Attachment),
113 MonitorCheckIn(MonitorCheckIn),
115 Raw,
117 }
120
121impl From<Event<'static>> for EnvelopeItem {
122 fn from(event: Event<'static>) -> Self {
123 EnvelopeItem::Event(event)
124 }
125}
126
127impl From<SessionUpdate<'static>> for EnvelopeItem {
128 fn from(session: SessionUpdate<'static>) -> Self {
129 EnvelopeItem::SessionUpdate(session)
130 }
131}
132
133impl From<SessionAggregates<'static>> for EnvelopeItem {
134 fn from(aggregates: SessionAggregates<'static>) -> Self {
135 EnvelopeItem::SessionAggregates(aggregates)
136 }
137}
138
139impl From<Transaction<'static>> for EnvelopeItem {
140 fn from(transaction: Transaction<'static>) -> Self {
141 EnvelopeItem::Transaction(transaction)
142 }
143}
144
145impl From<Attachment> for EnvelopeItem {
146 fn from(attachment: Attachment) -> Self {
147 EnvelopeItem::Attachment(attachment)
148 }
149}
150
151impl From<MonitorCheckIn> for EnvelopeItem {
152 fn from(check_in: MonitorCheckIn) -> Self {
153 EnvelopeItem::MonitorCheckIn(check_in)
154 }
155}
156
157#[derive(Clone)]
159pub struct EnvelopeItemIter<'s> {
160 inner: std::slice::Iter<'s, EnvelopeItem>,
161}
162
163impl<'s> Iterator for EnvelopeItemIter<'s> {
164 type Item = &'s EnvelopeItem;
165
166 fn next(&mut self) -> Option<Self::Item> {
167 self.inner.next()
168 }
169}
170
171#[derive(Debug, Clone, PartialEq)]
176enum Items {
177 EnvelopeItems(Vec<EnvelopeItem>),
178 Raw(Vec<u8>),
179}
180
181impl Default for Items {
182 fn default() -> Self {
183 Self::EnvelopeItems(Default::default())
184 }
185}
186
187impl Items {
188 fn is_empty(&self) -> bool {
189 match self {
190 Items::EnvelopeItems(items) => items.is_empty(),
191 Items::Raw(bytes) => bytes.is_empty(),
192 }
193 }
194}
195
196#[derive(Clone, Default, Debug, PartialEq)]
205pub struct Envelope {
206 event_id: Option<Uuid>,
207 items: Items,
208}
209
210impl Envelope {
211 pub fn new() -> Envelope {
213 Default::default()
214 }
215
216 pub fn add_item<I>(&mut self, item: I)
218 where
219 I: Into<EnvelopeItem>,
220 {
221 let item = item.into();
222
223 let Items::EnvelopeItems(ref mut items) = self.items else {
224 if item != EnvelopeItem::Raw {
225 eprintln!(
226 "WARNING: This envelope contains raw items. Adding an item is not supported."
227 );
228 }
229 return;
230 };
231
232 if self.event_id.is_none() {
233 if let EnvelopeItem::Event(ref event) = item {
234 self.event_id = Some(event.event_id);
235 } else if let EnvelopeItem::Transaction(ref transaction) = item {
236 self.event_id = Some(transaction.event_id);
237 }
238 }
239 items.push(item);
240 }
241
242 pub fn items(&self) -> EnvelopeItemIter {
244 let inner = match &self.items {
245 Items::EnvelopeItems(items) => items.iter(),
246 Items::Raw(_) => [].iter(),
247 };
248
249 EnvelopeItemIter { inner }
250 }
251
252 pub fn uuid(&self) -> Option<&Uuid> {
254 self.event_id.as_ref()
255 }
256
257 pub fn event(&self) -> Option<&Event<'static>> {
261 let Items::EnvelopeItems(ref items) = self.items else {
262 return None;
263 };
264
265 items.iter().find_map(|item| match item {
266 EnvelopeItem::Event(event) => Some(event),
267 _ => None,
268 })
269 }
270
271 pub fn filter<P>(self, mut predicate: P) -> Option<Self>
280 where
281 P: FnMut(&EnvelopeItem) -> bool,
282 {
283 let Items::EnvelopeItems(items) = self.items else {
284 return if predicate(&EnvelopeItem::Raw) {
285 Some(self)
286 } else {
287 None
288 };
289 };
290
291 let mut filtered = Envelope::new();
292 for item in items {
293 if predicate(&item) {
294 filtered.add_item(item);
295 }
296 }
297
298 if filtered.uuid().is_none() {
301 if let Items::EnvelopeItems(ref mut items) = filtered.items {
302 items.retain(|item| !matches!(item, EnvelopeItem::Attachment(..)))
303 }
304 }
305
306 if filtered.items.is_empty() {
307 None
308 } else {
309 Some(filtered)
310 }
311 }
312
313 pub fn to_writer<W>(&self, mut writer: W) -> std::io::Result<()>
317 where
318 W: Write,
319 {
320 let items = match &self.items {
321 Items::Raw(bytes) => return writer.write_all(bytes).map(|_| ()),
322 Items::EnvelopeItems(items) => items,
323 };
324
325 let event_id = self.uuid();
327 match event_id {
328 Some(uuid) => writeln!(writer, r#"{{"event_id":"{uuid}"}}"#)?,
329 _ => writeln!(writer, "{{}}")?,
330 }
331
332 let mut item_buf = Vec::new();
333 for item in items {
335 match item {
337 EnvelopeItem::Event(event) => serde_json::to_writer(&mut item_buf, event)?,
338 EnvelopeItem::SessionUpdate(session) => {
339 serde_json::to_writer(&mut item_buf, session)?
340 }
341 EnvelopeItem::SessionAggregates(aggregates) => {
342 serde_json::to_writer(&mut item_buf, aggregates)?
343 }
344 EnvelopeItem::Transaction(transaction) => {
345 serde_json::to_writer(&mut item_buf, transaction)?
346 }
347 EnvelopeItem::Attachment(attachment) => {
348 attachment.to_writer(&mut writer)?;
349 writeln!(writer)?;
350 continue;
351 }
352 EnvelopeItem::MonitorCheckIn(check_in) => {
353 serde_json::to_writer(&mut item_buf, check_in)?
354 }
355 EnvelopeItem::Raw => {
356 continue;
357 }
358 }
359 let item_type = match item {
360 EnvelopeItem::Event(_) => "event",
361 EnvelopeItem::SessionUpdate(_) => "session",
362 EnvelopeItem::SessionAggregates(_) => "sessions",
363 EnvelopeItem::Transaction(_) => "transaction",
364 EnvelopeItem::MonitorCheckIn(_) => "check_in",
365 EnvelopeItem::Attachment(_) | EnvelopeItem::Raw => unreachable!(),
366 };
367 writeln!(
368 writer,
369 r#"{{"type":"{}","length":{}}}"#,
370 item_type,
371 item_buf.len()
372 )?;
373 writer.write_all(&item_buf)?;
374 writeln!(writer)?;
375 item_buf.clear();
376 }
377
378 Ok(())
379 }
380
381 pub fn from_slice(slice: &[u8]) -> Result<Envelope, EnvelopeError> {
383 let (header, offset) = Self::parse_header(slice)?;
384 let items = Self::parse_items(slice, offset)?;
385
386 let mut envelope = Envelope {
387 event_id: header.event_id,
388 ..Default::default()
389 };
390
391 for item in items {
392 envelope.add_item(item);
393 }
394
395 Ok(envelope)
396 }
397
398 pub fn from_bytes_raw(bytes: Vec<u8>) -> Result<Self, EnvelopeError> {
400 Ok(Self {
401 event_id: None,
402 items: Items::Raw(bytes),
403 })
404 }
405
406 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Envelope, EnvelopeError> {
408 let bytes = std::fs::read(path).map_err(|_| EnvelopeError::UnexpectedEof)?;
409 Envelope::from_slice(&bytes)
410 }
411
412 pub fn from_path_raw<P: AsRef<Path>>(path: P) -> Result<Self, EnvelopeError> {
417 let bytes = std::fs::read(path).map_err(|_| EnvelopeError::UnexpectedEof)?;
418 Self::from_bytes_raw(bytes)
419 }
420
421 fn parse_header(slice: &[u8]) -> Result<(EnvelopeHeader, usize), EnvelopeError> {
422 let mut stream = serde_json::Deserializer::from_slice(slice).into_iter();
423
424 let header: EnvelopeHeader = match stream.next() {
425 None => return Err(EnvelopeError::MissingHeader),
426 Some(Err(error)) => return Err(EnvelopeError::InvalidHeader(error)),
427 Some(Ok(header)) => header,
428 };
429
430 Self::require_termination(slice, stream.byte_offset())?;
432
433 Ok((header, stream.byte_offset() + 1))
434 }
435
436 fn parse_items(slice: &[u8], mut offset: usize) -> Result<Vec<EnvelopeItem>, EnvelopeError> {
437 let mut items = Vec::new();
438
439 while offset < slice.len() {
440 let bytes = slice
441 .get(offset..)
442 .ok_or(EnvelopeError::MissingItemHeader)?;
443 let (item, item_size) = Self::parse_item(bytes)?;
444 offset += item_size;
445 items.push(item);
446 }
447
448 Ok(items)
449 }
450
451 fn parse_item(slice: &[u8]) -> Result<(EnvelopeItem, usize), EnvelopeError> {
452 let mut stream = serde_json::Deserializer::from_slice(slice).into_iter();
453
454 let header: EnvelopeItemHeader = match stream.next() {
455 None => return Err(EnvelopeError::UnexpectedEof),
456 Some(Err(error)) => return Err(EnvelopeError::InvalidItemHeader(error)),
457 Some(Ok(header)) => header,
458 };
459
460 let header_end = stream.byte_offset();
462 Self::require_termination(slice, header_end)?;
463
464 let payload_start = std::cmp::min(header_end + 1, slice.len());
467 let payload_end = match header.length {
468 Some(len) => {
469 let payload_end = payload_start + len;
470 if slice.len() < payload_end {
471 return Err(EnvelopeError::UnexpectedEof);
472 }
473
474 Self::require_termination(slice, payload_end)?;
476 payload_end
477 }
478 None => match slice.get(payload_start..) {
479 Some(range) => match range.iter().position(|&b| b == b'\n') {
480 Some(relative_end) => payload_start + relative_end,
481 None => slice.len(),
482 },
483 None => slice.len(),
484 },
485 };
486
487 let payload = slice.get(payload_start..payload_end).unwrap();
488
489 let item = match header.r#type {
490 EnvelopeItemType::Event => serde_json::from_slice(payload).map(EnvelopeItem::Event),
491 EnvelopeItemType::Transaction => {
492 serde_json::from_slice(payload).map(EnvelopeItem::Transaction)
493 }
494 EnvelopeItemType::SessionUpdate => {
495 serde_json::from_slice(payload).map(EnvelopeItem::SessionUpdate)
496 }
497 EnvelopeItemType::SessionAggregates => {
498 serde_json::from_slice(payload).map(EnvelopeItem::SessionAggregates)
499 }
500 EnvelopeItemType::Attachment => Ok(EnvelopeItem::Attachment(Attachment {
501 buffer: payload.to_owned(),
502 filename: header.filename.unwrap_or_default(),
503 content_type: header.content_type,
504 ty: header.attachment_type,
505 })),
506 EnvelopeItemType::MonitorCheckIn => {
507 serde_json::from_slice(payload).map(EnvelopeItem::MonitorCheckIn)
508 }
509 }
510 .map_err(EnvelopeError::InvalidItemPayload)?;
511
512 Ok((item, payload_end + 1))
513 }
514
515 fn require_termination(slice: &[u8], offset: usize) -> Result<(), EnvelopeError> {
516 match slice.get(offset) {
517 Some(&b'\n') | None => Ok(()),
518 Some(_) => Err(EnvelopeError::MissingNewline),
519 }
520 }
521}
522
523impl<T> From<T> for Envelope
524where
525 T: Into<EnvelopeItem>,
526{
527 fn from(item: T) -> Self {
528 let mut envelope = Self::default();
529 envelope.add_item(item.into());
530 envelope
531 }
532}
533
534#[cfg(test)]
535mod test {
536 use std::str::FromStr;
537 use std::time::{Duration, SystemTime};
538
539 use time::format_description::well_known::Rfc3339;
540 use time::OffsetDateTime;
541
542 use super::*;
543 use crate::protocol::v7::{
544 Level, MonitorCheckInStatus, MonitorConfig, MonitorSchedule, SessionAttributes,
545 SessionStatus, Span,
546 };
547
548 fn to_str(envelope: Envelope) -> String {
549 let mut vec = Vec::new();
550 envelope.to_writer(&mut vec).unwrap();
551 String::from_utf8_lossy(&vec).to_string()
552 }
553
554 fn timestamp(s: &str) -> SystemTime {
555 let dt = OffsetDateTime::parse(s, &Rfc3339).unwrap();
556 let secs = dt.unix_timestamp() as u64;
557 let nanos = dt.nanosecond();
558 let duration = Duration::new(secs, nanos);
559 SystemTime::UNIX_EPOCH.checked_add(duration).unwrap()
560 }
561
562 #[test]
563 fn test_empty() {
564 assert_eq!(to_str(Envelope::new()), "{}\n");
565 }
566
567 #[test]
568 fn raw_roundtrip() {
569 let buf = r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
570{"type":"event","length":74}
571{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
572"#;
573 let envelope = Envelope::from_bytes_raw(buf.to_string().into_bytes()).unwrap();
574 let serialized = to_str(envelope);
575 assert_eq!(&serialized, buf);
576
577 let random_invalid_bytes = b"oh stahp!\0\x01\x02";
578 let envelope = Envelope::from_bytes_raw(random_invalid_bytes.to_vec()).unwrap();
579 let mut serialized = Vec::new();
580 envelope.to_writer(&mut serialized).unwrap();
581 assert_eq!(&serialized, random_invalid_bytes);
582 }
583
584 #[test]
585 fn test_event() {
586 let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
587 let timestamp = timestamp("2020-07-20T14:51:14.296Z");
588 let event = Event {
589 event_id,
590 timestamp,
591 ..Default::default()
592 };
593 let envelope: Envelope = event.into();
594 assert_eq!(
595 to_str(envelope),
596 r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
597{"type":"event","length":74}
598{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
599"#
600 )
601 }
602
603 #[test]
604 fn test_session() {
605 let session_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
606 let started = timestamp("2020-07-20T14:51:14.296Z");
607 let session = SessionUpdate {
608 session_id,
609 distinct_id: Some("foo@bar.baz".to_owned()),
610 sequence: None,
611 timestamp: None,
612 started,
613 init: true,
614 duration: Some(1.234),
615 status: SessionStatus::Ok,
616 errors: 123,
617 attributes: SessionAttributes {
618 release: "foo-bar@1.2.3".into(),
619 environment: Some("production".into()),
620 ip_address: None,
621 user_agent: None,
622 },
623 };
624 let mut envelope = Envelope::new();
625 envelope.add_item(session);
626 assert_eq!(
627 to_str(envelope),
628 r#"{}
629{"type":"session","length":222}
630{"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"}}
631"#
632 )
633 }
634
635 #[test]
636 fn test_transaction() {
637 let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
638 let span_id = "d42cee9fc3e74f5c".parse().unwrap();
639 let trace_id = "335e53d614474acc9f89e632b776cc28".parse().unwrap();
640 let start_timestamp = timestamp("2020-07-20T14:51:14.296Z");
641 let spans = vec![Span {
642 span_id,
643 trace_id,
644 start_timestamp,
645 ..Default::default()
646 }];
647 let transaction = Transaction {
648 event_id,
649 start_timestamp,
650 spans,
651 ..Default::default()
652 };
653 let envelope: Envelope = transaction.into();
654 assert_eq!(
655 to_str(envelope),
656 r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
657{"type":"transaction","length":200}
658{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","start_timestamp":1595256674.296,"spans":[{"span_id":"d42cee9fc3e74f5c","trace_id":"335e53d614474acc9f89e632b776cc28","start_timestamp":1595256674.296}]}
659"#
660 )
661 }
662
663 #[test]
664 fn test_monitor_checkin() {
665 let check_in_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
666
667 let check_in = MonitorCheckIn {
668 check_in_id,
669 monitor_slug: "my-monitor".into(),
670 status: MonitorCheckInStatus::Ok,
671 duration: Some(123.4),
672 environment: Some("production".into()),
673 monitor_config: Some(MonitorConfig {
674 schedule: MonitorSchedule::Crontab {
675 value: "12 0 * * *".into(),
676 },
677 checkin_margin: Some(5),
678 max_runtime: Some(30),
679 timezone: Some("UTC".into()),
680 failure_issue_threshold: None,
681 recovery_threshold: None,
682 }),
683 };
684 let envelope: Envelope = check_in.into();
685 assert_eq!(
686 to_str(envelope),
687 r#"{}
688{"type":"check_in","length":259}
689{"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"}}
690"#
691 )
692 }
693
694 #[test]
695 fn test_monitor_checkin_with_thresholds() {
696 let check_in_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
697
698 let check_in = MonitorCheckIn {
699 check_in_id,
700 monitor_slug: "my-monitor".into(),
701 status: MonitorCheckInStatus::Ok,
702 duration: Some(123.4),
703 environment: Some("production".into()),
704 monitor_config: Some(MonitorConfig {
705 schedule: MonitorSchedule::Crontab {
706 value: "12 0 * * *".into(),
707 },
708 checkin_margin: Some(5),
709 max_runtime: Some(30),
710 timezone: Some("UTC".into()),
711 failure_issue_threshold: Some(4),
712 recovery_threshold: Some(7),
713 }),
714 };
715 let envelope: Envelope = check_in.into();
716 assert_eq!(
717 to_str(envelope),
718 r#"{}
719{"type":"check_in","length":310}
720{"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}}
721"#
722 )
723 }
724
725 #[test]
726 fn test_event_with_attachment() {
727 let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
728 let timestamp = timestamp("2020-07-20T14:51:14.296Z");
729 let event = Event {
730 event_id,
731 timestamp,
732 ..Default::default()
733 };
734 let mut envelope: Envelope = event.into();
735
736 envelope.add_item(Attachment {
737 buffer: "some content".as_bytes().to_vec(),
738 filename: "file.txt".to_string(),
739 ..Default::default()
740 });
741
742 assert_eq!(
743 to_str(envelope),
744 r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
745{"type":"event","length":74}
746{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
747{"type":"attachment","length":12,"filename":"file.txt","attachment_type":"event.attachment","content_type":"application/octet-stream"}
748some content
749"#
750 )
751 }
752
753 #[test]
754 fn test_deserialize_envelope_empty() {
755 let bytes = b"{\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}";
757 let envelope = Envelope::from_slice(bytes).unwrap();
758
759 let event_id = Uuid::from_str("9ec79c33ec9942ab8353589fcb2e04dc").unwrap();
760 assert_eq!(envelope.event_id, Some(event_id));
761 assert_eq!(envelope.items().count(), 0);
762 }
763
764 #[test]
765 fn test_deserialize_envelope_empty_newline() {
766 let bytes = b"{\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n";
768 let envelope = Envelope::from_slice(bytes).unwrap();
769 assert_eq!(envelope.items().count(), 0);
770 }
771
772 #[test]
773 fn test_deserialize_envelope_empty_item_newline() {
774 let bytes = b"\
776 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
777 {\"type\":\"attachment\",\"length\":0}\n\
778 \n\
779 {\"type\":\"attachment\",\"length\":0}\n\
780 ";
781
782 let envelope = Envelope::from_slice(bytes).unwrap();
783 assert_eq!(envelope.items().count(), 2);
784
785 let mut items = envelope.items();
786
787 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
788 assert_eq!(attachment.buffer.len(), 0);
789 } else {
790 panic!("invalid item type");
791 }
792
793 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
794 assert_eq!(attachment.buffer.len(), 0);
795 } else {
796 panic!("invalid item type");
797 }
798 }
799
800 #[test]
801 fn test_deserialize_envelope_empty_item_eof() {
802 let bytes = b"\
804 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
805 {\"type\":\"attachment\",\"length\":0}\n\
806 \n\
807 {\"type\":\"attachment\",\"length\":0}\
808 ";
809
810 let envelope = Envelope::from_slice(bytes).unwrap();
811 assert_eq!(envelope.items().count(), 2);
812
813 let mut items = envelope.items();
814
815 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
816 assert_eq!(attachment.buffer.len(), 0);
817 } else {
818 panic!("invalid item type");
819 }
820
821 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
822 assert_eq!(attachment.buffer.len(), 0);
823 } else {
824 panic!("invalid item type");
825 }
826 }
827
828 #[test]
829 fn test_deserialize_envelope_implicit_length() {
830 let bytes = b"\
832 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
833 {\"type\":\"attachment\"}\n\
834 helloworld\n\
835 ";
836
837 let envelope = Envelope::from_slice(bytes).unwrap();
838 assert_eq!(envelope.items().count(), 1);
839
840 let mut items = envelope.items();
841
842 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
843 assert_eq!(attachment.buffer.len(), 10);
844 } else {
845 panic!("invalid item type");
846 }
847 }
848
849 #[test]
850 fn test_deserialize_envelope_implicit_length_eof() {
851 let bytes = b"\
853 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
854 {\"type\":\"attachment\"}\n\
855 helloworld\
856 ";
857
858 let envelope = Envelope::from_slice(bytes).unwrap();
859 assert_eq!(envelope.items().count(), 1);
860
861 let mut items = envelope.items();
862
863 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
864 assert_eq!(attachment.buffer.len(), 10);
865 } else {
866 panic!("invalid item type");
867 }
868 }
869
870 #[test]
871 fn test_deserialize_envelope_implicit_length_empty_eof() {
872 let bytes = b"\
874 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
875 {\"type\":\"attachment\"}\
876 ";
877
878 let envelope = Envelope::from_slice(bytes).unwrap();
879 assert_eq!(envelope.items().count(), 1);
880
881 let mut items = envelope.items();
882
883 if let EnvelopeItem::Attachment(attachment) = items.next().unwrap() {
884 assert_eq!(attachment.buffer.len(), 0);
885 } else {
886 panic!("invalid item type");
887 }
888 }
889
890 #[test]
891 fn test_deserialize_envelope_multiple_items() {
892 let bytes = b"\
894 {\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n\
895 {\"type\":\"attachment\",\"length\":10,\"content_type\":\"text/plain\",\"filename\":\"hello.txt\"}\n\
896 \xef\xbb\xbfHello\r\n\n\
897 {\"type\":\"event\",\"length\":41,\"content_type\":\"application/json\",\"filename\":\"application.log\"}\n\
898 {\"message\":\"hello world\",\"level\":\"error\"}\n\
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(), 10);
908 assert_eq!(attachment.buffer, b"\xef\xbb\xbfHello\r\n");
909 assert_eq!(attachment.filename, "hello.txt");
910 assert_eq!(attachment.content_type, Some("text/plain".to_string()));
911 } else {
912 panic!("invalid item type");
913 }
914
915 if let EnvelopeItem::Event(event) = items.next().unwrap() {
916 assert_eq!(event.message, Some("hello world".to_string()));
917 assert_eq!(event.level, Level::Error);
918 } else {
919 panic!("invalid item type");
920 }
921 }
922
923 #[test]
925 fn test_deserialize_serialized() {
926 let event = Event {
928 event_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap(),
929 timestamp: timestamp("2020-07-20T14:51:14.296Z"),
930 ..Default::default()
931 };
932
933 let transaction = Transaction {
935 event_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9d").unwrap(),
936 start_timestamp: timestamp("2020-07-20T14:51:14.296Z"),
937 spans: vec![Span {
938 span_id: "d42cee9fc3e74f5c".parse().unwrap(),
939 trace_id: "335e53d614474acc9f89e632b776cc28".parse().unwrap(),
940 start_timestamp: timestamp("2020-07-20T14:51:14.296Z"),
941 ..Default::default()
942 }],
943 ..Default::default()
944 };
945
946 let session = SessionUpdate {
948 session_id: Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap(),
949 distinct_id: Some("foo@bar.baz".to_owned()),
950 sequence: None,
951 timestamp: None,
952 started: timestamp("2020-07-20T14:51:14.296Z"),
953 init: true,
954 duration: Some(1.234),
955 status: SessionStatus::Ok,
956 errors: 123,
957 attributes: SessionAttributes {
958 release: "foo-bar@1.2.3".into(),
959 environment: Some("production".into()),
960 ip_address: None,
961 user_agent: None,
962 },
963 };
964
965 let attachment = Attachment {
967 buffer: "some content".as_bytes().to_vec(),
968 filename: "file.txt".to_string(),
969 ..Default::default()
970 };
971
972 let mut envelope: Envelope = Envelope::new();
973
974 envelope.add_item(event);
975 envelope.add_item(transaction);
976 envelope.add_item(session);
977 envelope.add_item(attachment);
978
979 let serialized = to_str(envelope);
980 let deserialized = Envelope::from_slice(serialized.as_bytes()).unwrap();
981 assert_eq!(serialized, to_str(deserialized))
982 }
983}