use thiserror::Error;
use crate::vault::VaultError;
#[derive(Debug, Error)]
pub enum HeadlessError {
#[error("invalid input: {0}")]
InputInvalid(String),
#[error("input exceeds {0} bytes")]
InputTooLarge(usize),
#[error("I/O error: {0}")]
Io(String),
#[error("storage error: {0}")]
Storage(String),
#[error("operation cancelled")]
Aborted,
#[error("no passphrase: use -p or MAGI_PASSPHRASE in non-interactive environments")]
PassphraseUnavailable,
#[error(transparent)]
Db(VaultError),
}
impl From<VaultError> for HeadlessError {
fn from(err: VaultError) -> Self {
match err {
VaultError::PassphraseUnavailable => HeadlessError::PassphraseUnavailable,
VaultError::Aborted => HeadlessError::Aborted,
VaultError::Io(msg) => HeadlessError::Io(msg),
VaultError::Storage(msg) => HeadlessError::Storage(msg),
wrapped @ (VaultError::WrongPassphrase
| VaultError::VaultMetaCorrupt
| VaultError::DbCorrupt { .. }
| VaultError::SecretNotFound(_)
| VaultError::Crypto(_)
| VaultError::WeakPassphrase(_)
| VaultError::ValueTooLarge(_)) => HeadlessError::Db(wrapped),
}
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::HeadlessError;
use crate::vault::VaultError;
#[test]
fn test_input_invalid_display_is_user_facing() {
let e = HeadlessError::InputInvalid("missing prompt".into());
assert!(e.to_string().contains("missing prompt"));
}
#[test]
fn test_input_too_large_includes_the_limit_only() {
let e = HeadlessError::InputTooLarge(10 * 1024 * 1024);
assert!(e.to_string().contains("10485760"));
}
#[test]
fn test_from_vault_passphrase_unavailable_maps_to_dedicated_variant() {
let e: HeadlessError = VaultError::PassphraseUnavailable.into();
assert!(matches!(e, HeadlessError::PassphraseUnavailable));
}
#[test]
fn test_from_vault_wrong_passphrase_is_wrapped_in_db() {
let e: HeadlessError = VaultError::WrongPassphrase.into();
assert!(matches!(e, HeadlessError::Db(VaultError::WrongPassphrase)));
}
#[test]
fn test_from_vault_db_corrupt_is_wrapped_and_leaks_no_secret() {
let e: HeadlessError = VaultError::DbCorrupt {
db_path: PathBuf::from("/tmp/.magi/.magi-rs-memory.db"),
detail: "data present without envelope".into(),
}
.into();
let msg = e.to_string();
assert!(msg.contains("data present without envelope"));
assert!(matches!(e, HeadlessError::Db(VaultError::DbCorrupt { .. })));
}
#[test]
fn test_from_vault_storage_maps_to_dedicated_variant() {
let e: HeadlessError = VaultError::Storage("disk full".into()).into();
assert!(matches!(e, HeadlessError::Storage(_)));
}
}