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}