use thiserror::Error;
pub mod map;
#[derive(Debug, Error)]
pub enum ApiError {
#[error("policy violation: {0}")]
PolicyViolation(String),
#[error("locking timeout: {0}")]
LockingTimeout(String),
#[error("filesystem error: {0}")]
FilesystemError(String),
#[error("cross-filesystem degraded path not allowed: {0}")]
ExdevDegraded(String),
#[error("smoke tests failed")]
SmokeFailed,
#[error("ownership check failed: {0}")]
OwnershipError(String),
#[error("attestation failed: {0}")]
AttestationFailed(String),
}
#[must_use]
pub fn infer_summary_error_ids(errors: &[String]) -> Vec<&'static str> {
let mut out: Vec<&'static str> = Vec::new();
let joined = errors.join("; ").to_lowercase();
if joined.contains("smoke") {
out.push(id_str(ErrorId::E_SMOKE));
}
if joined.contains("lock") {
out.push(id_str(ErrorId::E_LOCKING));
}
if joined.contains("ownership") {
out.push(id_str(ErrorId::E_OWNERSHIP));
}
if joined.contains("exdev") {
out.push(id_str(ErrorId::E_EXDEV));
}
if joined.contains("xdev")
|| joined.contains("cross-device")
|| joined.contains("cross device")
|| joined.contains("os error 18")
|| joined.contains("errno 18")
{
out.push(id_str(ErrorId::E_EXDEV));
}
if joined.contains("atomic") || joined.contains("symlink") {
out.push(id_str(ErrorId::E_ATOMIC_SWAP));
}
if joined.contains("backup") && joined.contains("missing") {
out.push(id_str(ErrorId::E_BACKUP_MISSING));
}
if joined.contains("restore") && joined.contains("failed") {
out.push(id_str(ErrorId::E_RESTORE_FAILED));
}
if out.is_empty() {
out.push(id_str(ErrorId::E_POLICY));
} else {
out.push(id_str(ErrorId::E_POLICY));
}
let mut seen = std::collections::HashSet::new();
out.into_iter().filter(|id| seen.insert(*id)).collect()
}
impl From<crate::types::errors::Error> for ApiError {
fn from(e: crate::types::errors::Error) -> Self {
use crate::types::errors::ErrorKind::{InvalidPath, Io, Policy};
match e.kind {
InvalidPath | Io => ApiError::FilesystemError(e.msg),
Policy => ApiError::PolicyViolation(e.msg),
}
}
}
#[allow(
non_camel_case_types,
reason = "Error IDs must match SPEC/error_codes.toml format"
)]
#[derive(Clone, Copy, Debug)]
pub enum ErrorId {
E_POLICY,
E_OWNERSHIP,
E_LOCKING,
E_ATOMIC_SWAP,
E_EXDEV,
E_BACKUP_MISSING,
E_RESTORE_FAILED,
E_SMOKE,
E_GENERIC,
}
#[must_use]
pub const fn id_str(id: ErrorId) -> &'static str {
match id {
ErrorId::E_POLICY => "E_POLICY",
ErrorId::E_OWNERSHIP => "E_OWNERSHIP",
ErrorId::E_LOCKING => "E_LOCKING",
ErrorId::E_ATOMIC_SWAP => "E_ATOMIC_SWAP",
ErrorId::E_EXDEV => "E_EXDEV",
ErrorId::E_BACKUP_MISSING => "E_BACKUP_MISSING",
ErrorId::E_RESTORE_FAILED => "E_RESTORE_FAILED",
ErrorId::E_SMOKE => "E_SMOKE",
ErrorId::E_GENERIC => "E_GENERIC",
}
}
#[must_use]
pub const fn exit_code_for(id: ErrorId) -> i32 {
match id {
ErrorId::E_POLICY => 10,
ErrorId::E_OWNERSHIP => 20,
ErrorId::E_LOCKING => 30,
ErrorId::E_ATOMIC_SWAP => 40,
ErrorId::E_EXDEV => 50,
ErrorId::E_BACKUP_MISSING => 60,
ErrorId::E_RESTORE_FAILED => 70,
ErrorId::E_SMOKE => 80,
ErrorId::E_GENERIC => 1,
}
}
#[must_use]
pub fn exit_code_for_id_str(s: &str) -> Option<i32> {
match s {
"E_POLICY" => Some(10),
"E_OWNERSHIP" => Some(20),
"E_LOCKING" => Some(30),
"E_ATOMIC_SWAP" => Some(40),
"E_EXDEV" => Some(50),
"E_BACKUP_MISSING" => Some(60),
"E_RESTORE_FAILED" => Some(70),
"E_SMOKE" => Some(80),
_ => None,
}
}