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 →