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 = "ed[..close];
442 let rest = "ed[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}