1use std::{fmt, str::FromStr};
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5macro_rules! file_extensions {
6 ($($variant:ident => $extension:literal),+ $(,)?) => {
7 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
24 #[non_exhaustive]
25 pub enum FileExtension {
26 $($variant,)+
27 }
28
29 impl FileExtension {
30 pub const ALL: &'static [Self] = &[
32 $(Self::$variant,)+
33 ];
34
35 pub const fn as_str(self) -> &'static str {
37 match self {
38 $(Self::$variant => $extension,)+
39 }
40 }
41
42 pub fn parse(value: &str) -> Result<Self, ParseFileExtensionError> {
53 value.parse()
54 }
55 }
56
57 impl FromStr for FileExtension {
58 type Err = ParseFileExtensionError;
59
60 fn from_str(value: &str) -> Result<Self, Self::Err> {
61 let normalized = normalize_file_extension(value);
62
63 match normalized.as_str() {
64 $($extension => Ok(Self::$variant),)+
65 _ => Err(ParseFileExtensionError::new(value)),
66 }
67 }
68 }
69 };
70}
71
72file_extensions! {
73 ThreeFr => "3fr",
74 ThreeG2 => "3g2",
75 ThreeGp => "3gp",
76 ThreeGpp => "3gpp",
77 SevenZ => "7z",
78 Aac => "aac",
79 Abw => "abw",
80 Ac3 => "ac3",
81 Ace => "ace",
82 Ai => "ai",
83 Aif => "aif",
84 Aifc => "aifc",
85 Aiff => "aiff",
86 Alz => "alz",
87 Amr => "amr",
88 Arc => "arc",
89 Arj => "arj",
90 Arw => "arw",
91 Au => "au",
92 Avi => "avi",
93 Avif => "avif",
94 Azw => "azw",
95 Azw3 => "azw3",
96 Azw4 => "azw4",
97 Bmp => "bmp",
98 Bz => "bz",
99 Bz2 => "bz2",
100 Cab => "cab",
101 Caf => "caf",
102 Cavs => "cavs",
103 Cbc => "cbc",
104 Cbr => "cbr",
105 Cbz => "cbz",
106 Cdr => "cdr",
107 Cgm => "cgm",
108 Chm => "chm",
109 Cpio => "cpio",
110 Cr2 => "cr2",
111 Cr3 => "cr3",
112 Crw => "crw",
113 Csv => "csv",
114 Dcr => "dcr",
115 Deb => "deb",
116 Djvu => "djvu",
117 Dmg => "dmg",
118 Dng => "dng",
119 Doc => "doc",
120 Docm => "docm",
121 Docx => "docx",
122 Dot => "dot",
123 Dotx => "dotx",
124 Dps => "dps",
125 Dss => "dss",
126 Dv => "dv",
127 Dvr => "dvr",
128 Dwf => "dwf",
129 Dwg => "dwg",
130 Dxf => "dxf",
131 Emf => "emf",
132 Eml => "eml",
133 Eot => "eot",
134 Eps => "eps",
135 Epub => "epub",
136 Erf => "erf",
137 Et => "et",
138 Fb2 => "fb2",
139 Flac => "flac",
140 Flv => "flv",
141 Gif => "gif",
142 Gz => "gz",
143 Heic => "heic",
144 Heif => "heif",
145 Htm => "htm",
146 Html => "html",
147 Htmlz => "htmlz",
148 Hwp => "hwp",
149 Hwpx => "hwpx",
150 Icns => "icns",
151 Ico => "ico",
152 Img => "img",
153 Iso => "iso",
154 Jar => "jar",
155 Jfif => "jfif",
156 Jpeg => "jpeg",
157 Jpg => "jpg",
158 Key => "key",
159 Lha => "lha",
160 Lit => "lit",
161 Lrf => "lrf",
162 Lwp => "lwp",
163 Lz => "lz",
164 Lzma => "lzma",
165 Lzo => "lzo",
166 M2ts => "m2ts",
167 M4a => "m4a",
168 M4b => "m4b",
169 M4v => "m4v",
170 Md => "md",
171 Mkv => "mkv",
172 Mobi => "mobi",
173 Mod => "mod",
174 Mos => "mos",
175 Mov => "mov",
176 Mp3 => "mp3",
177 Mp4 => "mp4",
178 Mpeg => "mpeg",
179 Mpg => "mpg",
180 Mrw => "mrw",
181 Mts => "mts",
182 Mxf => "mxf",
183 Nef => "nef",
184 Numbers => "numbers",
185 Odd => "odd",
186 Odg => "odg",
187 Odp => "odp",
188 Ods => "ods",
189 Odt => "odt",
190 Oeb => "oeb",
191 Oga => "oga",
192 Ogg => "ogg",
193 Ogv => "ogv",
194 Opus => "opus",
195 Orf => "orf",
196 Otf => "otf",
197 Pages => "pages",
198 Pdb => "pdb",
199 Pdf => "pdf",
200 Pef => "pef",
201 Pml => "pml",
202 Png => "png",
203 Pot => "pot",
204 Potx => "potx",
205 Ppm => "ppm",
206 Pps => "pps",
207 Ppsx => "ppsx",
208 Ppt => "ppt",
209 Pptm => "pptm",
210 Pptx => "pptx",
211 Prc => "prc",
212 Ps => "ps",
213 Psb => "psb",
214 Psd => "psd",
215 Pub => "pub",
216 Raf => "raf",
217 Rar => "rar",
218 Raw => "raw",
219 Rb => "rb",
220 Rm => "rm",
221 Rmvb => "rmvb",
222 Rpm => "rpm",
223 Rst => "rst",
224 Rtf => "rtf",
225 Rw2 => "rw2",
226 Rz => "rz",
227 Sda => "sda",
228 Sdc => "sdc",
229 Sdw => "sdw",
230 Sf2 => "sf2",
231 Sfark => "sfark",
232 Sk => "sk",
233 Sk1 => "sk1",
234 Snb => "snb",
235 Svg => "svg",
236 Svgz => "svgz",
237 Swf => "swf",
238 Tar => "tar",
239 Tar7z => "tar.7z",
240 TarBz => "tar.bz",
241 TarBz2 => "tar.bz2",
242 TarGz => "tar.gz",
243 TarLzo => "tar.lzo",
244 TarXz => "tar.xz",
245 TarZ => "tar.z",
246 Tbz => "tbz",
247 Tbz2 => "tbz2",
248 Tcr => "tcr",
249 Tex => "tex",
250 Tga => "tga",
251 Tgz => "tgz",
252 Tif => "tif",
253 Tiff => "tiff",
254 Ts => "ts",
255 Ttf => "ttf",
256 Txt => "txt",
257 Txtz => "txtz",
258 Tz => "tz",
259 Tzo => "tzo",
260 Vob => "vob",
261 Voc => "voc",
262 Vsd => "vsd",
263 Vtt => "vtt",
264 Wav => "wav",
265 Weba => "weba",
266 Webm => "webm",
267 Webp => "webp",
268 Wma => "wma",
269 Wmf => "wmf",
270 Wmv => "wmv",
271 Woff => "woff",
272 Woff2 => "woff2",
273 Wpd => "wpd",
274 Wps => "wps",
275 Wtv => "wtv",
276 X3f => "x3f",
277 Xcf => "xcf",
278 Xls => "xls",
279 Xlsm => "xlsm",
280 Xlsx => "xlsx",
281 Xps => "xps",
282 Xz => "xz",
283 Z => "z",
284 Zabw => "zabw",
285 Zip => "zip",
286}
287
288impl AsRef<str> for FileExtension {
289 fn as_ref(&self) -> &str {
290 self.as_str()
291 }
292}
293
294impl fmt::Display for FileExtension {
295 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
296 formatter.write_str(self.as_str())
297 }
298}
299
300impl From<FileExtension> for String {
301 fn from(value: FileExtension) -> Self {
302 value.as_str().to_string()
303 }
304}
305
306impl From<&FileExtension> for String {
307 fn from(value: &FileExtension) -> Self {
308 value.as_str().to_string()
309 }
310}
311
312impl Serialize for FileExtension {
313 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
314 where
315 S: Serializer,
316 {
317 serializer.serialize_str(self.as_str())
318 }
319}
320
321impl<'de> Deserialize<'de> for FileExtension {
322 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
323 where
324 D: Deserializer<'de>,
325 {
326 let value = String::deserialize(deserializer)?;
327 value.parse().map_err(serde::de::Error::custom)
328 }
329}
330
331#[derive(Clone, Debug, Eq, PartialEq)]
340pub struct ParseFileExtensionError {
341 extension: String,
342}
343
344impl ParseFileExtensionError {
345 fn new(extension: impl Into<String>) -> Self {
346 Self {
347 extension: extension.into(),
348 }
349 }
350
351 pub fn extension(&self) -> &str {
353 &self.extension
354 }
355}
356
357impl fmt::Display for ParseFileExtensionError {
358 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
359 write!(
360 formatter,
361 "unsupported CloudConvert file extension: {}",
362 self.extension
363 )
364 }
365}
366
367impl std::error::Error for ParseFileExtensionError {}
368
369pub(crate) fn normalize_file_extension(value: impl Into<String>) -> String {
370 value
371 .into()
372 .trim()
373 .trim_start_matches('.')
374 .to_ascii_lowercase()
375}