use-brotli 0.1.0

Brotli compression labels, extensions, and option metadata for RustUse
Documentation
#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]

//! Brotli labels and option metadata for `RustUse`.

use core::fmt;

/// Common Brotli file extension.
pub const BROTLI_EXTENSION: &str = "br";
/// Common Brotli-compressed tar extension.
pub const TAR_BROTLI_EXTENSION: &str = "tar.br";
/// Common Brotli-related extensions.
pub const BROTLI_EXTENSIONS: &[&str] = &["br", "brotli", "tar.br"];

/// Brotli extension labels.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum BrotliExtension {
    /// `.br` payload.
    Br,
    /// `.tar.br` tar plus Brotli extension.
    TarBr,
    /// Unknown or unsupported Brotli extension label.
    #[default]
    Unknown,
}

impl BrotliExtension {
    /// Returns a stable lowercase label.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Br => "br",
            Self::TarBr => "tar.br",
            Self::Unknown => "unknown",
        }
    }

    /// Detects a Brotli extension label.
    #[must_use]
    pub fn from_extension(extension: &str) -> Self {
        match normalize_extension(extension).as_str() {
            "br" | "brotli" => Self::Br,
            "tar.br" | "tar.brotli" => Self::TarBr,
            _ => Self::Unknown,
        }
    }
}

impl fmt::Display for BrotliExtension {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(self.as_str())
    }
}

/// Brotli compression level labels.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum BrotliLevel {
    /// Prefer fastest compression.
    Fastest,
    /// Balance speed and compressed size.
    #[default]
    Balanced,
    /// Prefer best compressed size.
    Best,
    /// Caller-specified numeric level.
    Numeric(u32),
    /// Unknown or intentionally unspecified level.
    Unknown,
}

impl BrotliLevel {
    /// Returns a stable lowercase label.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Fastest => "fastest",
            Self::Balanced => "balanced",
            Self::Best => "best",
            Self::Numeric(_) => "numeric",
            Self::Unknown => "unknown",
        }
    }

    /// Returns the numeric level when this is [`BrotliLevel::Numeric`].
    #[must_use]
    pub const fn numeric(self) -> Option<u32> {
        match self {
            Self::Numeric(level) => Some(level),
            Self::Fastest | Self::Balanced | Self::Best | Self::Unknown => None,
        }
    }
}

/// High-level Brotli usage profile.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum BrotliProfile {
    /// Generic binary payload profile.
    #[default]
    Generic,
    /// Text-oriented profile.
    Text,
    /// Font-oriented profile.
    Font,
    /// Unknown or intentionally unspecified profile.
    Unknown,
}

impl BrotliProfile {
    /// Returns a stable lowercase label.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Generic => "generic",
            Self::Text => "text",
            Self::Font => "font",
            Self::Unknown => "unknown",
        }
    }
}

/// Returns whether `extension` is a known Brotli extension label.
#[must_use]
pub fn is_brotli_extension(extension: &str) -> bool {
    !matches!(
        BrotliExtension::from_extension(extension),
        BrotliExtension::Unknown
    )
}

/// Returns whether `name` has a known Brotli filename encoding.
#[must_use]
pub fn is_brotli_filename(name: &str) -> bool {
    let parts = filename_parts(name);

    match parts.as_slice() {
        [.., last] if matches!(last.as_str(), "br" | "brotli") => true,
        [.., previous, last] if previous == "tar" && matches!(last.as_str(), "br" | "brotli") => {
            true
        },
        _ => false,
    }
}

fn normalize_extension(extension: &str) -> String {
    extension
        .trim()
        .trim_start_matches('.')
        .to_ascii_lowercase()
}

fn filename_parts(name: &str) -> Vec<String> {
    name.trim()
        .to_ascii_lowercase()
        .rsplit(['/', '\\'])
        .next()
        .unwrap_or_default()
        .trim_start_matches('.')
        .split('.')
        .filter(|part| !part.is_empty())
        .map(str::to_owned)
        .collect()
}

#[cfg(test)]
mod tests {
    use super::{
        BROTLI_EXTENSIONS, BrotliExtension, BrotliLevel, BrotliProfile, is_brotli_extension,
        is_brotli_filename,
    };

    #[test]
    fn detects_brotli_extensions() {
        assert!(is_brotli_extension(".br"));
        assert!(is_brotli_extension("tar.br"));
        assert_eq!(
            BrotliExtension::from_extension("tar.br"),
            BrotliExtension::TarBr
        );
        assert_eq!(BROTLI_EXTENSIONS[0], "br");
    }

    #[test]
    fn detects_brotli_filenames() {
        assert!(is_brotli_filename("release.tar.br"));
        assert!(is_brotli_filename("payload.BR"));
        assert!(!is_brotli_filename("bundle.zip"));
    }

    #[test]
    fn exposes_default_and_unknown_labels() {
        assert_eq!(BrotliExtension::default(), BrotliExtension::Unknown);
        assert_eq!(BrotliLevel::default().as_str(), "balanced");
        assert_eq!(BrotliLevel::Numeric(11).numeric(), Some(11));
        assert_eq!(BrotliProfile::Unknown.as_str(), "unknown");
    }
}