use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
#[error("storage error: {0}\n hint: check that the data directory is writable and not on a full disk")]
Storage(#[from] StorageError),
#[error("blob store error: {0}")]
Blob(#[from] BlobError),
#[error("config error: {0}")]
Config(#[from] ConfigError),
#[error("bundle error: {0}")]
Bundle(#[from] BundleError),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum StorageError {
#[error("sqlite: {0}")]
Sqlite(#[from] rusqlite::Error),
#[error("cannot open database at {path:?}: {source}")]
Open {
path: PathBuf,
source: std::io::Error,
},
#[error("migration {version} failed: {source}")]
Migration {
version: i64,
source: rusqlite::Error,
},
#[error("session not resolvable: {0}")]
Resolve(#[from] ResolveError),
#[error("session {0} not found\n hint: run `hh list` to see recorded sessions")]
NotFound(String),
#[error("blob {0} referenced by event but missing from disk")]
MissingBlob(String),
#[error("session {0} is still recording\n hint: wait for the recording to finish (`hh list` shows status), then re-run `hh redact`")]
StillRecording(String),
#[error("the writer task is closed (it may have crashed; check stderr for prior errors)")]
WriterClosed,
#[error("the writer task panicked")]
WriterPanic,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ResolveError {
#[error("ambiguous session id `{prefix}` matches {count} sessions:\n{candidates}\n hint: use a longer prefix or the full id")]
Ambiguous {
prefix: String,
count: usize,
candidates: String,
},
#[error("no sessions recorded yet\n hint: run `hh run -- <command>` to record one")]
Empty,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum BlobError {
#[error("blob io error at {path:?}: {source}")]
Io {
path: PathBuf,
source: std::io::Error,
},
#[error("zstd: {0}")]
Zstd(String),
#[error("blob hash mismatch: expected {expected}, got {actual}")]
HashMismatch {
expected: String,
actual: String,
},
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ConfigError {
#[error("cannot parse config file {path:?}: {source}")]
Parse {
path: PathBuf,
source: toml::de::Error,
},
#[error("invalid config value: {0}")]
Value(String),
#[error("cannot read config file {path:?}: {source}")]
Read {
path: PathBuf,
source: std::io::Error,
},
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum BundleError {
#[error("could not decompress bundle: {0}\n hint: the file may be truncated or not an `hh` bundle at all")]
Zstd(String),
#[error("malformed bundle archive: {0}")]
Tar(String),
#[error("could not read bundle manifest: {0}")]
Manifest(String),
#[error("bundle format version {found} is newer than the highest version this build of hh supports ({max})\n hint: upgrade hh, then retry `hh import`")]
UnsupportedVersion {
found: u32,
max: u32,
},
#[error("could not read bundle events: {0}")]
Events(String),
#[error("bundle blob hash mismatch: expected {expected}, got {actual}\n hint: the bundle is corrupt or was tampered with — re-export it")]
HashMismatch {
expected: String,
actual: String,
},
#[error("bundle is missing blob {0}, referenced by an event\n hint: the bundle is corrupt or incomplete — re-export it")]
MissingBlob(String),
#[error("bundle events.ndjson does not match its recorded integrity hash\n hint: the bundle is corrupt or was tampered with — re-export it")]
IntegrityMismatch,
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<rusqlite::Error> for Error {
fn from(e: rusqlite::Error) -> Self {
Self::Storage(StorageError::from(e))
}
}
impl From<ResolveError> for Error {
fn from(e: ResolveError) -> Self {
Self::Storage(StorageError::from(e))
}
}