Skip to main content

media_type/
media_type.rs

1//! Media types (formerly known as MIME types).
2//!
3//! This crate provides the [`MediaType`] type to parse and inspect media types such as
4//! `text/plain; charset=utf-8` or `image/svg+xml`, a curated set of constants for the most common
5//! types, and helpers to guess a media type from a file extension or path.
6//!
7//! # Parsing
8//!
9//! ```
10//! # #[cfg(feature = "alloc")] {
11//! use media_type::MediaType;
12//!
13//! let media_type: MediaType = "text/plain; charset=utf-8".parse().unwrap();
14//! assert_eq!(media_type, media_type::TEXT_PLAIN_UTF_8);
15//! assert_eq!(media_type.type_(), "text");
16//! assert_eq!(media_type.subtype(), "plain");
17//! assert_eq!(media_type.get_parameter("charset"), Some("utf-8"));
18//! # }
19//! ```
20//!
21//! # Guessing from a path or extension
22//!
23//! The file does not have to exist: only its extension is inspected.
24//!
25//! ```
26//! # #[cfg(feature = "std")] {
27//! let guess = media_type::from_path("images/logo.png");
28//! assert_eq!(guess.first(), Some(media_type::IMAGE_PNG));
29//!
30//! let guess = media_type::from_ext("zst");
31//! assert_eq!(guess.first(), Some(media_type::APPLICATION_ZSTD));
32//! # }
33//! ```
34//!
35//! # Parsing a list of media types
36//!
37//! HTTP header fields such as `Accept` or `Content-Type` may contain a comma-separated list of
38//! media types. [`MediaTypeIter`] extracts the valid ones and reports the fragments that could not
39//! be parsed:
40//!
41//! ```
42//! # #[cfg(feature = "alloc")] {
43//! use media_type::MediaTypeIter;
44//!
45//! let header = "text/html, application/json, invalid, text/xml";
46//! let parsed: Vec<_> = MediaTypeIter::new(header).collect();
47//!
48//! assert_eq!(parsed.len(), 4);
49//! assert_eq!(parsed[0].as_ref().unwrap(), &media_type::TEXT_HTML);
50//! assert_eq!(parsed[1].as_ref().unwrap(), &media_type::APPLICATION_JSON);
51//! assert_eq!(*parsed[2].as_ref().unwrap_err(), "invalid");
52//! assert_eq!(parsed[3].as_ref().unwrap(), &media_type::TEXT_XML);
53//! # }
54//! ```
55//!
56//! # Cargo features
57//!
58//! | Feature | Default | Description |
59//! | --- | --- | --- |
60//! | `std` | yes | Enables `std`-only conveniences such as [`from_path`] and [`MediaTypeGuess::from_path`]. Implies `alloc`. |
61//! | `alloc` | yes (via `std`) | Enables the owned, allocating API: parsing, [`FromStr`](core::str::FromStr), [`MediaTypeIter`], [`MediaTypeGuess::first`], formatting, ... Without it, only the constants, the [`MediaType`] views, and the extension lookup are available. |
62//! | `reverse-lookup` | yes | Generates and exposes the media type -> extensions reverse lookup ([`get_extensions`], [`get_media_extensions`], [`get_media_extensions_str`]). Disable it to shrink the binary for size-constrained builds. |
63//!
64//! This crate is `no_std` by default. Disable the default features to use it without `std`:
65//!
66//! - without `alloc`: only the constants, [`MediaType`] views, and the extension lookup are
67//!   available;
68//! - with `alloc`: parsing, formatting, [`MediaTypeIter`], and [`MediaTypeGuess::first`] become
69//!   available;
70//! - with `std`: [`from_path`] and [`MediaTypeGuess::from_path`] are also available.
71
72#![no_std]
73#![forbid(unsafe_code)]
74#![deny(missing_docs)]
75#![deny(missing_debug_implementations)]
76
77#[cfg(any(feature = "alloc", test))]
78extern crate alloc;
79
80#[cfg(any(feature = "std", test))]
81extern crate std;
82
83#[cfg(feature = "alloc")]
84use alloc::string::String;
85use core::{
86    cmp::Ordering,
87    fmt,
88    hash::{Hash, Hasher},
89};
90
91mod media_guess;
92#[cfg(feature = "alloc")]
93mod parse;
94
95#[cfg(feature = "alloc")]
96pub use media_guess::Iter;
97#[cfg(feature = "std")]
98pub use media_guess::from_path;
99pub use media_guess::{IterRaw, MediaTypeGuess, from_ext};
100#[cfg(feature = "reverse-lookup")]
101pub use media_guess::{get_extensions, get_media_extensions, get_media_extensions_str};
102#[cfg(feature = "alloc")]
103pub use parse::{FromStrError, MediaTypeIter};
104
105#[cfg(feature = "alloc")]
106impl core::str::FromStr for MediaType {
107    type Err = FromStrError;
108
109    fn from_str(s: &str) -> Result<MediaType, Self::Err> {
110        parse::parse(s).map_err(FromStrError::new)
111    }
112}
113
114/// The source string of a [`MediaType`].
115#[derive(Clone)]
116enum Source {
117    /// A compile-time known string, used by the crate constants.
118    Static(&'static str),
119    /// An owned string produced while parsing.
120    #[cfg(feature = "alloc")]
121    Dynamic(String),
122}
123
124impl Source {
125    #[inline]
126    fn as_str(&self) -> &str {
127        match self {
128            Source::Static(source) => source,
129            #[cfg(feature = "alloc")]
130            Source::Dynamic(source) => source.as_str(),
131        }
132    }
133}
134
135/// A parsed media type (formerly known as a MIME type).
136///
137/// The type and subtype (including any `+suffix`) are compared case-insensitively, as are the
138/// parameter names and the value of the `charset` parameter. Other parameter values are compared
139/// case-sensitively.
140///
141/// Values of this type are always stored in their canonical form: the type, subtype and parameter
142/// names are lowercase, the parameters are separated by `; `, and parameter values are unquoted.
143///
144/// # Examples
145///
146/// ```
147/// # #[cfg(feature = "alloc")] {
148/// use media_type::MediaType;
149///
150/// let media_type: MediaType = "IMAGE/SVG+XML; Charset=UTF-8".parse().unwrap();
151/// assert_eq!(media_type.type_(), "image");
152/// assert_eq!(media_type.subtype(), "svg");
153/// assert_eq!(media_type.suffix(), Some("xml"));
154/// assert_eq!(media_type.essence(), "image/svg+xml");
155/// assert_eq!(media_type.to_string(), "image/svg+xml; charset=utf-8");
156/// # }
157/// ```
158#[derive(Clone)]
159pub struct MediaType {
160    source: Source,
161}
162
163impl MediaType {
164    /// Creates a media type from a `'static` string without validating it.
165    ///
166    /// The string **must** be a valid media type in canonical lowercase form, otherwise the
167    /// accessors may return unexpected results. Prefer [`MediaType::from_str`](core::str::FromStr)
168    /// for untrusted input.
169    ///
170    /// This is mostly useful to define your own constants:
171    ///
172    /// ```
173    /// use media_type::MediaType;
174    ///
175    /// const TEXT_X_LOG: MediaType = MediaType::from_static("text/x-log");
176    /// assert_eq!(TEXT_X_LOG.subtype(), "x-log");
177    /// ```
178    pub const fn from_static(source: &'static str) -> MediaType {
179        MediaType {
180            source: Source::Static(source),
181        }
182    }
183
184    /// Get the top-level media type for this `MediaType`.
185    ///
186    /// # Examples
187    ///
188    /// ```
189    /// assert_eq!(media_type::TEXT_PLAIN.type_(), "text");
190    /// assert_eq!(media_type::TEXT_PLAIN.type_(), media_type::TEXT);
191    /// ```
192    #[inline]
193    pub fn type_(&self) -> &str {
194        let source = self.source.as_str();
195        &source[..source.find('/').unwrap_or(source.len())]
196    }
197
198    /// Get the subtype of this `MediaType`, without its `+suffix`.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// # #[cfg(feature = "alloc")] {
204    /// use media_type::MediaType;
205    ///
206    /// assert_eq!(media_type::TEXT_PLAIN.subtype(), "plain");
207    /// let svg: MediaType = "image/svg+xml".parse().unwrap();
208    /// assert_eq!(svg.subtype(), "svg");
209    /// # }
210    /// ```
211    #[inline]
212    pub fn subtype(&self) -> &str {
213        let source = self.source.as_str();
214        let start = source.find('/').map_or(0, |i| i + 1);
215        let rest = &source[start..];
216        let end = rest.find(['+', ';']).unwrap_or(rest.len());
217        &rest[..end]
218    }
219
220    /// Get the optional `+suffix` of this `MediaType`.
221    ///
222    /// # Examples
223    ///
224    /// ```
225    /// # #[cfg(feature = "alloc")] {
226    /// use media_type::MediaType;
227    ///
228    /// assert_eq!(media_type::TEXT_PLAIN.suffix(), None);
229    /// let svg: MediaType = "image/svg+xml".parse().unwrap();
230    /// assert_eq!(svg.suffix(), Some("xml"));
231    /// # }
232    /// ```
233    #[inline]
234    pub fn suffix(&self) -> Option<&str> {
235        let source = self.source.as_str();
236        let slash = source.find('/').unwrap_or(0);
237        let plus = slash + source[slash..].find('+')?;
238        let start = plus + 1;
239        let end = source[start..].find(';').map_or(source.len(), |i| start + i);
240        Some(&source[start..end])
241    }
242
243    /// Get the "essence" of this `MediaType`, that is the type and subtype without any parameter.
244    ///
245    /// See the [WHATWG definition][essence].
246    ///
247    /// [essence]: https://mimesniff.spec.whatwg.org/#mime-type-essence
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// assert_eq!(media_type::TEXT_PLAIN_UTF_8.essence(), "text/plain");
253    /// ```
254    #[inline]
255    pub fn essence(&self) -> &str {
256        let source = self.source.as_str();
257        &source[..source.find(';').unwrap_or(source.len())]
258    }
259
260    /// Return an iterator over the parameters of this `MediaType`.
261    ///
262    /// # Examples
263    ///
264    /// ```
265    /// # #[cfg(feature = "alloc")] {
266    /// use media_type::MediaType;
267    ///
268    /// let media_type: MediaType = "text/plain; charset=utf-8; foo=bar".parse().unwrap();
269    /// let params: Vec<_> = media_type.parameters().collect();
270    /// assert_eq!(params, [("charset", "utf-8"), ("foo", "bar")]);
271    /// # }
272    /// ```
273    #[inline]
274    pub fn parameters(&self) -> Parameters<'_> {
275        Parameters::new(self.source.as_str())
276    }
277
278    /// Look up a parameter by name.
279    ///
280    /// The name is matched case-insensitively. Parameter values are returned verbatim, except for
281    /// the `charset` parameter whose value is lowercased.
282    ///
283    /// # Examples
284    ///
285    /// ```
286    /// assert_eq!(media_type::TEXT_PLAIN_UTF_8.get_parameter("charset"), Some("utf-8"));
287    /// assert_eq!(media_type::TEXT_PLAIN_UTF_8.get_parameter("boundary"), None);
288    /// ```
289    #[inline]
290    pub fn get_parameter<'a>(&'a self, name: &str) -> Option<&'a str> {
291        self.parameters()
292            .find(|(parameter, _)| parameter.eq_ignore_ascii_case(name))
293            .map(|(_, value)| value)
294    }
295}
296
297/// Compare a `MediaType` against a raw media type string.
298fn media_type_eq_str(media_type: &MediaType, other: &str) -> bool {
299    let source = media_type.source.as_str();
300
301    // Fast path: the whole canonical string matches.
302    if source.eq_ignore_ascii_case(other) {
303        return true;
304    }
305
306    // Compare the essence and then each parameter in order.
307    let other_essence = &other[..other.find(';').unwrap_or(other.len())];
308    if !media_type.essence().eq_ignore_ascii_case(other_essence) {
309        return false;
310    }
311
312    let mut left = media_type.parameters();
313    let mut right = Parameters::new(other);
314
315    loop {
316        match (left.next(), right.next()) {
317            (None, None) => return true,
318            (Some((name, value)), Some((other_name, other_value))) => {
319                if !name.eq_ignore_ascii_case(other_name) {
320                    return false;
321                }
322                let insensitive = name.eq_ignore_ascii_case(CHARSET);
323                let equal = if insensitive {
324                    value.eq_ignore_ascii_case(other_value)
325                } else {
326                    value == other_value
327                };
328                if !equal {
329                    return false;
330                }
331            }
332            _ => return false,
333        }
334    }
335}
336
337impl PartialEq for MediaType {
338    #[inline]
339    fn eq(&self, other: &MediaType) -> bool {
340        self.source.as_str() == other.source.as_str()
341    }
342}
343
344impl Eq for MediaType {}
345
346impl PartialOrd for MediaType {
347    #[inline]
348    fn partial_cmp(&self, other: &MediaType) -> Option<Ordering> {
349        Some(self.cmp(other))
350    }
351}
352
353impl Ord for MediaType {
354    #[inline]
355    fn cmp(&self, other: &MediaType) -> Ordering {
356        self.source.as_str().cmp(other.source.as_str())
357    }
358}
359
360impl Hash for MediaType {
361    #[inline]
362    fn hash<H: Hasher>(&self, state: &mut H) {
363        self.source.as_str().hash(state);
364    }
365}
366
367impl PartialEq<&str> for MediaType {
368    #[inline]
369    fn eq(&self, other: &&str) -> bool {
370        media_type_eq_str(self, other)
371    }
372}
373
374impl PartialEq<MediaType> for &str {
375    #[inline]
376    fn eq(&self, other: &MediaType) -> bool {
377        media_type_eq_str(other, self)
378    }
379}
380
381impl AsRef<str> for MediaType {
382    #[inline]
383    fn as_ref(&self) -> &str {
384        self.source.as_str()
385    }
386}
387
388impl fmt::Display for MediaType {
389    #[inline]
390    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
391        formatter.write_str(self.source.as_str())
392    }
393}
394
395impl fmt::Debug for MediaType {
396    #[inline]
397    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
398        fmt::Debug::fmt(self.source.as_str(), formatter)
399    }
400}
401
402/// An iterator over the parameters of a [`MediaType`].
403///
404/// Each item is a `(name, value)` pair. See [`MediaType::parameters`].
405#[derive(Clone)]
406pub struct Parameters<'a> {
407    remaining: Option<&'a str>,
408}
409
410impl<'a> Parameters<'a> {
411    fn new(source: &'a str) -> Self {
412        Self {
413            remaining: source.find(';').map(|index| &source[index + 1..]),
414        }
415    }
416}
417
418impl fmt::Debug for Parameters<'_> {
419    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
420        formatter.debug_struct("Parameters").finish()
421    }
422}
423
424impl<'a> Iterator for Parameters<'a> {
425    type Item = (&'a str, &'a str);
426
427    fn next(&mut self) -> Option<Self::Item> {
428        let remaining = self.remaining?;
429        let remaining = remaining.trim_start_matches([' ', '\t']);
430        if remaining.is_empty() {
431            self.remaining = None;
432            return None;
433        }
434
435        let equals = remaining.find('=')?;
436        let name = remaining[..equals].trim_end();
437        let after_equals = &remaining[equals + 1..];
438
439        if let Some(quoted) = after_equals.strip_prefix('"') {
440            let close = quoted.find('"')?;
441            let value = &quoted[..close];
442            let rest = &quoted[close + 1..];
443            self.remaining = rest.find(';').map(|i| &rest[i + 1..]);
444            Some((name, value))
445        } else {
446            let end = after_equals.find(';').unwrap_or(after_equals.len());
447            let value = after_equals[..end].trim_end();
448            self.remaining = if end < after_equals.len() {
449                Some(&after_equals[end + 1..])
450            } else {
451                None
452            };
453            Some((name, value))
454        }
455    }
456}
457
458macro_rules! names {
459    ($($(#[$meta:meta])* $name:ident = $value:literal;)*) => {
460        $(
461            $(#[$meta])*
462            #[doc = concat!("`", $value, "`")]
463            pub const $name: &str = $value;
464        )*
465    };
466}
467
468macro_rules! media_types {
469    ($($(#[$meta:meta])* $name:ident = $value:literal;)*) => {
470        $(
471            $(#[$meta])*
472            #[doc = concat!("`", $value, "`")]
473            pub const $name: MediaType = MediaType::from_static($value);
474        )*
475    };
476}
477
478names! {
479    /// The `*` wildcard.
480    STAR = "*";
481
482    /// The `text` top-level type.
483    TEXT = "text";
484    /// The `image` top-level type.
485    IMAGE = "image";
486    /// The `audio` top-level type.
487    AUDIO = "audio";
488    /// The `video` top-level type.
489    VIDEO = "video";
490    /// The `application` top-level type.
491    APPLICATION = "application";
492    /// The `multipart` top-level type.
493    MULTIPART = "multipart";
494    /// The `message` top-level type.
495    MESSAGE = "message";
496    /// The `model` top-level type.
497    MODEL = "model";
498    /// The `font` top-level type.
499    FONT = "font";
500
501    /// The `plain` subtype.
502    PLAIN = "plain";
503    /// The `html` subtype.
504    HTML = "html";
505    /// The `xml` subtype or `+xml` suffix.
506    XML = "xml";
507    /// The `javascript` subtype.
508    JAVASCRIPT = "javascript";
509    /// The `css` subtype.
510    CSS = "css";
511    /// The `csv` subtype.
512    CSV = "csv";
513    /// The `event-stream` subtype.
514    EVENT_STREAM = "event-stream";
515    /// The `vcard` subtype.
516    VCARD = "vcard";
517    /// The `markdown` subtype.
518    MARKDOWN = "markdown";
519    /// The `bmp` subtype.
520    BMP = "bmp";
521    /// The `gif` subtype.
522    GIF = "gif";
523    /// The `jpeg` subtype.
524    JPEG = "jpeg";
525    /// The `png` subtype.
526    PNG = "png";
527    /// The `svg` subtype.
528    SVG = "svg";
529    /// The `webp` subtype.
530    WEBP = "webp";
531    /// The `avif` subtype.
532    AVIF = "avif";
533    /// The `json` subtype.
534    JSON = "json";
535    /// The `x-www-form-urlencoded` subtype.
536    WWW_FORM_URLENCODED = "x-www-form-urlencoded";
537    /// The `msgpack` subtype.
538    MSGPACK = "msgpack";
539    /// The `octet-stream` subtype.
540    OCTET_STREAM = "octet-stream";
541    /// The `pdf` subtype.
542    PDF = "pdf";
543    /// The `zstd` subtype.
544    ZSTD = "zstd";
545    /// The `brotli` subtype.
546    BROTLI = "brotli";
547    /// The `gzip` subtype.
548    GZIP = "gzip";
549    /// The `x-tar` subtype.
550    X_TAR = "x-tar";
551    /// The `zip` subtype.
552    ZIP = "zip";
553    /// The `x-7z-compressed` subtype.
554    X_7Z_COMPRESSED = "x-7z-compressed";
555    /// The `wasm` subtype.
556    WASM = "wasm";
557    /// The `protobuf` subtype.
558    PROTOBUF = "protobuf";
559    /// The `yaml` subtype.
560    YAML = "yaml";
561    /// The `woff` subtype.
562    WOFF = "woff";
563    /// The `woff2` subtype.
564    WOFF2 = "woff2";
565    /// The `ttf` subtype.
566    TTF = "ttf";
567    /// The `otf` subtype.
568    OTF = "otf";
569    /// The `form-data` subtype.
570    FORM_DATA = "form-data";
571    /// The `mpeg` subtype.
572    MPEG = "mpeg";
573    /// The `mp4` subtype.
574    MP4 = "mp4";
575    /// The `ogg` subtype.
576    OGG = "ogg";
577    /// The `opus` subtype.
578    OPUS = "opus";
579    /// The `flac` subtype.
580    FLAC = "flac";
581    /// The `webm` subtype.
582    WEBM = "webm";
583
584    /// The `charset` parameter name.
585    CHARSET = "charset";
586    /// The `boundary` parameter name.
587    BOUNDARY = "boundary";
588    /// The `utf-8` charset value.
589    UTF_8 = "utf-8";
590}
591
592media_types! {
593    /// `*/*`
594    STAR_STAR = "*/*";
595
596    /// `text/*`
597    TEXT_STAR = "text/*";
598    /// `text/plain`
599    TEXT_PLAIN = "text/plain";
600    /// `text/plain; charset=utf-8`
601    TEXT_PLAIN_UTF_8 = "text/plain; charset=utf-8";
602    /// `text/html`
603    TEXT_HTML = "text/html";
604    /// `text/html; charset=utf-8`
605    TEXT_HTML_UTF_8 = "text/html; charset=utf-8";
606    /// `text/css`
607    TEXT_CSS = "text/css";
608    /// `text/css; charset=utf-8`
609    TEXT_CSS_UTF_8 = "text/css; charset=utf-8";
610    /// `text/javascript`
611    TEXT_JAVASCRIPT = "text/javascript";
612    /// `text/xml`
613    TEXT_XML = "text/xml";
614    /// `text/event-stream`
615    TEXT_EVENT_STREAM = "text/event-stream";
616    /// `text/csv`
617    TEXT_CSV = "text/csv";
618    /// `text/csv; charset=utf-8`
619    TEXT_CSV_UTF_8 = "text/csv; charset=utf-8";
620    /// `text/tab-separated-values`
621    TEXT_TAB_SEPARATED_VALUES = "text/tab-separated-values";
622    /// `text/tab-separated-values; charset=utf-8`
623    TEXT_TAB_SEPARATED_VALUES_UTF_8 = "text/tab-separated-values; charset=utf-8";
624    /// `text/vcard`
625    TEXT_VCARD = "text/vcard";
626    /// `text/markdown`
627    TEXT_MARKDOWN = "text/markdown";
628
629    /// `image/*`
630    IMAGE_STAR = "image/*";
631    /// `image/jpeg`
632    IMAGE_JPEG = "image/jpeg";
633    /// `image/gif`
634    IMAGE_GIF = "image/gif";
635    /// `image/png`
636    IMAGE_PNG = "image/png";
637    /// `image/bmp`
638    IMAGE_BMP = "image/bmp";
639    /// `image/svg+xml`
640    IMAGE_SVG = "image/svg+xml";
641    /// `image/webp`
642    IMAGE_WEBP = "image/webp";
643    /// `image/avif`
644    IMAGE_AVIF = "image/avif";
645
646    /// `font/woff`
647    FONT_WOFF = "font/woff";
648    /// `font/woff2`
649    FONT_WOFF2 = "font/woff2";
650    /// `font/ttf`
651    FONT_TTF = "font/ttf";
652    /// `font/otf`
653    FONT_OTF = "font/otf";
654
655    /// `application/json`
656    APPLICATION_JSON = "application/json";
657    /// `application/javascript`
658    APPLICATION_JAVASCRIPT = "application/javascript";
659    /// `application/javascript; charset=utf-8`
660    APPLICATION_JAVASCRIPT_UTF_8 = "application/javascript; charset=utf-8";
661    /// `application/x-www-form-urlencoded`
662    APPLICATION_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded";
663    /// `application/octet-stream`
664    APPLICATION_OCTET_STREAM = "application/octet-stream";
665    /// `application/msgpack`
666    APPLICATION_MSGPACK = "application/msgpack";
667    /// `application/pdf`
668    APPLICATION_PDF = "application/pdf";
669    /// `application/zstd`
670    APPLICATION_ZSTD = "application/zstd";
671    /// `application/brotli`
672    APPLICATION_BROTLI = "application/brotli";
673    /// `application/gzip`
674    APPLICATION_GZIP = "application/gzip";
675    /// `application/x-tar`
676    APPLICATION_X_TAR = "application/x-tar";
677    /// `application/zip`
678    APPLICATION_ZIP = "application/zip";
679    /// `application/x-7z-compressed`
680    APPLICATION_X_7Z_COMPRESSED = "application/x-7z-compressed";
681    /// `application/wasm`
682    APPLICATION_WASM = "application/wasm";
683    /// `application/protobuf`
684    APPLICATION_PROTOBUF = "application/protobuf";
685    /// `application/yaml`
686    APPLICATION_YAML = "application/yaml";
687
688    /// `multipart/form-data`
689    MULTIPART_FORM_DATA = "multipart/form-data";
690
691    /// `audio/opus`
692    AUDIO_OPUS = "audio/opus";
693    /// `audio/flac`
694    AUDIO_FLAC = "audio/flac";
695
696    /// `video/mp4`
697    VIDEO_MP4 = "video/mp4";
698    /// `video/webm`
699    VIDEO_WEBM = "video/webm";
700}
701
702#[cfg(all(test, feature = "alloc"))]
703mod tests {
704    use alloc::string::ToString;
705
706    use super::*;
707
708    #[test]
709    fn test_type_and_subtype() {
710        assert_eq!(TEXT_PLAIN.type_(), TEXT);
711        assert_eq!(TEXT_PLAIN.subtype(), PLAIN);
712        assert_eq!(TEXT_PLAIN_UTF_8.subtype(), PLAIN);
713        let svg: MediaType = "image/svg+xml".parse().unwrap();
714        assert_eq!(svg.type_(), IMAGE);
715        assert_eq!(svg.subtype(), SVG);
716        assert_eq!(svg.suffix(), Some(XML));
717        assert_eq!(TEXT_PLAIN.suffix(), None);
718    }
719
720    #[test]
721    fn test_essence() {
722        assert_eq!(TEXT_PLAIN.essence(), "text/plain");
723        assert_eq!(TEXT_PLAIN_UTF_8.essence(), "text/plain");
724        assert_eq!(IMAGE_SVG.essence(), "image/svg+xml");
725    }
726
727    #[test]
728    fn test_get_parameter() {
729        assert_eq!(TEXT_PLAIN.get_parameter("charset"), None);
730        assert_eq!(TEXT_PLAIN_UTF_8.get_parameter(CHARSET), Some(UTF_8));
731
732        let media_type: MediaType = "text/plain; charset=utf-8; foo=bar".parse().unwrap();
733        assert_eq!(media_type.get_parameter("CHARSET"), Some("utf-8"));
734        assert_eq!(media_type.get_parameter("foo"), Some("bar"));
735        assert_eq!(media_type.get_parameter("baz"), None);
736    }
737
738    #[test]
739    fn test_case_sensitive_parameter_values() {
740        let media_type: MediaType = "multipart/form-data; charset=BASE64; boundary=ABCDEFG".parse().unwrap();
741        assert_eq!(media_type.get_parameter(CHARSET), Some("base64"));
742        assert_eq!(media_type.get_parameter(BOUNDARY), Some("ABCDEFG"));
743        assert_ne!(media_type.get_parameter(BOUNDARY), Some("abcdefg"));
744    }
745
746    #[test]
747    fn test_equality() {
748        assert_eq!("TEXT/PLAIN".parse::<MediaType>().unwrap(), TEXT_PLAIN);
749        assert_eq!("text/plain;charset=utf-8".parse::<MediaType>().unwrap(), TEXT_PLAIN_UTF_8);
750        assert_eq!("text/plain; charset=\"utf-8\"".parse::<MediaType>().unwrap(), TEXT_PLAIN_UTF_8);
751        assert_eq!(TEXT_PLAIN, "text/PLAIN");
752        assert_eq!("text/plain", TEXT_PLAIN);
753        assert_eq!(TEXT_PLAIN_UTF_8, "text/plain;charset=UTF-8");
754        assert_eq!(STAR_STAR, "*/*");
755        assert_eq!("image/*".parse::<MediaType>().unwrap(), IMAGE_STAR);
756    }
757
758    #[test]
759    fn test_display() {
760        assert_eq!(TEXT_PLAIN.to_string(), "text/plain");
761        assert_eq!(TEXT_PLAIN_UTF_8.to_string(), "text/plain; charset=utf-8");
762    }
763
764    #[test]
765    fn test_from_static_constant() {
766        const TEXT_X_LOG: MediaType = MediaType::from_static("text/x-log");
767        assert_eq!(TEXT_X_LOG.subtype(), "x-log");
768        assert_eq!(TEXT_X_LOG.essence(), "text/x-log");
769    }
770}