use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("crypto error: {0}")]
Crypto(lupine_core::Error),
#[error("invalid SPIFFE ID: {0}")]
InvalidSpiffeId(String),
#[error("delegation depth limit (3) exceeded")]
DelegationDepthExceeded,
#[error("invalid scope: {0}")]
InvalidScope(String),
#[error("chain verification failed: {0}")]
ChainVerificationFailed(String),
#[error("audit error: {0}")]
AuditError(String),
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("already initialized: .okami/ directory already exists")]
AlreadyInitialized,
#[error("insecure key permissions: file must be mode 0600")]
InsecureKeyPermissions,
#[error("insecure key ownership: file owner does not match effective UID")]
InsecureKeyOwner,
#[error("signing key does not match credential: verifying keys differ")]
KeyCredentialMismatch,
#[error("serialization error: {0}")]
Serialization(String),
#[error("token expired")]
TokenExpired,
#[error("token not yet valid (issued in the future beyond clock skew tolerance)")]
TokenNotYetValid,
#[error("scope escalation: requested scopes are not a subset of issuer scopes")]
ScopeEscalation,
}
impl From<lupine_core::Error> for Error {
fn from(e: lupine_core::Error) -> Self {
Error::Crypto(e)
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_display_variants() {
let e = Error::DelegationDepthExceeded;
assert!(e.to_string().contains("depth limit"));
let e = Error::AlreadyInitialized;
assert!(e.to_string().contains("already initialized"));
let e = Error::InsecureKeyPermissions;
assert!(e.to_string().contains("0600"));
let e = Error::InvalidSpiffeId("bad".to_string());
assert!(e.to_string().contains("bad"));
let e = Error::InvalidScope("foo bar".to_string());
assert!(e.to_string().contains("foo bar"));
let e = Error::TokenExpired;
assert!(e.to_string().contains("expired"));
let e = Error::ScopeEscalation;
assert!(e.to_string().contains("escalation"));
}
#[test]
fn from_lupine_error() {
let lupine_err = lupine_core::Error::Signing;
let okami_err = Error::from(lupine_err);
assert!(matches!(okami_err, Error::Crypto(_)));
assert!(okami_err.to_string().contains("crypto error"));
}
#[test]
fn from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
let okami_err: Error = io_err.into();
assert!(matches!(okami_err, Error::IoError(_)));
assert!(okami_err.to_string().contains("I/O error"));
}
#[test]
fn error_is_debug() {
let e = Error::DelegationDepthExceeded;
let dbg = format!("{e:?}");
assert!(!dbg.is_empty());
}
}