hf2q 0.1.7

Pure Rust CLI for converting HuggingFace models to hardware-optimized formats and serving them over an OpenAI-compatible API on Apple Silicon
use std::fmt;

use serde::Serialize;

const MAX_RELEASE_VERSION_BYTES: usize = 64;
const MAX_BUNDLE_PATH_BYTES: usize = 512;

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SchemaValueError {
    pub(crate) field: &'static str,
    pub(crate) reason: String,
}

impl SchemaValueError {
    pub(crate) fn new(field: &'static str, reason: impl Into<String>) -> Self {
        Self {
            field,
            reason: reason.into(),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(transparent)]
pub struct Sha256Digest(String);

impl Sha256Digest {
    pub(crate) fn parse(field: &'static str, value: String) -> Result<Self, SchemaValueError> {
        if value.len() != 64
            || !value
                .bytes()
                .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
        {
            return Err(SchemaValueError::new(
                field,
                "must be exactly 64 lowercase hexadecimal characters",
            ));
        }
        Ok(Self(value))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

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

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(transparent)]
pub struct GitCommit(String);

impl GitCommit {
    pub(crate) fn parse(field: &'static str, value: String) -> Result<Self, SchemaValueError> {
        if value.len() != 40
            || !value
                .bytes()
                .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
        {
            return Err(SchemaValueError::new(
                field,
                "must be exactly 40 lowercase hexadecimal characters",
            ));
        }
        Ok(Self(value))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(transparent)]
pub struct ReleaseVersion(String);

impl ReleaseVersion {
    pub(crate) fn parse_stable(
        field: &'static str,
        value: String,
    ) -> Result<Self, SchemaValueError> {
        if value.is_empty() || value.len() > MAX_RELEASE_VERSION_BYTES {
            return Err(SchemaValueError::new(
                field,
                "must be a bounded semantic version",
            ));
        }
        let parsed = semver::Version::parse(&value)
            .map_err(|_| SchemaValueError::new(field, "must be a canonical semantic version"))?;
        if !parsed.pre.is_empty() || !parsed.build.is_empty() {
            return Err(SchemaValueError::new(
                field,
                "stable releases cannot contain prerelease or build metadata",
            ));
        }
        if parsed.to_string() != value {
            return Err(SchemaValueError::new(
                field,
                "semantic version is not in canonical form",
            ));
        }
        Ok(Self(value))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }

    fn parsed(&self) -> semver::Version {
        semver::Version::parse(&self.0).expect("validated release version")
    }
}

impl PartialOrd for ReleaseVersion {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for ReleaseVersion {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.parsed().cmp(&other.parsed())
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum TargetTriple {
    #[serde(rename = "aarch64-apple-darwin")]
    Aarch64AppleDarwin,
}

impl TargetTriple {
    pub(crate) fn parse(field: &'static str, value: String) -> Result<Self, SchemaValueError> {
        match value.as_str() {
            "aarch64-apple-darwin" => Ok(Self::Aarch64AppleDarwin),
            _ => Err(SchemaValueError::new(
                field,
                "v1 supports only aarch64-apple-darwin",
            )),
        }
    }

    pub fn as_str(self) -> &'static str {
        match self {
            Self::Aarch64AppleDarwin => "aarch64-apple-darwin",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum UpdateChannel {
    #[serde(rename = "stable")]
    Stable,
}

impl UpdateChannel {
    pub(crate) fn parse(field: &'static str, value: String) -> Result<Self, SchemaValueError> {
        match value.as_str() {
            "stable" => Ok(Self::Stable),
            _ => Err(SchemaValueError::new(
                field,
                "v1 supports only the stable channel",
            )),
        }
    }

    pub fn as_str(self) -> &'static str {
        match self {
            Self::Stable => "stable",
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(transparent)]
pub struct MacOsVersion(String);

impl MacOsVersion {
    pub(crate) fn parse(field: &'static str, value: String) -> Result<Self, SchemaValueError> {
        if value.is_empty() || value.len() > 16 {
            return Err(SchemaValueError::new(
                field,
                "must be a bounded major.minor or major.minor.patch version",
            ));
        }
        let parts: Vec<_> = value.split('.').collect();
        if !(2..=3).contains(&parts.len())
            || parts.iter().any(|part| {
                part.is_empty()
                    || !part.bytes().all(|byte| byte.is_ascii_digit())
                    || (part.len() > 1 && part.starts_with('0'))
                    || part.parse::<u16>().is_err()
            })
            || (parts.len() == 3 && parts[2] == "0")
            || parts[0] == "0"
        {
            return Err(SchemaValueError::new(
                field,
                "must be canonical major.minor or nonzero-patch major.minor.patch decimal components",
            ));
        }
        Ok(Self(value))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }

    fn numeric_components(&self) -> impl Iterator<Item = u16> + '_ {
        self.0
            .split('.')
            .map(|part| part.parse::<u16>().expect("validated macOS version"))
            .chain(std::iter::repeat(0))
            .take(3)
    }
}

impl PartialOrd for MacOsVersion {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for MacOsVersion {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.numeric_components().cmp(other.numeric_components())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(transparent)]
pub struct BundlePath(String);

impl BundlePath {
    pub(crate) fn parse(field: &'static str, value: String) -> Result<Self, SchemaValueError> {
        if value.is_empty()
            || value.len() > MAX_BUNDLE_PATH_BYTES
            || !value.is_ascii()
            || value.starts_with('/')
            || value.ends_with('/')
            || value.contains("//")
            || value.contains('\\')
            || value.contains(':')
            || value.bytes().any(|byte| byte.is_ascii_control())
            || !value.bytes().all(|byte| {
                byte.is_ascii_alphanumeric() || matches!(byte, b'/' | b'.' | b'_' | b'-')
            })
            || value.split('/').any(|component| {
                component.is_empty()
                    || component == "."
                    || component == ".."
                    || component.len() > 255
            })
        {
            return Err(SchemaValueError::new(
                field,
                "must be a canonical, safe ASCII bundle-relative path",
            ));
        }

        if value == "release-manifest.json" {
            return Err(SchemaValueError::new(
                field,
                "release-manifest.json is the reserved envelope and is not self-inventoried",
            ));
        }

        let allowed = value == "bin/hf2q"
            || matches!(
                value.as_str(),
                "libexec/serve_qwen38_opencode.sh"
                    | "libexec/serve_qwen36_opencode.sh"
                    | "libexec/serve_gemma4_opencode.sh"
                    | "libexec/serve_deepseek4_opencode.sh"
            )
            || value
                .strip_prefix("share/doc/hf2q/")
                .is_some_and(|suffix| !suffix.is_empty())
            || value
                .strip_prefix("share/licenses/hf2q/")
                .is_some_and(|suffix| !suffix.is_empty());
        if !allowed {
            return Err(SchemaValueError::new(
                field,
                "path is outside the v1 release-bundle whitelist",
            ));
        }

        Ok(Self(value))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize, Serialize)]
pub enum BundleEntryType {
    #[serde(rename = "regular")]
    Regular,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize, Serialize)]
pub enum FileMode {
    #[serde(rename = "0644")]
    Data,
    #[serde(rename = "0755")]
    Executable,
}

impl FileMode {
    pub fn as_octal(self) -> u32 {
        match self {
            Self::Data => 0o644,
            Self::Executable => 0o755,
        }
    }
}