Skip to main content

cloudconvert_sdk/
file_extension.rs

1use std::{fmt, str::FromStr};
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5macro_rules! file_extensions {
6    ($($variant:ident => $extension:literal),+ $(,)?) => {
7        /// Supported CloudConvert file extension tokens.
8        ///
9        /// Values serialize to the lowercase extension token CloudConvert expects,
10        /// without a leading dot. The list is derived from CloudConvert's
11        /// operations metadata endpoint.
12        ///
13        /// ```
14        /// use cloudconvert_sdk::FileExtension;
15        ///
16        /// assert_eq!(FileExtension::Pdf.as_str(), "pdf");
17        /// assert_eq!(".TAR.GZ".parse::<FileExtension>().unwrap(), FileExtension::TarGz);
18        /// assert_eq!(
19        ///     serde_json::to_value(FileExtension::SevenZ).unwrap(),
20        ///     serde_json::json!("7z")
21        /// );
22        /// ```
23        #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
24        #[non_exhaustive]
25        pub enum FileExtension {
26            $($variant,)+
27        }
28
29        impl FileExtension {
30            /// All known file extensions in sorted order.
31            pub const ALL: &'static [Self] = &[
32                $(Self::$variant,)+
33            ];
34
35            /// Returns the extension token CloudConvert expects.
36            pub const fn as_str(self) -> &'static str {
37                match self {
38                    $(Self::$variant => $extension,)+
39                }
40            }
41
42            /// Parses a CloudConvert extension token.
43            ///
44            /// Leading dots, surrounding whitespace and ASCII case differences are
45            /// normalized before matching.
46            ///
47            /// ```
48            /// use cloudconvert_sdk::FileExtension;
49            ///
50            /// assert_eq!(FileExtension::parse(" .PDF ").unwrap(), FileExtension::Pdf);
51            /// ```
52            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/// Error returned when parsing a string into [`FileExtension`] fails.
332///
333/// ```
334/// use cloudconvert_sdk::FileExtension;
335///
336/// let error = FileExtension::parse("not-real").unwrap_err();
337/// assert_eq!(error.extension(), "not-real");
338/// ```
339#[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    /// The unrecognized extension as provided to the parser.
352    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}