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