sentry_types/protocol/
attachment.rs1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Default)]
7pub enum AttachmentType {
8 #[serde(rename = "event.attachment")]
9 #[default]
11 Attachment,
12 #[serde(rename = "event.minidump")]
15 Minidump,
16 #[serde(rename = "event.applecrashreport")]
18 AppleCrashReport,
19 #[serde(rename = "unreal.context")]
22 UnrealContext,
23 #[serde(rename = "unreal.logs")]
26 UnrealLogs,
27 #[serde(untagged)]
29 Custom(String),
30}
31
32impl AttachmentType {
33 pub fn as_str(&self) -> &str {
35 match self {
36 Self::Attachment => "event.attachment",
37 Self::Minidump => "event.minidump",
38 Self::AppleCrashReport => "event.applecrashreport",
39 Self::UnrealContext => "unreal.context",
40 Self::UnrealLogs => "unreal.logs",
41 Self::Custom(s) => s,
42 }
43 }
44}
45
46#[derive(Clone, PartialEq, Default)]
47pub struct Attachment {
49 pub buffer: Vec<u8>,
51 pub filename: String,
53 pub content_type: Option<String>,
55 pub ty: Option<AttachmentType>,
57}
58
59struct AttachmentHeaderType;
60
61impl Serialize for AttachmentHeaderType {
62 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
63 where
64 S: serde::Serializer,
65 {
66 "attachment".serialize(serializer)
67 }
68}
69
70#[derive(Serialize)]
71struct AttachmentHeader<'a> {
72 r#type: AttachmentHeaderType,
73 length: usize,
74 filename: &'a str,
75 attachment_type: &'a AttachmentType,
76 content_type: &'a str,
77}
78
79impl Attachment {
80 pub fn to_writer<W>(&self, writer: &mut W) -> std::io::Result<()>
82 where
83 W: std::io::Write,
84 {
85 let attachment_type = match self.ty.as_ref() {
86 Some(ty) => ty,
87 None => &Default::default(),
88 };
89
90 let content_type = self
91 .content_type
92 .as_deref()
93 .unwrap_or("application/octet-stream");
94 let header = AttachmentHeader {
95 r#type: AttachmentHeaderType,
96 length: self.buffer.len(),
97 filename: &self.filename,
98 attachment_type,
99 content_type,
100 };
101
102 serde_json::to_writer(&mut *writer, &header)?;
103 writeln!(writer)?;
104
105 writer.write_all(&self.buffer)?;
106 Ok(())
107 }
108}
109
110impl fmt::Debug for Attachment {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 f.debug_struct("Attachment")
115 .field("buffer", &self.buffer.len())
116 .field("filename", &self.filename)
117 .field("content_type", &self.content_type)
118 .field("type", &self.ty)
119 .finish()
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use serde_json::{self, json};
127
128 #[test]
129 fn test_attachment_type_deserialize() {
130 let result: AttachmentType = serde_json::from_str(r#""event.minidump""#).unwrap();
131 assert_eq!(result, AttachmentType::Minidump);
132
133 let result: AttachmentType = serde_json::from_str(r#""my.custom.type""#).unwrap();
134 assert_eq!(result, AttachmentType::Custom("my.custom.type".to_string()));
135 }
136
137 #[test]
138 fn test_attachment_header_escapes_json_strings() {
139 let attachment = Attachment {
140 buffer: b"payload".to_vec(),
141 filename: "file \"name\"\npart.txt".to_string(),
142 content_type: Some("text/\"plain\nnext".to_string()),
143 ty: Some(AttachmentType::Custom("custom/\"type\nnext".to_string())),
144 };
145
146 let mut buf = Vec::new();
147 attachment.to_writer(&mut buf).unwrap();
148
149 let mut parts = buf.splitn(2, |&b| b == b'\n');
150 let header: serde_json::Value = serde_json::from_slice(parts.next().unwrap()).unwrap();
151 let payload = parts.next().unwrap();
152
153 assert_eq!(
154 header,
155 json!({
156 "type": "attachment",
157 "length": 7,
158 "filename": "file \"name\"\npart.txt",
159 "content_type": "text/\"plain\nnext",
160 "attachment_type": "custom/\"type\nnext",
161 })
162 );
163 assert_eq!(payload, b"payload");
164 }
165}