mime_db/
lib.rs

1#![no_std]
2
3use core::slice;
4
5mod extensions;
6mod types;
7
8pub use extensions::EXTENSIONS;
9pub use types::TYPES;
10
11pub fn lookup(extension: impl AsRef<str>) -> Option<&'static str> {
12    let extension = extension.as_ref();
13    if extension.is_empty() {
14        return None;
15    }
16
17    let extension = extension
18        .rfind('.')
19        .map_or(extension, |i| &extension[i + 1..]);
20
21    EXTENSIONS
22        .iter()
23        .find(|(ext, _)| *ext == extension)
24        .and_then(|(_, i)| TYPES.get(*i).map(|(kind, _, _)| *kind))
25}
26
27pub fn extensions(mime_type: impl AsRef<str>) -> Option<impl Iterator<Item = &'static str>> {
28    let iter = extensions2(mime_type);
29    if iter.size_hint().0 == 0 {
30        None
31    } else {
32        Some(iter)
33    }
34}
35
36pub fn extensions2(mime_type: impl AsRef<str>) -> ExtensionsIter {
37    let mime_type = mime_type.as_ref();
38
39    if mime_type.is_empty() {
40        return ExtensionsIter::default();
41    }
42
43    TYPES
44        .iter()
45        .find(|(kind, _, _)| *kind == mime_type)
46        .map_or_else(ExtensionsIter::default, |(_, start, len)| ExtensionsIter {
47            inner: EXTENSIONS[*start..][..*len].iter(),
48        })
49}
50
51#[inline]
52pub fn extension(mime_type: impl AsRef<str>) -> Option<&'static str> {
53    extensions2(mime_type).next()
54}
55
56#[must_use = "iterators are lazy and do nothing unless consumed"]
57#[derive(Debug, Clone)]
58pub struct ExtensionsIter {
59    // Uses std's implementation of slice Iterator, since it's much more optimized
60    // and probably uses unsafe internally to avoid bounds checks.
61    inner: slice::Iter<'static, (&'static str, usize)>,
62}
63
64impl Default for ExtensionsIter {
65    fn default() -> Self {
66        // easy way to get an empty &'static slice
67        const EMPTY: &[(&str, usize)] = &[];
68        Self {
69            inner: EMPTY.iter(),
70        }
71    }
72}
73
74impl Iterator for ExtensionsIter {
75    type Item = &'static str;
76
77    fn next(&mut self) -> Option<Self::Item> {
78        self.inner.next().map(|(ext, _)| *ext)
79    }
80
81    #[inline]
82    fn size_hint(&self) -> (usize, Option<usize>) {
83        self.inner.size_hint()
84    }
85
86    #[inline]
87    fn count(self) -> usize {
88        self.inner.count()
89    }
90}
91
92#[cfg(test)]
93#[test]
94fn search() {
95    assert_eq!(lookup("json").unwrap(), "application/json");
96    assert_eq!(lookup(".md").unwrap(), "text/markdown");
97    assert_eq!(lookup("folder/file.js").unwrap(), "application/javascript");
98    assert_eq!(lookup("folder/.htaccess"), None);
99    assert_eq!(lookup("cats"), None);
100    assert_eq!(lookup(""), None);
101
102    assert!(extensions("application/octet-stream").unwrap().eq([
103        "bin", "dms", "lrf", "mar", "so", "dist", "distz", "pkg", "bpk", "dump", "elc", "deploy",
104        "exe", "dll", "deb", "dmg", "iso", "img", "msi", "msp", "msm", "buffer"
105    ]
106    .iter()
107    .copied()));
108    assert!(extensions("application/cat").is_none());
109
110    assert!(extensions2("application/octet-stream").eq([
111        "bin", "dms", "lrf", "mar", "so", "dist", "distz", "pkg", "bpk", "dump", "elc", "deploy",
112        "exe", "dll", "deb", "dmg", "iso", "img", "msi", "msp", "msm", "buffer"
113    ]
114    .iter()
115    .copied()));
116    assert!(extensions2("application/cat").next().is_none());
117    assert_eq!(extensions2("application/cat").size_hint(), (0, Some(0)));
118    assert_eq!(extensions2("application/cat").count(), 0);
119    assert_eq!(extensions2("").count(), 0);
120
121    assert_eq!(extension("application/octet-stream").unwrap(), "bin");
122}