bindet 0.3.2

Fast file type detection
Documentation
/// Generates [`TryInto`] implementations for [`crate::FileType`],
/// this is done as a macro instead of regular impl to enable future `no-std` support,
/// and this macros helps by not relying on `Vec<_>` version for preferred mime conversion.
///
/// ## Naming
///
/// In these docs, we always talk about **mime types** and never about **media types**, because they
/// are essentially the same (in this case), but with different crate names.
///
/// However, documentation is generated biased on the underlying crate name and most of the nomenclature
/// depends on the crate nomenclature. This is done on purpose to have a consistent citation of the
/// representation that matches the target type (the transformation result).
///
/// For internal documentation, we always call them **mime types** to avoid confusion, this is not a
/// problem for the conversions since they have separated documentations based on the active feature.
///
/// ## Values
///
/// ### `$into`: `type`
/// Target type to convert to, like `Mime` and `MediaType`.
///
/// When present, this value appears as the [`Result::Ok`] variant of the [`TryInto::try_into`] return,
/// or as the element type of the [`Vec<T>`] type (in supported environments), that also appears as the [`Result::Ok`] variant.
///
/// ### `err`: `type`
/// The error type that may occur when trying to convert.
///
/// This error is not expected to ever happen under normal circumstances, but it may happen in two scenarios:
///
/// - A library error (mostly **bindet** library error) providing malformed mime
/// or mime parameters to the parser library.
/// - Target library being restrictive over the supported mimes (e.g. not supporting arbitrary mimes).
///
/// When present, this error appears as the [`Result::Err`] variant of the [`TryInto::try_into`] return.
///
/// ### `def`: `type`
///
/// The default mime type for cases where there are no corresponding mime types to translate to,
/// it's commonly `application/octet-stream`.
///
/// ### `(opts)`
///
/// #### `singular`: `literal`
///
/// The singular word to use to mention the representation in
/// documentation about the conversion, like `mime` or `mime type`.
///
/// #### `plural`: `literal`
///
/// The plural word to use to mention the representation in
/// documentation about the conversion, like `mimes` or `mime types`.
///
/// #### `title`: `literal`
///
/// The title of the representation, to put in the documentation to explain mime support in browsers,
/// like `Mime` and `Media type`.
///
/// #### `sec`: `literal`
///
/// The corresponding section to the specified title, this is needed since some titles contains spaces,
/// like `Media type`, which needs the section reference to be `media-type`.
///
/// **Note:** we can use another macro (probably procedural) to transform the title, it only happens
/// to not be necessary now.
///
/// ### Conversion syntax
///
/// `(id => main)`
///
/// `(id => main, others...)`
///
/// #### `id`: `identifier`
///
/// The [`crate::FileType`] variant identifier to convert.
///
/// #### `main`: `expression`
///
/// The text representation of the mime type correspondent to the previously provided `id`.
///
/// **This is the preferred mime type and the one that results from a regular [`TryInto::try_into`]
/// call, or appears as the first mime in a conversion to a list of mimes.**
///
/// #### `other`: `expression`
///
/// Additional mimes known to be used for this content, maybe some vendor specific, personal, experimental or
/// unregistered mimes that are supported by some applications, but not standardized.
///
/// Also, some mimes are standardized but browsers still rely on another mime type to present the content,
/// mainly because those mimes happened to be standardized after the widespread use or adoption of the content
/// and browsers still supports both for retro-compatibility, or because the new mime type is only supported
/// for specific codecs (mainly the modern ones), but the older one supports older codecs, and sometimes
/// the new ones.
///
/// In the future, **bindet** may also include additional information about the files, like codecs for
/// audio and video containers, dict sizes, and so on. **\[opt-in\]**
///
/// ## Example:
///
/// ```rust,ignore
/// try_conv! {
///     mime::Mime : mime::FromStrError : "application/octet-stream" => (singular: "mime", plural: "mimes", title: "Mime", sec: "mime");
///     Matroska => "video/webm", "video/x-matroska";
/// }
/// ```
///
#[cfg(any(feature = "mime", feature = "mediatype"))]
macro_rules! try_conv {
    (
        $from:ty;
        $into:ty : $err:ty => (singular: $singular:literal, plural: $plural:literal, title: $title:literal, sec: $sec:literal);
        $def:expr;
        $($id:ident => $main:expr $(,$other:expr)*;)*
    ) => {

        #[doc = concat!("
Provides conversion from [`", stringify!($from), "`] to preferred [", $singular, "][", stringify!($into), "].

Read more at: [", $singular, " support info][", stringify!($from), "#", $sec, "-support-info]."
)]
        impl TryInto<$into> for $from {
            type Error = $err;

            #[doc = concat!("
Converts this type into the preferred [", $singular, "][", stringify!($into), "].
            ")]
            fn try_into(self) -> Result<$into, Self::Error> {
                match self {
                    $(<$from>::$id => parse_mime($main),)*
                    _ => parse_mime($def),
                }
            }
        }

        #[doc = concat!("
Provides conversion from [`", stringify!($from), "`] to known [", $plural, "][", stringify!($into), "] that they correspond to.

# ", $title, " support info

Be aware that different media types are supported by different browsers, and some of them,
for example, supports the `x-` prefixed one, and others don't.
This means that you should always test those ", $plural, " and select then based on the browser
support and the `User-Agent` header.

Another case is the [Matroska][", stringify!($from), "::Matroska] ", $plural, ", which some browsers support
as `video/x-matroska` and others as `video/webm`, but the latter is the preferred [", $singular, "][", stringify!($into), "].

Also, the [", $plural, "][", stringify!($into), "] that starts with `x-` (unregistered), `vnd.` or `prs.` are commonly not
**the preferred** [", $singular, "][", stringify!($into), "], so they are *commonly* present at the end of the list, unless
there is no other functional alternative (like any alternative that browsers recognize
and presents or manages the content correctly).

# Preferred ", $plural, "

The preferred [", $singular, "][", stringify!($into), "] is the most common or most browser compatible one,
and this [`TryInto`] implementation returns them in the preferred order, below is a list of preferred browser mimes:

", $(concat!("- [", stringify!($id), "][", stringify!($from), "::", stringify!($id), "]: `", $main, "`\n"),)* "")]
        impl TryInto<Vec<$into>> for $from {
            type Error = $err;
            #[doc = concat!("
Converts this type into known [", $plural, "][", stringify!($into), "].
            ")]
            fn try_into(self) -> Result<Vec<$into>, Self::Error> {
                match self {
                    $(<$from>::$id => parse_mimes(&[$main, $($other,)*]),)*
                    _ => parse_mimes(&[$def]),
                }
            }
        }
    }
}

/// Generates the conversion module alongside with [`TryInto`] implementations through [`try_conv!`] macro,
/// the values of this macro are the same as the [`try_conv!`], but it also receives two additional values:
///
/// ## Values
///
/// ### `feat`: `expression`
///
/// The feature that enables this conversion, for example: `"mime"` and `"mediatype"`.
///
/// ### `name`: `ident`
///
/// The conversion module identifier, example: `mime` and `mediatype`.
///
/// ## Example:
///
/// ```rust,ignore
/// impl_for! {
///     "mime", mime: mime::Mime, mime::FromStrError => (singular: "mime", plural: "mimes", title: "Mime", sec: "mime");
///     "application/octet-stream";
///     Matroska => "video/webm", "video/x-matroska";
/// }
/// ```
///
/// This macro is used internally by [`conv_mod!`].
#[cfg(any(feature = "mime", feature = "mediatype"))]
macro_rules! impl_for {
    (
        $from:ty;
        $feat:expr, $name:ident: $into:ty, $err:ty => (singular: $singular:literal, plural: $plural:literal, title: $title:literal, sec: $sec: literal);
        $def:expr; $($id:ident => $main:expr $(,$other:expr)*;)*
    ) => {

        #[cfg(feature = $feat)]
        mod $name {
            try_conv!{$from; $into : $err => (singular: $singular, plural: $plural, title: $title, sec: $sec); $def; $($id => $main $(,$other)*;)*}

            fn parse_mime(mime_type: &str) -> Result<$into, $err> {
                mime_type.parse()
            }

            fn parse_mimes(mimes: &[&str]) -> Result<Vec<$into>, $err> {
                let mut parsed_mimes = vec![];
                let mut has_ok = false;
                for mime in mimes {
                    let p = mime.parse::<$into>();
                    has_ok |= p.is_ok();
                    parsed_mimes.push(p);
                }

                if has_ok {
                    Ok(parsed_mimes.into_iter().filter_map(|p| p.ok()).collect())
                } else {
                    parsed_mimes.into_iter().collect::<Result<Vec<$into>, _>>()
                }
            }

        }
    };
}

/// Generates conversion modules for multiple features through [`impl_for!`] macro
/// and sharing the same mime representation.
///
/// The values are the same as [`impl_for!`], however, this macro features special syntax for
/// specifying every feature to generate [conversion implementation][TryInto].
///
/// This is done through the `those` declaration, see the [Example][conv_mod!#example].
///
/// # Example
///
/// ```rust,no_run
/// conv_mod! {
///     those {
///         "mime", mime: mime::Mime, mime::FromStrError => (singular: "mime", plural: "mimes", title: "Mime", sec: "mime");
///         "mediatype", mediatype: mediatype::MediaTypeBuf, mediatype::MediaTypeError => (singular: "media type", plural: "media types", title: "Media Type", sec: "media-type");
///     }
///     "application/octet-stream";
///     Png => "image/png";
///     Jpg => "image/jpeg";
/// }
/// ```
///
/// This macro is enabled when `mime` or `mediatype` feature is enabled (or both), and it provides implementation
/// directly to [`crate::FileType`], so if you are looking for the generated implementation documentation,
/// just refer to [`crate::FileType`].
///
#[cfg(any(feature = "mime", feature = "mediatype"))]
macro_rules! conv_mod {
    (
        $from: ty;
        those {
            $($feat:expr, $name:ident: $typ:ty, $err:ty => (singular: $singular:literal, plural: $plural:literal, title: $title:literal, sec: $sec:literal);)+
        }
        $def:expr; $($id:ident => $main:expr $(,$other:expr)*;)*
    ) => {
        conv_mod! {
            @inner
            $from;
            those {
                $($feat, $name: $typ, $err => (singular: $singular, plural: $plural, title: $title, sec: $sec);)*
            }
            $def; $($id => $main $(,$other)*;)*
        }
    };
    (
        @inner
        $from: ty;
        those {}
        $def:expr; $($id:ident => $main:expr $(,$other:expr)*;)*
    ) => {};
    (
        @inner
        $from: ty;
        those {
            $head_feat:expr, $head_name:ident: $head_typ:ty, $head_err:ty => (singular: $head_singular:literal, plural: $head_plural:literal, title: $head_title:literal, sec: $head_sec:literal);
            $($tail_feat:expr, $tail_name:ident: $tail_typ:ty, $tail_err:ty => (singular: $tail_singular:literal, plural: $tail_plural:literal, title: $tail_title:literal, sec: $tail_sec:literal);)*
        }
        $def:expr; $($id:ident => $main:expr $(,$other:expr)*;)*
    ) => {
        impl_for! { $from; $head_feat, $head_name: $head_typ, $head_err => (singular: $head_singular, plural: $head_plural, title: $head_title, sec: $head_sec); $def; $($id => $main $(,$other)*;)* }
        conv_mod! {
            @inner
            $from;
            those {
                $($tail_feat, $tail_name: $tail_typ, $tail_err => (singular: $tail_singular, plural: $tail_plural, title: $tail_title, sec: $tail_sec);)*
            }
            $def; $($id => $main $(,$other)*;)*
        }
    };
}

conv_mod! {
    crate::FileType;
    those {
        "mime", mime: mime::Mime, mime::FromStrError => (singular: "mime", plural: "mimes", title: "Mime", sec: "mime");
        "mediatype", mediatype: mediatype::MediaTypeBuf, mediatype::MediaTypeError => (singular: "media type", plural: "media types", title: "Media Type", sec: "media-type");
    }
    "application/octet-stream";
    Png => "image/png";
    Jpg => "image/jpeg";
    Gif => "image/gif";
    Bmp => "image/bmp";
    Webp => "image/webp";
    Opus => "audio/opus", "audio/ogg";
    Vorbis => "audio/vorbis", "audio/ogg";
    Mp3 => "audio/mpeg", "audio/mpeg3", "audio/mp3", "audio/x-mpeg3";
    Flac => "audio/flac", "audio/x-flac";
    Matroska => "video/webm", "video/x-matroska";
    Wasm => "application/wasm";
    Class => "application/java-vm", "application/x-java-vm";
    Wav => "audio/wav", "audio/x-wav";
    Avi => "video/avi", "video/msvideo", "video/x-msvideo";
    Aiff => "audio/aiff", "audio/x-aiff";
    Tiff => "image/tiff";
    Sqlite3 => "application/vnd.sqlite3", "application/x-sqlite3";
    Ico => "image/x-icon";
    Pdf => "application/pdf", "application/x-pdf";
}

conv_mod! {
    crate::FileRootType;
    those {
        "mime", root_mime: mime::Mime, mime::FromStrError => (singular: "mime", plural: "mimes", title: "Mime", sec: "mime");
        "mediatype", root_mediatype: mediatype::MediaTypeBuf, mediatype::MediaTypeError => (singular: "media type", plural: "media types", title: "Media Type", sec: "media-type");
    }
    "application/octet-stream";
    Png => "image/png";
    Jpg => "image/jpeg";
    Gif => "image/gif";
    Bmp => "image/bmp";
    Webp => "image/webp";
    Ogg => "audio/opus", "audio/ogg";
    Mp3 => "audio/mpeg", "audio/mpeg3", "audio/mp3", "audio/x-mpeg3";
    Flac => "audio/flac", "audio/x-flac";
    Matroska => "video/webm", "video/x-matroska";
    Wasm => "application/wasm";
    Class => "application/java-vm", "application/x-java-vm";
    Wav => "audio/wav", "audio/x-wav";
    Avi => "video/avi", "video/msvideo", "video/x-msvideo";
    Aiff => "audio/aiff", "audio/x-aiff";
    Tiff => "image/tiff";
    Sqlite3 => "application/vnd.sqlite3", "application/x-sqlite3";
    Ico => "image/x-icon";
    Pdf => "application/pdf", "application/x-pdf";
}