modelshelf 0.1.0

A shared local LLM model registry: discover, deduplicate, download, and update models across desktop apps.
Documentation
//! Error types with stable, FFI-friendly discriminant codes.

use std::path::PathBuf;

/// Stable, FFI-friendly discriminant for [`Error`].
///
/// The numeric values are part of the public contract once 1.0 is reached:
/// language bindings map errors to these codes.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
#[non_exhaustive]
pub enum ErrorKind {
    /// The registry lock could not be acquired within the timeout.
    RegistryBusy = 2,
    /// A model matching the query/spec was not found.
    NotFound = 3,
    /// `registry.json` exists but could not be parsed.
    RegistryCorrupt = 4,
    /// The registry was written by a newer, unsupported schema version.
    UnsupportedSchema = 5,
    /// A model spec string could not be parsed.
    InvalidSpec = 6,
    /// A network or remote-API failure.
    Network = 7,
    /// Downloaded or scanned content did not match the expected hash.
    HashMismatch = 8,
    /// An operation was refused for safety reasons (see message).
    Refused = 9,
    /// An underlying filesystem error.
    Io = 10,
    /// Anything else.
    Other = 1,
}

/// The error type returned by all fallible modelshelf operations.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
    /// The registry lock could not be acquired within the configured timeout.
    #[error("registry is busy: could not acquire lock on {path} within {timeout_ms} ms")]
    RegistryBusy {
        /// Path of the lock file.
        path: PathBuf,
        /// How long we waited.
        timeout_ms: u64,
    },

    /// `registry.json` exists but could not be parsed. The corrupt file has
    /// been preserved at `quarantined_to`; the registry is NOT rebuilt
    /// automatically.
    #[error("registry file {path} is corrupt (preserved at {quarantined_to}): {reason}")]
    RegistryCorrupt {
        /// Path of the unreadable registry file.
        path: PathBuf,
        /// Where the corrupt copy was preserved.
        quarantined_to: PathBuf,
        /// Parser error message.
        reason: String,
    },

    /// The registry was written by a newer schema version than this build
    /// understands. Refusing to touch it protects the newer writer's data.
    #[error("registry schema version {found} is newer than supported version {supported}")]
    UnsupportedSchema {
        /// Version found on disk.
        found: u64,
        /// Highest version this build supports.
        supported: u64,
    },

    /// No model matched the given id, query, or spec.
    #[error("model not found: {0}")]
    NotFound(String),

    /// A model spec string (e.g. `hf:org/repo/file.gguf`) could not be parsed.
    #[error("invalid model spec: {0}")]
    InvalidSpec(String),

    /// A network or remote-API failure.
    #[error("network error: {0}")]
    Network(String),

    /// Content hash verification failed.
    #[error("hash mismatch: expected {expected}, got {actual}")]
    HashMismatch {
        /// The hash we expected.
        expected: String,
        /// The hash we computed.
        actual: String,
    },

    /// The operation was refused for safety reasons.
    #[error("refused: {0}")]
    Refused(String),

    /// An underlying filesystem error, annotated with the path involved.
    #[error("I/O error at {path}: {source}")]
    Io {
        /// The path the operation was acting on.
        path: PathBuf,
        /// The underlying error.
        #[source]
        source: std::io::Error,
    },
}

impl Error {
    /// The stable [`ErrorKind`] code for this error.
    pub fn kind(&self) -> ErrorKind {
        match self {
            Error::RegistryBusy { .. } => ErrorKind::RegistryBusy,
            Error::RegistryCorrupt { .. } => ErrorKind::RegistryCorrupt,
            Error::UnsupportedSchema { .. } => ErrorKind::UnsupportedSchema,
            Error::NotFound(_) => ErrorKind::NotFound,
            Error::InvalidSpec(_) => ErrorKind::InvalidSpec,
            Error::Network(_) => ErrorKind::Network,
            Error::HashMismatch { .. } => ErrorKind::HashMismatch,
            Error::Refused(_) => ErrorKind::Refused,
            Error::Io { .. } => ErrorKind::Io,
        }
    }

    pub(crate) fn io(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
        Error::Io {
            path: path.into(),
            source,
        }
    }
}

/// Convenience alias used across the crate.
pub type Result<T> = std::result::Result<T, Error>;