use std::fmt::Display; use color_eyre::eyre::eyre; use patch_db::Revision; use rpc_toolkit::hyper::http::uri::InvalidUri; use rpc_toolkit::reqwest; use rpc_toolkit::yajrc::RpcError; use crate::InvalidId; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ErrorKind { Unknown = 1, Filesystem = 2, Docker = 3, ConfigSpecViolation = 4, ConfigRulesViolation = 5, NotFound = 6, IncorrectPassword = 7, VersionIncompatible = 8, Network = 9, Registry = 10, Serialization = 11, Deserialization = 12, Utf8 = 13, ParseVersion = 14, IncorrectDisk = 15, // Nginx = 16, Dependency = 17, ParseS9pk = 18, ParseUrl = 19, DiskNotAvailable = 20, BlockDevice = 21, InvalidOnionAddress = 22, Pack = 23, ValidateS9pk = 24, DiskCorrupted = 25, // Remove Tor = 26, ConfigGen = 27, ParseNumber = 28, Database = 29, InvalidPackageId = 30, InvalidSignature = 31, Backup = 32, Restore = 33, Authorization = 34, AutoConfigure = 35, Action = 36, RateLimited = 37, InvalidRequest = 38, MigrationFailed = 39, Uninitialized = 40, ParseNetAddress = 41, ParseSshKey = 42, SoundError = 43, ParseTimestamp = 44, ParseSysInfo = 45, Wifi = 46, Journald = 47, DiskManagement = 48, OpenSsl = 49, PasswordHashGeneration = 50, DiagnosticMode = 51, ParseDbField = 52, Duplicate = 53, MultipleErrors = 54, Incoherent = 55, InvalidBackupTargetId = 56, ProductKeyMismatch = 57, LanPortConflict = 58, Javascript = 59, Pem = 60, TLSInit = 61, Ascii = 62, MissingHeader = 63, Grub = 64, Systemd = 65, OpenSsh = 66, Zram = 67, Lshw = 68, CpuSettings = 69, Firmware = 70, Timeout = 71, } impl ErrorKind { pub fn as_str(&self) -> &'static str { use ErrorKind::*; match self { Unknown => "Unknown Error", Filesystem => "Filesystem I/O Error", Docker => "Docker Error", ConfigSpecViolation => "Config Spec Violation", ConfigRulesViolation => "Config Rules Violation", NotFound => "Not Found", IncorrectPassword => "Incorrect Password", VersionIncompatible => "Version Incompatible", Network => "Network Error", Registry => "Registry Error", Serialization => "Serialization Error", Deserialization => "Deserialization Error", Utf8 => "UTF-8 Parse Error", ParseVersion => "Version Parsing Error", IncorrectDisk => "Incorrect Disk", // Nginx => "Nginx Error", Dependency => "Dependency Error", ParseS9pk => "S9PK Parsing Error", ParseUrl => "URL Parsing Error", DiskNotAvailable => "Disk Not Available", BlockDevice => "Block Device Error", InvalidOnionAddress => "Invalid Onion Address", Pack => "Pack Error", ValidateS9pk => "S9PK Validation Error", DiskCorrupted => "Disk Corrupted", // Remove Tor => "Tor Daemon Error", ConfigGen => "Config Generation Error", ParseNumber => "Number Parsing Error", Database => "Database Error", InvalidPackageId => "Invalid Package ID", InvalidSignature => "Invalid Signature", Backup => "Backup Error", Restore => "Restore Error", Authorization => "Unauthorized", AutoConfigure => "Auto-Configure Error", Action => "Action Failed", RateLimited => "Rate Limited", InvalidRequest => "Invalid Request", MigrationFailed => "Migration Failed", Uninitialized => "Uninitialized", ParseNetAddress => "Net Address Parsing Error", ParseSshKey => "SSH Key Parsing Error", SoundError => "Sound Interface Error", ParseTimestamp => "Timestamp Parsing Error", ParseSysInfo => "System Info Parsing Error", Wifi => "WiFi Internal Error", Journald => "Journald Error", DiskManagement => "Disk Management Error", OpenSsl => "OpenSSL Internal Error", PasswordHashGeneration => "Password Hash Generation Error", DiagnosticMode => "Server is in Diagnostic Mode", ParseDbField => "Database Field Parse Error", Duplicate => "Duplication Error", MultipleErrors => "Multiple Errors", Incoherent => "Incoherent", InvalidBackupTargetId => "Invalid Backup Target ID", ProductKeyMismatch => "Incompatible Product Keys", LanPortConflict => "Incompatible LAN Port Configuration", Javascript => "Javascript Engine Error", Pem => "PEM Encoding Error", TLSInit => "TLS Backend Initialization Error", Ascii => "ASCII Parse Error", MissingHeader => "Missing Header", Grub => "Grub Error", Systemd => "Systemd Error", OpenSsh => "OpenSSH Error", Zram => "Zram Error", Lshw => "LSHW Error", CpuSettings => "CPU Settings Error", Firmware => "Firmware Error", Timeout => "Timeout Error", } } } impl Display for ErrorKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.as_str()) } } #[derive(Debug)] pub struct Error { pub source: color_eyre::eyre::Error, pub kind: ErrorKind, pub revision: Option, } impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}: {}", self.kind.as_str(), self.source) } } impl Error { pub fn new>(source: E, kind: ErrorKind) -> Self { Error { source: source.into(), kind, revision: None, } } } impl From for Error { fn from(err: InvalidId) -> Self { Error::new(err, ErrorKind::InvalidPackageId) } } impl From for Error { fn from(e: std::io::Error) -> Self { Error::new(e, ErrorKind::Filesystem) } } impl From for Error { fn from(e: std::str::Utf8Error) -> Self { Error::new(e, ErrorKind::Utf8) } } impl From for Error { fn from(e: std::string::FromUtf8Error) -> Self { Error::new(e, ErrorKind::Utf8) } } impl From for Error { fn from(e: emver::ParseError) -> Self { Error::new(e, ErrorKind::ParseVersion) } } impl From for Error { fn from(e: rpc_toolkit::url::ParseError) -> Self { Error::new(e, ErrorKind::ParseUrl) } } impl From for Error { fn from(e: std::num::ParseIntError) -> Self { Error::new(e, ErrorKind::ParseNumber) } } impl From for Error { fn from(e: std::num::ParseFloatError) -> Self { Error::new(e, ErrorKind::ParseNumber) } } impl From for Error { fn from(e: patch_db::Error) -> Self { Error::new(e, ErrorKind::Database) } } impl From for Error { fn from(e: sqlx::Error) -> Self { Error::new(e, ErrorKind::Database) } } impl From for Error { fn from(e: ed25519_dalek::SignatureError) -> Self { Error::new(e, ErrorKind::InvalidSignature) } } impl From for Error { fn from(e: std::net::AddrParseError) -> Self { Error::new(e, ErrorKind::ParseNetAddress) } } impl From for Error { fn from(e: torut::control::ConnError) -> Self { Error::new(e, ErrorKind::Tor) } } impl From for Error { fn from(e: ipnet::AddrParseError) -> Self { Error::new(e, ErrorKind::ParseNetAddress) } } impl From for Error { fn from(e: openssl::error::ErrorStack) -> Self { Error::new(eyre!("{}", e), ErrorKind::OpenSsl) } } impl From for Error { fn from(e: mbrman::Error) -> Self { Error::new(e, ErrorKind::DiskManagement) } } impl From for Error { fn from(e: InvalidUri) -> Self { Error::new(eyre!("{}", e), ErrorKind::ParseUrl) } } impl From for Error { fn from(e: ssh_key::Error) -> Self { Error::new(e, ErrorKind::OpenSsh) } } impl From for Error { fn from(e: reqwest::Error) -> Self { let kind = match e { _ if e.is_builder() => ErrorKind::ParseUrl, _ if e.is_decode() => ErrorKind::Deserialization, _ => ErrorKind::Network, }; Error::new(e, kind) } } impl From for Error { fn from(value: patch_db::value::Error) -> Self { match value.kind { patch_db::value::ErrorKind::Serialization => { Error::new(value.source, ErrorKind::Serialization) } patch_db::value::ErrorKind::Deserialization => { Error::new(value.source, ErrorKind::Deserialization) } } } } impl From for RpcError { fn from(e: Error) -> Self { let mut data_object = serde_json::Map::with_capacity(3); data_object.insert("details".to_owned(), format!("{}", e.source).into()); data_object.insert("debug".to_owned(), format!("{:?}", e.source).into()); data_object.insert( "revision".to_owned(), match serde_json::to_value(&e.revision) { Ok(a) => a, Err(e) => { tracing::warn!("Error serializing revision for Error object: {}", e); serde_json::Value::Null } }, ); RpcError { code: e.kind as i32, message: e.kind.as_str().into(), data: Some(data_object.into()), } } } #[derive(Debug, Default)] pub struct ErrorCollection(Vec); impl ErrorCollection { pub fn new() -> Self { Self::default() } pub fn handle>(&mut self, result: Result) -> Option { match result { Ok(a) => Some(a), Err(e) => { self.0.push(e.into()); None } } } pub fn into_result(self) -> Result<(), Error> { if self.0.is_empty() { Ok(()) } else { Err(Error::new(eyre!("{}", self), ErrorKind::MultipleErrors)) } } } impl From for Result<(), Error> { fn from(e: ErrorCollection) -> Self { e.into_result() } } impl> Extend> for ErrorCollection { fn extend>>(&mut self, iter: I) { for item in iter { self.handle(item); } } } impl std::fmt::Display for ErrorCollection { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for (idx, e) in self.0.iter().enumerate() { if idx > 0 { write!(f, "; ")?; } write!(f, "{}", e)?; } Ok(()) } } pub trait ResultExt where Self: Sized, { fn with_kind(self, kind: ErrorKind) -> Result; fn with_ctx (ErrorKind, D), D: Display + Send + Sync + 'static>( self, f: F, ) -> Result; } impl ResultExt for Result where color_eyre::eyre::Error: From, { fn with_kind(self, kind: ErrorKind) -> Result { self.map_err(|e| Error { source: e.into(), kind, revision: None, }) } fn with_ctx (ErrorKind, D), D: Display + Send + Sync + 'static>( self, f: F, ) -> Result { self.map_err(|e| { let (kind, ctx) = f(&e); let source = color_eyre::eyre::Error::from(e); let ctx = format!("{}: {}", ctx, source); let source = source.wrap_err(ctx); Error { kind, source, revision: None, } }) } } pub trait OptionExt where Self: Sized, { fn or_not_found(self, message: impl std::fmt::Display) -> Result; } impl OptionExt for Option { fn or_not_found(self, message: impl std::fmt::Display) -> Result { self.ok_or_else(|| Error::new(eyre!("{}", message), ErrorKind::NotFound)) } } #[macro_export] macro_rules! ensure_code { ($x:expr, $c:expr, $fmt:expr $(, $arg:expr)*) => { if !($x) { return Err(Error::new(color_eyre::eyre::eyre!($fmt, $($arg, )*), $c)); } }; }