sentry_types/protocol/
attachment.rs

1use std::fmt;
2
3use serde::Deserialize;
4
5/// The different types an attachment can have.
6#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Default)]
7pub enum AttachmentType {
8    #[serde(rename = "event.attachment")]
9    /// (default) A standard attachment without special meaning.
10    #[default]
11    Attachment,
12    /// A minidump file that creates an error event and is symbolicated. The
13    /// file should start with the `MDMP` magic bytes.
14    #[serde(rename = "event.minidump")]
15    Minidump,
16    /// An Apple crash report file that creates an error event and is symbolicated.
17    #[serde(rename = "event.applecrashreport")]
18    AppleCrashReport,
19    /// An XML file containing UE4 crash meta data. During event ingestion,
20    /// event contexts and extra fields are extracted from this file.
21    #[serde(rename = "unreal.context")]
22    UnrealContext,
23    /// A plain-text log file obtained from UE4 crashes. During event ingestion,
24    /// the last logs are extracted into event breadcrumbs.
25    #[serde(rename = "unreal.logs")]
26    UnrealLogs,
27    /// A custom attachment type with an arbitrary string value.
28    #[serde(untagged)]
29    Custom(String),
30}
31
32impl AttachmentType {
33    /// Gets the string value Sentry expects for the attachment type.
34    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)]
47/// Represents an attachment item.
48pub struct Attachment {
49    /// The actual attachment data.
50    pub buffer: Vec<u8>,
51    /// The filename of the attachment.
52    pub filename: String,
53    /// The Content Type of the attachment
54    pub content_type: Option<String>,
55    /// The special type of this attachment.
56    pub ty: Option<AttachmentType>,
57}
58
59impl Attachment {
60    /// Writes the attachment and its headers to the provided `Writer`.
61    pub fn to_writer<W>(&self, writer: &mut W) -> std::io::Result<()>
62    where
63        W: std::io::Write,
64    {
65        writeln!(
66            writer,
67            r#"{{"type":"attachment","length":{length},"filename":"{filename}","attachment_type":"{at}","content_type":"{ct}"}}"#,
68            filename = self.filename,
69            length = self.buffer.len(),
70            at = self
71                .ty
72                .as_ref()
73                .unwrap_or(&AttachmentType::default())
74                .as_str(),
75            ct = self
76                .content_type
77                .as_ref()
78                .unwrap_or(&"application/octet-stream".to_string())
79        )?;
80
81        writer.write_all(&self.buffer)?;
82        Ok(())
83    }
84}
85
86// Implement Debug manually, otherwise users will be sad when they get a dump
87// of decimal encoded bytes to their console
88impl fmt::Debug for Attachment {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        f.debug_struct("Attachment")
91            .field("buffer", &self.buffer.len())
92            .field("filename", &self.filename)
93            .field("content_type", &self.content_type)
94            .field("type", &self.ty)
95            .finish()
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use serde_json;
103
104    #[test]
105    fn test_attachment_type_deserialize() {
106        let result: AttachmentType = serde_json::from_str(r#""event.minidump""#).unwrap();
107        assert_eq!(result, AttachmentType::Minidump);
108
109        let result: AttachmentType = serde_json::from_str(r#""my.custom.type""#).unwrap();
110        assert_eq!(result, AttachmentType::Custom("my.custom.type".to_string()));
111    }
112}