Skip to main content

lash_sansio/
attachment.rs

1use std::fmt;
2
3#[derive(
4    Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, PartialOrd, Ord,
5)]
6#[serde(transparent)]
7pub struct AttachmentId(String);
8
9impl AttachmentId {
10    pub fn new(id: impl Into<String>) -> Self {
11        Self(id.into())
12    }
13
14    pub fn as_str(&self) -> &str {
15        &self.0
16    }
17}
18
19impl fmt::Display for AttachmentId {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        f.write_str(&self.0)
22    }
23}
24
25impl From<String> for AttachmentId {
26    fn from(value: String) -> Self {
27        Self::new(value)
28    }
29}
30
31impl From<&str> for AttachmentId {
32    fn from(value: &str) -> Self {
33        Self::new(value)
34    }
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
38#[serde(rename_all = "snake_case")]
39pub enum ImageMediaType {
40    Png,
41    Jpeg,
42    Gif,
43    Webp,
44    Bmp,
45}
46
47impl ImageMediaType {
48    pub fn from_mime(mime: &str) -> Option<Self> {
49        match mime.trim().to_ascii_lowercase().as_str() {
50            "image/png" => Some(Self::Png),
51            "image/jpeg" | "image/jpg" => Some(Self::Jpeg),
52            "image/gif" => Some(Self::Gif),
53            "image/webp" => Some(Self::Webp),
54            "image/bmp" => Some(Self::Bmp),
55            _ => None,
56        }
57    }
58
59    pub fn canonical_mime(self) -> &'static str {
60        match self {
61            Self::Png => "image/png",
62            Self::Jpeg => "image/jpeg",
63            Self::Gif => "image/gif",
64            Self::Webp => "image/webp",
65            Self::Bmp => "image/bmp",
66        }
67    }
68}
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
71#[serde(tag = "kind", content = "type", rename_all = "snake_case")]
72pub enum MediaType {
73    Image(ImageMediaType),
74}
75
76impl MediaType {
77    pub fn from_mime(mime: &str) -> Option<Self> {
78        ImageMediaType::from_mime(mime).map(Self::Image)
79    }
80
81    pub fn canonical_mime(self) -> &'static str {
82        match self {
83            Self::Image(image) => image.canonical_mime(),
84        }
85    }
86
87    pub fn is_image(self) -> bool {
88        matches!(self, Self::Image(_))
89    }
90}
91
92#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
93pub struct AttachmentCreateMeta {
94    pub media_type: MediaType,
95    #[serde(default, skip_serializing_if = "Option::is_none")]
96    pub width: Option<u32>,
97    #[serde(default, skip_serializing_if = "Option::is_none")]
98    pub height: Option<u32>,
99    #[serde(default, skip_serializing_if = "Option::is_none")]
100    pub label: Option<String>,
101}
102
103impl AttachmentCreateMeta {
104    pub fn new(
105        media_type: MediaType,
106        width: Option<u32>,
107        height: Option<u32>,
108        label: Option<String>,
109    ) -> Self {
110        Self {
111            media_type,
112            width,
113            height,
114            label,
115        }
116    }
117}
118
119#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
120pub struct AttachmentMeta {
121    pub id: AttachmentId,
122    pub media_type: MediaType,
123    pub byte_len: u64,
124    #[serde(default, skip_serializing_if = "Option::is_none")]
125    pub width: Option<u32>,
126    #[serde(default, skip_serializing_if = "Option::is_none")]
127    pub height: Option<u32>,
128    #[serde(default, skip_serializing_if = "Option::is_none")]
129    pub label: Option<String>,
130}
131
132impl AttachmentMeta {
133    pub fn new(
134        id: AttachmentId,
135        media_type: MediaType,
136        byte_len: u64,
137        width: Option<u32>,
138        height: Option<u32>,
139        label: Option<String>,
140    ) -> Self {
141        Self {
142            id,
143            media_type,
144            byte_len,
145            width,
146            height,
147            label,
148        }
149    }
150
151    pub fn as_ref(&self) -> AttachmentRef {
152        AttachmentRef {
153            id: self.id.clone(),
154            media_type: self.media_type,
155            byte_len: self.byte_len,
156            width: self.width,
157            height: self.height,
158            label: self.label.clone(),
159        }
160    }
161}
162
163#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
164pub struct AttachmentRef {
165    pub id: AttachmentId,
166    pub media_type: MediaType,
167    pub byte_len: u64,
168    #[serde(default, skip_serializing_if = "Option::is_none")]
169    pub width: Option<u32>,
170    #[serde(default, skip_serializing_if = "Option::is_none")]
171    pub height: Option<u32>,
172    #[serde(default, skip_serializing_if = "Option::is_none")]
173    pub label: Option<String>,
174}
175
176impl AttachmentRef {
177    pub fn meta(&self) -> AttachmentMeta {
178        AttachmentMeta {
179            id: self.id.clone(),
180            media_type: self.media_type,
181            byte_len: self.byte_len,
182            width: self.width,
183            height: self.height,
184            label: self.label.clone(),
185        }
186    }
187
188    pub fn canonical_mime(&self) -> &'static str {
189        self.media_type.canonical_mime()
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    #[test]
198    fn image_mime_parses_to_canonical_type() {
199        assert_eq!(
200            MediaType::from_mime("image/jpg")
201                .expect("jpeg")
202                .canonical_mime(),
203            "image/jpeg"
204        );
205        assert_eq!(
206            MediaType::from_mime("image/webp")
207                .expect("webp")
208                .canonical_mime(),
209            "image/webp"
210        );
211        assert!(MediaType::from_mime("application/octet-stream").is_none());
212    }
213}