Skip to content

MIME Type Reference — Rust source

A searchable map of common file extensions to their MIME types - text, image, audio, video, application, font, and multipart. Look up by extension or by type, filter by category, and copy the exact Content-Type string for any format.

This is the Rust implementation — the same logic the interactive tool runs, in a shareable, citable form.

//! MIME / Content-Type reference — pure, deterministic data and lookups, with
//! no external crates (std only).
//!
//! Language: Rust.
//! CosmoDev polyglot showcase port of the `mime-types` tool, ported from
//! `src/lib/mime-types.ts`. Display source — part of CosmoDev's polyglot tool
//! pages.
//!
//! A curated set of common file extensions mapped to their standard MIME types,
//! grouped by the seven top-level media types (RFC 2046): text, image, audio,
//! video, application, font, and multipart. Each entry's [`Category`] always
//! equals the top-level segment of its own `mime`, so the category filter and
//! the Content-Type never disagree.
//!
//! Design note: the table is a `const &[Entry]` of `&'static str` data, so every
//! lookup returns borrowed references — zero allocation per match, and the data
//! lives for the program's lifetime.

/// The seven top-level media types a curated entry can belong to.
///
/// Stored as an enum (rather than the TypeScript string union) so the compiler
/// proves every entry's category is one of the seven known values.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum Category {
    Text,
    Image,
    Audio,
    Video,
    Application,
    Font,
    Multipart,
}

impl Category {
    /// The lowercase slug used in the data table and surfaced to the UI. This
    /// is what a `type:` filter string from the TypeScript API maps onto.
    pub fn as_str(self) -> &'static str {
        match self {
            Category::Text => "text",
            Category::Image => "image",
            Category::Audio => "audio",
            Category::Video => "video",
            Category::Application => "application",
            Category::Font => "font",
            Category::Multipart => "multipart",
        }
    }
}

/// One curated extension ↔ MIME mapping.
///
/// Field renamed from `type` (TypeScript) to `category` because `type` is a
/// reserved keyword in Rust; the semantics are identical.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Entry {
    /// File extension, lowercase, with no leading dot (e.g. `json`).
    pub ext: &'static str,
    /// Full MIME / Content-Type string (e.g. `application/json`).
    pub mime: &'static str,
    /// Top-level media type — matches `mime`'s first segment.
    pub category: Category,
    /// Human-readable format name (e.g. `JSON`).
    pub name: &'static str,
}

/// Display order of the categories (used by the UI filter).
pub const MIME_CATEGORIES: [Category; 7] = [
    Category::Text,
    Category::Image,
    Category::Audio,
    Category::Video,
    Category::Application,
    Category::Font,
    Category::Multipart,
];

/// The curated extension ↔ MIME table. One [`Entry`] is one (extension, type)
/// pair, so an extension with several MIME types (e.g. `.js`) appears more than
/// once, and a MIME with several extensions (e.g. `image/jpeg`) appears once
/// per extension. Values follow IANA / MDN conventions.
pub const MIME: &[Entry] = &[
    // ── text ──────────────────────────────────────────────────────────────
    Entry { ext: "txt", mime: "text/plain", category: Category::Text, name: "Plain text" },
    Entry { ext: "html", mime: "text/html", category: Category::Text, name: "HTML document" },
    Entry { ext: "htm", mime: "text/html", category: Category::Text, name: "HTML document" },
    Entry { ext: "xhtml", mime: "application/xhtml+xml", category: Category::Application, name: "XHTML" },
    Entry { ext: "css", mime: "text/css", category: Category::Text, name: "CSS stylesheet" },
    Entry { ext: "csv", mime: "text/csv", category: Category::Text, name: "Comma-separated values" },
    Entry { ext: "xml", mime: "application/xml", category: Category::Application, name: "XML document" },
    Entry { ext: "js", mime: "text/javascript", category: Category::Text, name: "JavaScript" },
    Entry { ext: "js", mime: "application/javascript", category: Category::Application, name: "JavaScript (legacy)" },
    Entry { ext: "mjs", mime: "text/javascript", category: Category::Text, name: "JavaScript module" },
    Entry { ext: "json", mime: "application/json", category: Category::Application, name: "JSON" },
    Entry { ext: "md", mime: "text/markdown", category: Category::Text, name: "Markdown" },
    Entry { ext: "yaml", mime: "text/yaml", category: Category::Text, name: "YAML" },
    Entry { ext: "yml", mime: "text/yaml", category: Category::Text, name: "YAML" },
    Entry { ext: "ics", mime: "text/calendar", category: Category::Text, name: "iCalendar" },
    Entry { ext: "rtf", mime: "application/rtf", category: Category::Application, name: "Rich text" },
    Entry { ext: "tsv", mime: "text/tab-separated-values", category: Category::Text, name: "Tab-separated values" },

    // ── image ─────────────────────────────────────────────────────────────
    Entry { ext: "jpg", mime: "image/jpeg", category: Category::Image, name: "JPEG image" },
    Entry { ext: "jpeg", mime: "image/jpeg", category: Category::Image, name: "JPEG image" },
    Entry { ext: "png", mime: "image/png", category: Category::Image, name: "PNG image" },
    Entry { ext: "gif", mime: "image/gif", category: Category::Image, name: "GIF image" },
    Entry { ext: "webp", mime: "image/webp", category: Category::Image, name: "WebP image" },
    Entry { ext: "avif", mime: "image/avif", category: Category::Image, name: "AVIF image" },
    Entry { ext: "svg", mime: "image/svg+xml", category: Category::Image, name: "SVG image" },
    Entry { ext: "bmp", mime: "image/bmp", category: Category::Image, name: "Bitmap image" },
    Entry { ext: "ico", mime: "image/x-icon", category: Category::Image, name: "Icon" },
    Entry { ext: "tif", mime: "image/tiff", category: Category::Image, name: "TIFF image" },
    Entry { ext: "tiff", mime: "image/tiff", category: Category::Image, name: "TIFF image" },
    Entry { ext: "heic", mime: "image/heic", category: Category::Image, name: "HEIC image" },

    // ── audio ─────────────────────────────────────────────────────────────
    Entry { ext: "mp3", mime: "audio/mpeg", category: Category::Audio, name: "MP3 audio" },
    Entry { ext: "wav", mime: "audio/wav", category: Category::Audio, name: "WAV audio" },
    Entry { ext: "ogg", mime: "audio/ogg", category: Category::Audio, name: "Ogg audio" },
    Entry { ext: "oga", mime: "audio/ogg", category: Category::Audio, name: "Ogg audio" },
    Entry { ext: "flac", mime: "audio/flac", category: Category::Audio, name: "FLAC audio" },
    Entry { ext: "aac", mime: "audio/aac", category: Category::Audio, name: "AAC audio" },
    Entry { ext: "m4a", mime: "audio/mp4", category: Category::Audio, name: "M4A audio" },
    Entry { ext: "weba", mime: "audio/webm", category: Category::Audio, name: "WebM audio" },
    Entry { ext: "mid", mime: "audio/midi", category: Category::Audio, name: "MIDI" },
    Entry { ext: "midi", mime: "audio/midi", category: Category::Audio, name: "MIDI" },

    // ── video ─────────────────────────────────────────────────────────────
    Entry { ext: "mp4", mime: "video/mp4", category: Category::Video, name: "MP4 video" },
    Entry { ext: "webm", mime: "video/webm", category: Category::Video, name: "WebM video" },
    Entry { ext: "avi", mime: "video/x-msvideo", category: Category::Video, name: "AVI video" },
    Entry { ext: "mov", mime: "video/quicktime", category: Category::Video, name: "QuickTime video" },
    Entry { ext: "mkv", mime: "video/x-matroska", category: Category::Video, name: "Matroska video" },
    Entry { ext: "mpeg", mime: "video/mpeg", category: Category::Video, name: "MPEG video" },
    Entry { ext: "mpg", mime: "video/mpeg", category: Category::Video, name: "MPEG video" },
    Entry { ext: "ogv", mime: "video/ogg", category: Category::Video, name: "Ogg video" },
    Entry { ext: "m4v", mime: "video/mp4", category: Category::Video, name: "M4V video" },

    // ── application ───────────────────────────────────────────────────────
    Entry { ext: "pdf", mime: "application/pdf", category: Category::Application, name: "PDF document" },
    Entry { ext: "zip", mime: "application/zip", category: Category::Application, name: "ZIP archive" },
    Entry { ext: "gz", mime: "application/gzip", category: Category::Application, name: "Gzip archive" },
    Entry { ext: "tar", mime: "application/x-tar", category: Category::Application, name: "Tar archive" },
    Entry { ext: "rar", mime: "application/vnd.rar", category: Category::Application, name: "RAR archive" },
    Entry { ext: "7z", mime: "application/x-7z-compressed", category: Category::Application, name: "7-Zip archive" },
    Entry { ext: "bin", mime: "application/octet-stream", category: Category::Application, name: "Binary data" },
    Entry { ext: "exe", mime: "application/x-msdownload", category: Category::Application, name: "Windows executable" },
    Entry { ext: "jar", mime: "application/java-archive", category: Category::Application, name: "Java archive" },
    Entry { ext: "wasm", mime: "application/wasm", category: Category::Application, name: "WebAssembly" },
    Entry { ext: "doc", mime: "application/msword", category: Category::Application, name: "Word document" },
    Entry { ext: "docx", mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", category: Category::Application, name: "Word document (OOXML)" },
    Entry { ext: "xls", mime: "application/vnd.ms-excel", category: Category::Application, name: "Excel spreadsheet" },
    Entry { ext: "xlsx", mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", category: Category::Application, name: "Excel spreadsheet (OOXML)" },
    Entry { ext: "ppt", mime: "application/vnd.ms-powerpoint", category: Category::Application, name: "PowerPoint" },
    Entry { ext: "pptx", mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation", category: Category::Application, name: "PowerPoint (OOXML)" },

    // ── font ──────────────────────────────────────────────────────────────
    Entry { ext: "woff", mime: "font/woff", category: Category::Font, name: "WOFF font" },
    Entry { ext: "woff2", mime: "font/woff2", category: Category::Font, name: "WOFF2 font" },
    Entry { ext: "ttf", mime: "font/ttf", category: Category::Font, name: "TrueType font" },
    Entry { ext: "otf", mime: "font/otf", category: Category::Font, name: "OpenType font" },
    Entry { ext: "eot", mime: "application/vnd.ms-fontobject", category: Category::Application, name: "Embedded OpenType" },

    // ── multipart ─────────────────────────────────────────────────────────
    Entry { ext: "mht", mime: "multipart/related", category: Category::Multipart, name: "MHTML web archive" },
    Entry { ext: "mhtml", mime: "multipart/related", category: Category::Multipart, name: "MHTML web archive" },
];

/// Lowercase an extension and drop a leading dot so `.JSON` and `json` match.
/// `trim_start_matches('.')` removes one or more leading dots, matching the
/// TypeScript `/^\.+/` regex.
pub fn normalize_ext(ext: &str) -> String {
    ext.trim().to_ascii_lowercase().trim_start_matches('.')
}

/// Find every entry whose extension matches. The dot is optional and matching
/// is case-insensitive, so `.js`, `JS`, and `js` are equivalent. Returns an
/// empty vector for an unknown/empty extension.
pub fn lookup_by_ext(ext: &str) -> Vec<&'static Entry> {
    let e = normalize_ext(ext);
    if e.is_empty() {
        return Vec::new();
    }
    MIME.iter().filter(|m| m.ext == e).collect()
}

/// Find every entry whose MIME type matches, case-insensitively (also tolerating
/// surrounding whitespace). One MIME commonly maps to several extensions — e.g.
/// `image/jpeg` → `jpg`, `jpeg` — so all are returned. Empty for unknown/empty.
pub fn lookup_by_mime(mime: &str) -> Vec<&'static Entry> {
    let m = mime.trim().to_ascii_lowercase();
    if m.is_empty() {
        return Vec::new();
    }
    MIME.iter().filter(|e| e.mime.eq_ignore_ascii_case(&m)).collect()
}

/// Optional constraints for [`filter`]. `None` fields are ignored, mirroring
/// `undefined` fields in the TypeScript source.
#[derive(Clone, Debug, Default)]
pub struct Filter {
    /// Constrain to a single top-level category. `None` = any category.
    pub category: Option<Category>,
    /// Free text matched (case-insensitively) against ext, mime, and name.
    pub query: Option<String>,
}

/// Filter the full table by category and/or a free-text query. Omitted options
/// are ignored; an empty query returns the category-filtered set unchanged.
///
/// Fidelity note: the TypeScript variant treats an unknown `type:` string as a
/// real filter that matches nothing. Because [`Category`] is a closed enum,
/// "unknown" cannot be expressed here — the compiler forbids it — so the
/// guarantee is upheld trivially. Callers holding a runtime string should parse
/// it into `Option<Category>` first; an unparseable value should be mapped to a
/// no-match (empty result), not to `None`, to preserve the original semantics.
pub fn filter(opts: &Filter) -> Vec<&'static Entry> {
    let q = opts
        .query
        .as_deref()
        .map(|q| q.trim().to_ascii_lowercase())
        .unwrap_or_default();

    MIME.iter()
        .filter(|e| {
            if let Some(cat) = opts.category {
                if e.category != cat {
                    return false;
                }
            }
            if !q.is_empty() {
                return e.ext.to_ascii_lowercase().contains(q.as_str())
                    || e.mime.to_ascii_lowercase().contains(q.as_str())
                    || e.name.to_ascii_lowercase().contains(q.as_str());
            }
            true
        })
        .collect()
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →