1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
#![no_std]

use core::slice;

mod extensions;
mod types;

pub use extensions::EXTENSIONS;
pub use types::TYPES;

pub fn lookup(extension: impl AsRef<str>) -> Option<&'static str> {
    let extension = extension.as_ref();
    let extension = extension
        .rfind('.')
        .map_or(extension, |i| &extension[i + 1..]);

    EXTENSIONS
        .iter()
        .find(|(ext, _)| *ext == extension)
        .and_then(|(_, i)| TYPES.get(*i).map(|(kind, _, _)| *kind))
}

pub fn extensions(mime_type: impl AsRef<str>) -> Option<impl Iterator<Item = &'static str>> {
    let iter = extensions2(mime_type);
    if iter.size_hint().0 == 0 {
        None
    } else {
        Some(iter)
    }
}

pub fn extensions2(mime_type: impl AsRef<str>) -> ExtensionsIter {
    let mime_type = mime_type.as_ref();

    // easy way to get an empty &'static slice
    const EMPTY: &[(&str, usize)] = &[];

    TYPES
        .iter()
        .find(|(kind, _, _)| *kind == mime_type)
        .map_or_else(
            || ExtensionsIter {
                inner: EMPTY.iter(),
            },
            |(_, start, len)| ExtensionsIter {
                inner: EXTENSIONS[*start..][..*len].iter(),
            },
        )
}

#[inline]
pub fn extension(mime_type: impl AsRef<str>) -> Option<&'static str> {
    extensions2(mime_type).next()
}

#[must_use = "iterators are lazy and do nothing unless consumed"]
#[derive(Debug, Clone)]
pub struct ExtensionsIter {
    // Uses std's implementation of slice Iterator, since it's much more optimized
    // and probably uses unsafe internally to avoid bounds checks.
    inner: slice::Iter<'static, (&'static str, usize)>,
}

impl Iterator for ExtensionsIter {
    type Item = &'static str;

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next().map(|(ext, _)| *ext)
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        self.inner.size_hint()
    }

    #[inline]
    fn count(self) -> usize {
        self.inner.count()
    }
}

#[cfg(test)]
#[test]
fn search() {
    assert_eq!(lookup("json").unwrap(), "application/json");
    assert_eq!(lookup(".md").unwrap(), "text/markdown");
    assert_eq!(lookup("folder/file.js").unwrap(), "application/javascript");
    assert_eq!(lookup("folder/.htaccess"), None);
    assert_eq!(lookup("cats"), None);

    assert!(extensions("application/octet-stream").unwrap().eq([
        "bin", "dms", "lrf", "mar", "so", "dist", "distz", "pkg", "bpk", "dump", "elc", "deploy",
        "exe", "dll", "deb", "dmg", "iso", "img", "msi", "msp", "msm", "buffer"
    ]
    .iter()
    .cloned()));
    assert!(extensions("application/cat").is_none());

    assert!(extensions2("application/octet-stream").eq([
        "bin", "dms", "lrf", "mar", "so", "dist", "distz", "pkg", "bpk", "dump", "elc", "deploy",
        "exe", "dll", "deb", "dmg", "iso", "img", "msi", "msp", "msm", "buffer"
    ]
    .iter()
    .cloned()));
    assert!(extensions2("application/cat").next().is_none());
    assert!(extensions2("application/cat").size_hint() == (0, Some(0)));
    assert!(extensions2("application/cat").count() == 0);

    assert_eq!(extension("application/octet-stream").unwrap(), "bin");
}