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