use std::io; use thiserror::Error; /// The application-wide error type. #[derive(Debug, Error)] pub enum Error { /// A command-line or configuration value was invalid. #[error("configuration: {0}")] Config(String), /// A filesystem operation failed. #[error("{context}: {source}")] Io { /// The operation being performed. context: String, /// The underlying error. #[source] source: io::Error, }, /// TOML could not be decoded. #[error("parse {path}: {source}")] Toml { /// The file being parsed. path: String, /// The parser error. #[source] source: toml::de::Error, }, /// PAM rejected or could not complete an operation. #[error("PAM {operation} failed with status {status}: {message}")] Pam { /// The PAM operation. operation: &'static str, /// The numeric PAM status. status: i32, /// The human-readable PAM message. message: String, }, /// A session process failed. #[error("session: {0}")] Session(String), /// The terminal could not be initialized or restored. #[error("terminal: {0}")] Terminal(String), /// An authentication worker request was cancelled or disconnected. #[error("authentication worker: {0}")] Worker(String), } impl Error { /// Returns a PAM status when this error came directly from PAM. pub fn pam_status(&self) -> Option { match self { Self::Pam { status, .. } => Some(*status), _ => None, } } /// Returns whether PAM requires a password change before login can finish. pub fn requires_password_change(&self) -> bool { matches!(self.pam_status(), Some(12 | 27)) } } /// Adds a stable operation label to an I/O error. pub fn io_error(context: impl Into, source: io::Error) -> Error { Error::Io { context: context.into(), source, } } /// Converts a failed result into an application error with context. pub trait ResultExt { /// Adds context to an I/O operation. fn io_context(self, context: impl Into) -> Result; } impl ResultExt for io::Result { fn io_context(self, context: impl Into) -> Result { self.map_err(|source| io_error(context, source)) } }