//! Error types for STAR-lite. //! //! The top-level [`enum@Error`] groups failures by phase. Each phase has its //! own detailed enum so callers can match precisely on what went wrong without //! parsing strings. use std::io; use star_repo::Cid; use thiserror::Error; pub type Result = std::result::Result; /// Top-level error for all crate operations. #[derive(Debug, Error)] pub enum Error { #[error("io: {0}")] Io(#[from] io::Error), #[error("star-lite framing: {0}")] Format(#[from] FormatError), #[error("repo: {0}")] Repo(#[from] star_repo::Error), #[error("storage: {0}")] Storage(#[from] StorageError), #[error("verify: {0}")] Verify(#[from] VerifyError), } // `?` shortcuts so call sites can return star-repo error subtypes directly // instead of double-wrapping through `star_repo::Error`. impl From for Error { fn from(e: star_repo::error::MstError) -> Self { Error::Repo(e.into()) } } impl From for Error { fn from(e: star_repo::error::CommitError) -> Self { Error::Repo(e.into()) } } /// Errors specific to the on-disk STAR-lite framing. #[derive(Debug, Error)] pub enum FormatError { #[error("not a STAR-lite file: bad or missing magic bytes")] InvalidMagic, #[error( "invalid header CID: must start with 0x01711220 (CIDv1, dag-cbor, sha-256) and be 36 bytes" )] InvalidHeaderCid, #[error("unexpected end of file")] UnexpectedEof, #[error("invalid utf-8 in key")] InvalidUtf8(#[from] std::str::Utf8Error), #[error("keys out of order: {prev:?} must sort strictly before {curr:?}")] KeyOrder { prev: String, curr: String }, #[error("declared size {actual} exceeds limit {limit} for field {field}")] SizeLimit { field: &'static str, actual: u64, limit: u64, }, #[error("writer used incorrectly: {0}")] BadWriterState(&'static str), #[error("empty key is not allowed")] EmptyKey, #[error("unknown archive kind byte {0:#04x}; expected 'C' (0x43) or 'L' (0x4c)")] UnknownKind(u8), #[error( "archive kind mismatch: this operation needs kind {expected:?} but the archive is kind {actual:?}", expected = *expected as char, actual = *actual as char )] KindMismatch { expected: u8, actual: u8 }, #[error("kind {kind:?} cannot be encoded in a v0 header, which has no kind byte", kind = *kind as char)] KindNotRepresentable { kind: u8 }, } /// Errors from the spilling byte storage. #[derive(Debug, Error)] pub enum StorageError { #[error("storage io: {0}")] Io(#[from] io::Error), #[error("range [{offset}, {offset}+{length}) exceeds storage size {total}")] OutOfRange { offset: u64, length: u64, total: u64, }, } /// Errors from archive verification. #[derive(Debug, Error)] pub enum VerifyError { #[error("{0}")] RootMismatch(Box), #[error("{0}")] CommitmentMismatch(Box), } /// Payload for [`VerifyError::CommitmentMismatch`]: the LtHash computed over a /// kind `L` archive's contents did not match its header commitment. #[derive(Debug, Error)] #[error("computed lthash commitment {computed} does not match the header commitment {declared}")] pub struct CommitmentMismatch { pub declared: String, pub computed: String, } /// Payload for [`VerifyError::RootMismatch`]. Boxed inside `VerifyError` so /// the `Result` discriminant doesn't carry two CIDs of dead weight on the /// success path. #[derive(Debug, Error)] #[error("computed mst root {computed} does not match commit.data {declared}")] pub struct RootMismatch { pub declared: Cid, pub computed: Cid, }