ckb_sentry_types/protocol/
envelope.rs1use std::io::Write;
2
3use uuid::Uuid;
4
5use super::v7::Event;
6use super::v7::SessionUpdate;
7use super::v7::Transaction;
8
9#[derive(Clone, Debug, PartialEq)]
14#[non_exhaustive]
15pub enum EnvelopeItem {
16 Event(Event<'static>),
21 SessionUpdate(SessionUpdate<'static>),
26 Transaction(Transaction<'static>),
31 }
35
36impl From<Event<'static>> for EnvelopeItem {
37 fn from(event: Event<'static>) -> Self {
38 EnvelopeItem::Event(event)
39 }
40}
41
42impl From<SessionUpdate<'static>> for EnvelopeItem {
43 fn from(session: SessionUpdate<'static>) -> Self {
44 EnvelopeItem::SessionUpdate(session)
45 }
46}
47
48impl From<Transaction<'static>> for EnvelopeItem {
49 fn from(session: Transaction<'static>) -> Self {
50 EnvelopeItem::Transaction(session)
51 }
52}
53
54#[derive(Clone)]
56pub struct EnvelopeItemIter<'s> {
57 inner: std::slice::Iter<'s, EnvelopeItem>,
58}
59
60impl<'s> Iterator for EnvelopeItemIter<'s> {
61 type Item = &'s EnvelopeItem;
62
63 fn next(&mut self) -> Option<Self::Item> {
64 self.inner.next()
65 }
66}
67
68#[derive(Clone, Default, Debug, PartialEq)]
77pub struct Envelope {
78 event_id: Option<Uuid>,
79 items: Vec<EnvelopeItem>,
80}
81
82impl Envelope {
83 pub fn new() -> Envelope {
85 Default::default()
86 }
87
88 pub fn add_item<I>(&mut self, item: I)
90 where
91 I: Into<EnvelopeItem>,
92 {
93 let item = item.into();
94 if self.event_id.is_none() {
95 if let EnvelopeItem::Event(ref event) = item {
96 self.event_id = Some(event.event_id);
97 } else if let EnvelopeItem::Transaction(ref transaction) = item {
98 self.event_id = Some(transaction.event_id);
99 }
100 }
101 self.items.push(item);
102 }
103
104 pub fn items(&self) -> EnvelopeItemIter {
106 EnvelopeItemIter {
107 inner: self.items.iter(),
108 }
109 }
110
111 pub fn uuid(&self) -> Option<&Uuid> {
113 self.event_id.as_ref()
114 }
115
116 pub fn event(&self) -> Option<&Event<'static>> {
120 self.items
121 .iter()
122 .filter_map(|item| match item {
123 EnvelopeItem::Event(event) => Some(event),
124 _ => None,
125 })
126 .next()
127 }
128
129 pub fn to_writer<W>(&self, mut writer: W) -> std::io::Result<()>
133 where
134 W: Write,
135 {
136 let mut item_buf = Vec::new();
137
138 let event_id = self.uuid();
140 match event_id {
141 Some(uuid) => writeln!(writer, r#"{{"event_id":"{}"}}"#, uuid)?,
142 _ => writeln!(writer, "{{}}")?,
143 }
144
145 for item in &self.items {
147 match item {
149 EnvelopeItem::Event(event) => serde_json::to_writer(&mut item_buf, event)?,
150 EnvelopeItem::SessionUpdate(session) => {
151 serde_json::to_writer(&mut item_buf, session)?
152 }
153 EnvelopeItem::Transaction(transaction) => {
154 serde_json::to_writer(&mut item_buf, transaction)?
155 }
156 }
157 let item_type = match item {
158 EnvelopeItem::Event(_) => "event",
159 EnvelopeItem::SessionUpdate(_) => "session",
160 EnvelopeItem::Transaction(_) => "transaction",
161 };
162 writeln!(
163 writer,
164 r#"{{"type":"{}","length":{}}}"#,
165 item_type,
166 item_buf.len()
167 )?;
168 writer.write_all(&item_buf)?;
169 writeln!(writer)?;
170 item_buf.clear();
171 }
172
173 Ok(())
174 }
175}
176
177impl From<Event<'static>> for Envelope {
178 fn from(event: Event<'static>) -> Self {
179 let mut envelope = Self::default();
180 envelope.add_item(event);
181 envelope
182 }
183}
184
185impl From<Transaction<'static>> for Envelope {
186 fn from(transaction: Transaction<'static>) -> Self {
187 let mut envelope = Self::default();
188 envelope.add_item(transaction);
189 envelope
190 }
191}
192
193#[cfg(test)]
194mod test {
195 use chrono::{DateTime, Utc};
196
197 use super::*;
198 use crate::protocol::v7::{SessionAttributes, SessionStatus, Span};
199
200 fn to_str(envelope: Envelope) -> String {
201 let mut vec = Vec::new();
202 envelope.to_writer(&mut vec).unwrap();
203 String::from_utf8_lossy(&vec).to_string()
204 }
205
206 #[test]
207 fn test_empty() {
208 assert_eq!(to_str(Envelope::new()), "{}\n");
209 }
210
211 #[test]
212 fn test_event() {
213 let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
214 let timestamp = "2020-07-20T14:51:14.296Z".parse::<DateTime<Utc>>().unwrap();
215 let event = Event {
216 event_id,
217 timestamp,
218 ..Default::default()
219 };
220 let envelope: Envelope = event.into();
221 assert_eq!(
222 to_str(envelope),
223 r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
224{"type":"event","length":74}
225{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
226"#
227 )
228 }
229
230 #[test]
231 fn test_session() {
232 let session_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
233 let started = "2020-07-20T14:51:14.296Z".parse::<DateTime<Utc>>().unwrap();
234 let session = SessionUpdate {
235 session_id,
236 distinct_id: Some("foo@bar.baz".to_owned()),
237 sequence: None,
238 timestamp: None,
239 started,
240 init: true,
241 duration: Some(1.234),
242 status: SessionStatus::Ok,
243 errors: 123,
244 attributes: SessionAttributes {
245 release: "foo-bar@1.2.3".into(),
246 environment: Some("production".into()),
247 ip_address: None,
248 user_agent: None,
249 },
250 };
251 let mut envelope = Envelope::new();
252 envelope.add_item(session);
253 assert_eq!(
254 to_str(envelope),
255 r#"{}
256{"type":"session","length":222}
257{"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"}}
258"#
259 )
260 }
261
262 #[test]
263 fn test_transaction() {
264 let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
265 let span_id = Uuid::parse_str("d42cee9f-c3e7-4f5c-ada9-47ab601a14d2").unwrap();
266 let trace_id = Uuid::parse_str("335e53d6-1447-4acc-9f89-e632b776cc28").unwrap();
267 let start_timestamp = "2020-07-20T14:51:14.296Z".parse::<DateTime<Utc>>().unwrap();
268 let spans = vec![Span {
269 span_id,
270 trace_id,
271 start_timestamp,
272 ..Default::default()
273 }];
274 let transaction = Transaction {
275 event_id,
276 start_timestamp,
277 spans,
278 ..Default::default()
279 };
280 let envelope: Envelope = transaction.into();
281 assert_eq!(
282 to_str(envelope),
283 r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
284{"type":"transaction","length":216}
285{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","start_timestamp":1595256674.296,"spans":[{"span_id":"d42cee9fc3e74f5cada947ab601a14d2","trace_id":"335e53d614474acc9f89e632b776cc28","start_timestamp":1595256674.296}]}
286"#
287 )
288 }
289}