From f5c49b1b267f9fed8a7e89e3868cdb5f95839166 Mon Sep 17 00:00:00 2001 From: Philippe Loctaux Date: Tue, 4 Aug 2026 10:32:02 +0200 Subject: [PATCH] Implement `PartialEq, Eq` on error enums The biggest change was to move the `io::Error` variants to `io::ErrorKind`, and keep original error messages as strings. Some manual `Clone` impls were moved to `#[derive(Clone)]` --- age-core/src/plugin.rs | 2 +- age-plugin/src/identity.rs | 1 + age-plugin/src/recipient.rs | 1 + age/src/cli_common/error.rs | 17 ++---- age/src/cli_common/identities.rs | 2 +- age/src/cli_common/recipients.rs | 2 +- age/src/error.rs | 87 ++++++------------------------- age/src/format.rs | 12 ++--- age/src/identity.rs | 7 ++- age/src/plugin.rs | 2 +- age/src/primitives/armor.rs | 2 +- age/tests/testkit.rs | 14 +++-- rage/src/bin/rage-keygen/error.rs | 25 ++++----- rage/src/bin/rage-keygen/main.rs | 9 ++-- rage/src/bin/rage-mount/main.rs | 7 +-- rage/src/bin/rage/error.rs | 23 ++++---- rage/src/bin/rage/main.rs | 26 ++++----- 17 files changed, 93 insertions(+), 146 deletions(-) diff --git a/age-core/src/plugin.rs b/age-core/src/plugin.rs index 886cc4e9..adde20ce 100644 --- a/age-core/src/plugin.rs +++ b/age-core/src/plugin.rs @@ -26,7 +26,7 @@ const RESPONSE_FAIL: &str = "fail"; const RESPONSE_UNSUPPORTED: &str = "unsupported"; /// An error within the plugin protocol. -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum Error { Fail, Unsupported, diff --git a/age-plugin/src/identity.rs b/age-plugin/src/identity.rs index 7ed65d8b..7ae73012 100644 --- a/age-plugin/src/identity.rs +++ b/age-plugin/src/identity.rs @@ -146,6 +146,7 @@ impl Callbacks for BidirCallbacks<'_, '_, R, W } /// The kinds of errors that can occur within the identity plugin state machine. +#[derive(PartialEq, Eq)] pub enum Error { /// An error caused by a specific identity. Identity { diff --git a/age-plugin/src/recipient.rs b/age-plugin/src/recipient.rs index 373c80d3..a9e83a45 100644 --- a/age-plugin/src/recipient.rs +++ b/age-plugin/src/recipient.rs @@ -198,6 +198,7 @@ impl Callbacks for BidirCallbacks<'_, '_, R, W } /// The kinds of errors that can occur within the recipient plugin state machine. +#[derive(PartialEq, Eq)] pub enum Error { /// An error caused by a specific recipient. Recipient { diff --git a/age/src/cli_common/error.rs b/age/src/cli_common/error.rs index 13b7cb0a..5b379b83 100644 --- a/age/src/cli_common/error.rs +++ b/age/src/cli_common/error.rs @@ -4,7 +4,7 @@ use std::io; use crate::{DecryptError, wfl}; /// Errors that can occur while reading recipients or identities. -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] #[non_exhaustive] pub enum ReadError { /// An error occured while decrypting passphrase-encrypted identities. @@ -23,7 +23,7 @@ pub enum ReadError { line_number: usize, }, /// An I/O error occurred while reading. - Io(io::Error), + Io(io::ErrorKind, String), /// The given recipients file could not be found. MissingRecipientsFile(String), /// Standard input was used by multiple files. @@ -49,7 +49,7 @@ pub enum ReadError { impl From for ReadError { fn from(e: io::Error) -> Self { - ReadError::Io(e) + ReadError::Io(e.kind(), e.to_string()) } } @@ -83,7 +83,7 @@ impl fmt::Display for ReadError { filename = filename.as_str(), line_number = line_number, ), - ReadError::Io(e) => write!(f, "{e}"), + ReadError::Io(_, message) => write!(f, "{message}"), ReadError::MissingRecipientsFile(filename) => wfl!( f, "err-read-missing-recipients-file", @@ -106,11 +106,4 @@ impl fmt::Display for ReadError { } } -impl std::error::Error for ReadError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::Io(inner) => Some(inner), - _ => None, - } - } -} +impl std::error::Error for ReadError {} diff --git a/age/src/cli_common/identities.rs b/age/src/cli_common/identities.rs index f22113a2..c0037d29 100644 --- a/age/src/cli_common/identities.rs +++ b/age/src/cli_common/identities.rs @@ -84,7 +84,7 @@ pub(super) fn parse_identity_files + From>( #[cfg_attr(not(any(feature = "armor", feature = "ssh")), allow(unused_mut))] let mut reader = PeekableReader::new(stdin_guard.open(filename.clone()).map_err(|e| match e { - ReadError::Io(e) if matches!(e.kind(), io::ErrorKind::NotFound) => { + ReadError::Io(io::ErrorKind::NotFound, _) => { ReadError::IdentityNotFound(filename.clone()) } _ => e, diff --git a/age/src/cli_common/recipients.rs b/age/src/cli_common/recipients.rs index bd39ea00..6752c8d1 100644 --- a/age/src/cli_common/recipients.rs +++ b/age/src/cli_common/recipients.rs @@ -156,7 +156,7 @@ pub fn read_recipients( for arg in recipients_file_strings { let f = stdin_guard.open(arg.clone()).map_err(|e| match e { - ReadError::Io(e) if matches!(e.kind(), io::ErrorKind::NotFound) => { + ReadError::Io(io::ErrorKind::NotFound, _) => { ReadError::MissingRecipientsFile(arg.clone()) } _ => e, diff --git a/age/src/error.rs b/age/src/error.rs index 93894748..a6f672e7 100644 --- a/age/src/error.rs +++ b/age/src/error.rs @@ -13,12 +13,12 @@ use age_core::format::Stanza; use crate::plugin::CMD_ERROR; /// Errors returned when converting an identity file to a recipients file. -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] #[non_exhaustive] pub enum IdentityFileConvertError { /// An I/O error occurred while writing out a recipient corresponding to an identity /// in this file. - FailedToWriteOutput(io::Error), + FailedToWriteOutput(io::ErrorKind, String), /// The identity file contains a plugin identity, which can be converted to a /// recipient for encryption purposes, but not for writing a recipients file. #[cfg(feature = "plugin")] @@ -40,8 +40,8 @@ pub enum IdentityFileConvertError { impl fmt::Display for IdentityFileConvertError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - IdentityFileConvertError::FailedToWriteOutput(e) => { - wfl!(f, "err-failed-to-write-output", err = e.to_string()) + IdentityFileConvertError::FailedToWriteOutput(_, message) => { + wfl!(f, "err-failed-to-write-output", err = message.as_str()) } #[cfg(feature = "plugin")] IdentityFileConvertError::IdentityFileContainsPlugin { @@ -70,19 +70,12 @@ impl fmt::Display for IdentityFileConvertError { } } -impl std::error::Error for IdentityFileConvertError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - IdentityFileConvertError::FailedToWriteOutput(e) => Some(e), - _ => None, - } - } -} +impl std::error::Error for IdentityFileConvertError {} /// Errors returned by a plugin. #[cfg(feature = "plugin")] #[cfg_attr(docsrs, doc(cfg(feature = "plugin")))] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum PluginError { /// An error caused by a specific identity. Identity { @@ -172,7 +165,7 @@ impl fmt::Display for PluginError { } /// The various errors that can be returned during the encryption process. -#[derive(Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum EncryptError { /// An error occured while decrypting passphrase-encrypted identities. @@ -190,7 +183,7 @@ pub enum EncryptError { /// Labels must be valid age "arbitrary string"s (`1*VCHAR` in ABNF). InvalidRecipientLabels(HashSet), /// An I/O error occurred during encryption. - Io(io::Error), + Io(io::ErrorKind, String), /// The encryptor was not given any recipients. MissingRecipients, /// [`scrypt::Recipient`] was mixed with other recipient types. @@ -209,27 +202,7 @@ pub enum EncryptError { impl From for EncryptError { fn from(e: io::Error) -> Self { - EncryptError::Io(e) - } -} - -impl Clone for EncryptError { - fn clone(&self) -> Self { - match self { - Self::EncryptedIdentities(e) => Self::EncryptedIdentities(e.clone()), - Self::IncompatibleRecipients { l_labels, r_labels } => Self::IncompatibleRecipients { - l_labels: l_labels.clone(), - r_labels: r_labels.clone(), - }, - Self::InvalidRecipientLabels(labels) => Self::InvalidRecipientLabels(labels.clone()), - Self::Io(e) => Self::Io(io::Error::new(e.kind(), e.to_string())), - Self::MissingRecipients => Self::MissingRecipients, - Self::MixedRecipientAndPassphrase => Self::MixedRecipientAndPassphrase, - #[cfg(feature = "plugin")] - Self::Plugin(e) => Self::Plugin(e.clone()), - #[cfg(feature = "plugin")] - Self::PluginResolve(e) => Self::PluginResolve(e.clone()), - } + EncryptError::Io(e.kind(), e.to_string()) } } @@ -278,7 +251,7 @@ impl fmt::Display for EncryptError { "err-invalid-recipient-labels", labels = print_labels(labels), ), - EncryptError::Io(e) => e.fmt(f), + EncryptError::Io(_, message) => write!(f, "{message}"), EncryptError::MissingRecipients => wfl!(f, "err-missing-recipients"), EncryptError::MixedRecipientAndPassphrase => { wfl!(f, "err-mixed-recipient-passphrase") @@ -305,14 +278,13 @@ impl std::error::Error for EncryptError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { EncryptError::EncryptedIdentities(inner) => Some(inner), - EncryptError::Io(inner) => Some(inner), _ => None, } } } /// The various errors that can be returned during the decryption process. -#[derive(Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum DecryptError { /// The age file failed to decrypt. @@ -329,7 +301,7 @@ pub enum DecryptError { /// The MAC in the age header was invalid. InvalidMac, /// An I/O error occurred during decryption. - Io(io::Error), + Io(io::ErrorKind, String), /// Failed to decrypt an encrypted key. KeyDecryptionFailed, /// None of the provided keys could be used to decrypt the age file. @@ -346,28 +318,6 @@ pub enum DecryptError { UnknownFormat, } -impl Clone for DecryptError { - fn clone(&self) -> Self { - match self { - Self::DecryptionFailed => Self::DecryptionFailed, - Self::ExcessiveWork { required, target } => Self::ExcessiveWork { - required: *required, - target: *target, - }, - Self::InvalidHeader => Self::InvalidHeader, - Self::InvalidMac => Self::InvalidMac, - Self::Io(e) => Self::Io(io::Error::new(e.kind(), e.to_string())), - Self::KeyDecryptionFailed => Self::KeyDecryptionFailed, - Self::NoMatchingKeys => Self::NoMatchingKeys, - #[cfg(feature = "plugin")] - Self::Plugin(e) => Self::Plugin(e.clone()), - #[cfg(feature = "plugin")] - Self::PluginResolve(e) => Self::PluginResolve(e.clone()), - Self::UnknownFormat => Self::UnknownFormat, - } - } -} - impl fmt::Display for DecryptError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -382,7 +332,7 @@ impl fmt::Display for DecryptError { } DecryptError::InvalidHeader => wfl!(f, "err-header-invalid"), DecryptError::InvalidMac => wfl!(f, "err-header-mac-invalid"), - DecryptError::Io(e) => e.fmt(f), + DecryptError::Io(_, message) => write!(f, "{message}"), DecryptError::KeyDecryptionFailed => wfl!(f, "err-key-decryption"), DecryptError::NoMatchingKeys => wfl!(f, "err-no-matching-keys"), #[cfg(feature = "plugin")] @@ -415,7 +365,7 @@ impl From for DecryptError { impl From for DecryptError { fn from(e: io::Error) -> Self { - DecryptError::Io(e) + DecryptError::Io(e.kind(), e.to_string()) } } @@ -433,11 +383,4 @@ impl From for DecryptError { } } -impl std::error::Error for DecryptError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - DecryptError::Io(inner) => Some(inner), - _ => None, - } - } -} +impl std::error::Error for DecryptError {} diff --git a/age/src/format.rs b/age/src/format.rs index b1752d91..51208661 100644 --- a/age/src/format.rs +++ b/age/src/format.rs @@ -144,10 +144,10 @@ impl Header { // currently-defined header formats are newline-separated, to more // efficiently read data for the parser to consume. if input.read_until(b'\n', &mut data)? == 0 { - break Err(DecryptError::Io(io::Error::new( + break Err(DecryptError::Io( io::ErrorKind::UnexpectedEof, - "Incomplete header", - ))); + "Incomplete header".to_owned(), + )); } } Err(_) => { @@ -206,10 +206,10 @@ impl Header { // currently-defined header formats are newline-separated, to more // efficiently read data for the parser to consume. if input.read_until(b'\n', &mut data).await? == 0 { - break Err(DecryptError::Io(io::Error::new( + break Err(DecryptError::Io( io::ErrorKind::UnexpectedEof, - "Incomplete header", - ))); + "Incomplete header".to_owned(), + )); } } Err(_) => { diff --git a/age/src/identity.rs b/age/src/identity.rs index f3fec4e0..1ad859ca 100644 --- a/age/src/identity.rs +++ b/age/src/identity.rs @@ -174,8 +174,11 @@ impl IdentityFile { for identity in &self.identities { match identity { - IdentityFileEntry::Native(sk) => writeln!(output, "{}", sk.to_public()) - .map_err(IdentityFileConvertError::FailedToWriteOutput)?, + IdentityFileEntry::Native(sk) => { + writeln!(output, "{}", sk.to_public()).map_err(|e| { + IdentityFileConvertError::FailedToWriteOutput(e.kind(), e.to_string()) + })? + } #[cfg(feature = "plugin")] IdentityFileEntry::Plugin(id) => { return Err(IdentityFileConvertError::IdentityFileContainsPlugin { diff --git a/age/src/plugin.rs b/age/src/plugin.rs index 83e04205..6b0f74b0 100644 --- a/age/src/plugin.rs +++ b/age/src/plugin.rs @@ -740,7 +740,7 @@ impl crate::Identity for IdentityPluginV1 { } /// Errors returned when resolving a plugin. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum ResolveError { /// A provided plugin name was invalid. diff --git a/age/src/primitives/armor.rs b/age/src/primitives/armor.rs index 40e78168..3de1582c 100644 --- a/age/src/primitives/armor.rs +++ b/age/src/primitives/armor.rs @@ -584,7 +584,7 @@ impl AsyncWrite for ArmoredWriter { } /// The various errors that can be returned while parsing the armored format. -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] #[non_exhaustive] pub enum ArmoredReadError { /// An error occurred while parsing Base64. diff --git a/age/tests/testkit.rs b/age/tests/testkit.rs index 4cf7fc54..56e98d54 100644 --- a/age/tests/testkit.rs +++ b/age/tests/testkit.rs @@ -728,14 +728,12 @@ fn check_decrypt_error(filename: &str, testfile: TestFile, e: DecryptError) { assert_eq!(testfile.expect, Expect::HeaderFailure); } } - DecryptError::Io(e) => { - let kind = e.kind(); - if e.into_inner().map(|inner| inner.is::()) == Some(true) { - assert_eq!(kind, io::ErrorKind::InvalidData); - assert_eq!(testfile.expect, Expect::ArmorFailure); - } else { - assert_eq!(testfile.expect, Expect::HeaderFailure); - } + DecryptError::Io(io::ErrorKind::InvalidData, _) => { + // Armor errors are always reported with `io::ErrorKind::InvalidData`. + assert_eq!(testfile.expect, Expect::ArmorFailure); + } + DecryptError::Io(_, _) => { + assert_eq!(testfile.expect, Expect::HeaderFailure); } DecryptError::ExcessiveWork { .. } | DecryptError::UnknownFormat => { assert_eq!(testfile.expect, Expect::HeaderFailure) diff --git a/rage/src/bin/rage-keygen/error.rs b/rage/src/bin/rage-keygen/error.rs index 75ee3e85..521c9182 100644 --- a/rage/src/bin/rage-keygen/error.rs +++ b/rage/src/bin/rage-keygen/error.rs @@ -13,11 +13,12 @@ macro_rules! wlnfl { }; } +#[derive(PartialEq, Eq)] pub(crate) enum Error { - FailedToOpenInput(io::Error), - FailedToOpenOutput(io::Error), - FailedToReadInput(io::Error), - FailedToWriteOutput(io::Error), + FailedToOpenInput(io::ErrorKind, String), + FailedToOpenOutput(io::ErrorKind, String), + FailedToReadInput(io::ErrorKind, String), + FailedToWriteOutput(io::ErrorKind, String), IdentityFileConvert(IdentityFileConvertError), } @@ -26,17 +27,17 @@ pub(crate) enum Error { impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Error::FailedToOpenInput(e) => { - wlnfl!(f, "err-failed-to-open-input", err = e.to_string())? + Error::FailedToOpenInput(_, message) => { + wlnfl!(f, "err-failed-to-open-input", err = message.as_str())? } - Error::FailedToOpenOutput(e) => { - wlnfl!(f, "err-failed-to-open-output", err = e.to_string())? + Error::FailedToOpenOutput(_, message) => { + wlnfl!(f, "err-failed-to-open-output", err = message.as_str())? } - Error::FailedToReadInput(e) => { - wlnfl!(f, "err-failed-to-read-input", err = e.to_string())? + Error::FailedToReadInput(_, message) => { + wlnfl!(f, "err-failed-to-read-input", err = message.as_str())? } - Error::FailedToWriteOutput(e) => { - wlnfl!(f, "err-failed-to-write-output", err = e.to_string())? + Error::FailedToWriteOutput(_, message) => { + wlnfl!(f, "err-failed-to-write-output", err = message.as_str())? } Error::IdentityFileConvert(e) => writeln!(f, "{e}")?, } diff --git a/rage/src/bin/rage-keygen/main.rs b/rage/src/bin/rage-keygen/main.rs index a9ea54ed..2651d3c0 100644 --- a/rage/src/bin/rage-keygen/main.rs +++ b/rage/src/bin/rage-keygen/main.rs @@ -44,12 +44,12 @@ fn main() -> Result<(), error::Error> { 0o600, false, ) - .map_err(error::Error::FailedToOpenOutput)?; + .map_err(|e| error::Error::FailedToOpenOutput(e.kind(), e.to_string()))?; if opts.convert { convert(opts.input, output) } else { - generate(output).map_err(error::Error::FailedToWriteOutput) + generate(output).map_err(|e| error::Error::FailedToWriteOutput(e.kind(), e.to_string())) } } @@ -75,9 +75,10 @@ fn generate(mut output: file_io::OutputWriter) -> io::Result<()> { fn convert(filename: Option, output: file_io::OutputWriter) -> Result<(), error::Error> { let file = age::IdentityFile::from_input_reader( - file_io::InputReader::new(filename).map_err(error::Error::FailedToOpenInput)?, + file_io::InputReader::new(filename) + .map_err(|e| error::Error::FailedToOpenInput(e.kind(), e.to_string()))?, ) - .map_err(error::Error::FailedToReadInput)?; + .map_err(|e| error::Error::FailedToReadInput(e.kind(), e.to_string()))?; file.write_recipients_file(output) .map_err(error::Error::IdentityFileConvert)?; diff --git a/rage/src/bin/rage-mount/main.rs b/rage/src/bin/rage-mount/main.rs index 91f57458..9a2f3e45 100644 --- a/rage/src/bin/rage-mount/main.rs +++ b/rage/src/bin/rage-mount/main.rs @@ -56,10 +56,11 @@ macro_rules! wlnfl { }; } +#[derive(PartialEq, Eq)] enum Error { Age(age::DecryptError), IdentityRead(age::cli_common::ReadError), - Io(io::Error), + Io(io::ErrorKind, String), MissingFilename, MissingIdentities, MissingMountpoint, @@ -81,7 +82,7 @@ impl From for Error { impl From for Error { fn from(e: io::Error) -> Self { - Error::Io(e) + Error::Io(e.kind(), e.to_string()) } } @@ -98,7 +99,7 @@ impl fmt::Debug for Error { _ => write!(f, "{e}"), }, Error::IdentityRead(e) => write!(f, "{e}"), - Error::Io(e) => write!(f, "{e}"), + Error::Io(_, message) => write!(f, "{message}"), Error::MissingFilename => wfl!(f, "err-mnt-missing-filename"), Error::MissingIdentities => { wlnfl!(f, "err-dec-missing-identities")?; diff --git a/rage/src/bin/rage/error.rs b/rage/src/bin/rage/error.rs index ba27fc53..a23cd361 100644 --- a/rage/src/bin/rage/error.rs +++ b/rage/src/bin/rage/error.rs @@ -21,14 +21,15 @@ macro_rules! wlnfl { }; } +#[derive(PartialEq, Eq)] pub(crate) enum EncryptError { Age(age::EncryptError), BrokenPipe { is_stdout: bool, - source: io::Error, + source: (io::ErrorKind, String), }, IdentityRead(age::cli_common::ReadError), - Io(io::Error), + Io(io::ErrorKind, String), MixedIdentityAndPassphrase, MixedRecipientAndPassphrase, MixedRecipientsFileAndPassphrase, @@ -41,7 +42,7 @@ pub(crate) enum EncryptError { impl From for EncryptError { fn from(e: age::EncryptError) -> Self { match e { - age::EncryptError::Io(e) => EncryptError::Io(e), + age::EncryptError::Io(kind, message) => EncryptError::Io(kind, message), _ => EncryptError::Age(e), } } @@ -55,7 +56,7 @@ impl From for EncryptError { impl From for EncryptError { fn from(e: io::Error) -> Self { - EncryptError::Io(e) + EncryptError::Io(e.kind(), e.to_string()) } } @@ -69,14 +70,14 @@ impl fmt::Display for EncryptError { EncryptError::Age(e) => write!(f, "{e}"), EncryptError::BrokenPipe { is_stdout, source } => { if *is_stdout { - wlnfl!(f, "err-enc-broken-stdout", err = source.to_string())?; + wlnfl!(f, "err-enc-broken-stdout", err = source.1.as_str())?; wfl!(f, "rec-enc-broken-stdout") } else { - wfl!(f, "err-enc-broken-file", err = source.to_string()) + wfl!(f, "err-enc-broken-file", err = source.1.as_str()) } } EncryptError::IdentityRead(e) => write!(f, "{e}"), - EncryptError::Io(e) => write!(f, "{e}"), + EncryptError::Io(_, message) => write!(f, "{message}"), EncryptError::MixedIdentityAndPassphrase => { wfl!(f, "err-enc-mixed-identity-passphrase") } @@ -110,11 +111,12 @@ impl fmt::Display for DetectedPowerShellCorruptionError { impl std::error::Error for DetectedPowerShellCorruptionError {} +#[derive(PartialEq, Eq)] pub(crate) enum DecryptError { Age(age::DecryptError), ArmorFlag, IdentityRead(age::cli_common::ReadError), - Io(io::Error), + Io(io::ErrorKind, String), MissingIdentities { stdin_identity: bool, }, @@ -148,7 +150,7 @@ impl From for DecryptError { impl From for DecryptError { fn from(e: io::Error) -> Self { - DecryptError::Io(e) + DecryptError::Io(e.kind(), e.to_string()) } } @@ -167,7 +169,7 @@ impl fmt::Display for DecryptError { wfl!(f, "rec-dec-armor-flag") } DecryptError::IdentityRead(e) => write!(f, "{e}"), - DecryptError::Io(e) => write!(f, "{e}"), + DecryptError::Io(_, message) => write!(f, "{message}"), DecryptError::MissingIdentities { stdin_identity } => { wlnfl!(f, "err-dec-missing-identities")?; if *stdin_identity { @@ -203,6 +205,7 @@ impl fmt::Display for DecryptError { } } +#[derive(PartialEq, Eq)] pub(crate) enum Error { Decryption(DecryptError), Encryption(EncryptError), diff --git a/rage/src/bin/rage/main.rs b/rage/src/bin/rage/main.rs index 7ae90df1..380e15bb 100644 --- a/rage/src/bin/rage/main.rs +++ b/rage/src/bin/rage/main.rs @@ -155,16 +155,18 @@ fn encrypt(opts: AgeOptions) -> Result<(), error::EncryptError> { Err(pinentry::Error::Timeout) => return Err(error::EncryptError::PassphraseTimedOut), Err(pinentry::Error::Encoding(e)) => { // Pretend it is an I/O error - return Err(error::EncryptError::Io(io::Error::new( + return Err(error::EncryptError::Io( io::ErrorKind::InvalidData, - e, - ))); + e.to_string(), + )); } Err(pinentry::Error::Gpg(e)) => { // Pretend it is an I/O error - return Err(error::EncryptError::Io(io::Error::other(format!("{e}")))); + return Err(error::EncryptError::Io(io::ErrorKind::Other, e.to_string())); + } + Err(pinentry::Error::Io(e)) => { + return Err(error::EncryptError::Io(e.kind(), e.to_string())); } - Err(pinentry::Error::Io(e)) => return Err(error::EncryptError::Io(e)), } } else { if opts.recipient.is_empty() && opts.recipients_file.is_empty() && opts.identity.is_empty() @@ -191,7 +193,7 @@ fn encrypt(opts: AgeOptions) -> Result<(), error::EncryptError> { let map_io_errors = |e: io::Error| match e.kind() { io::ErrorKind::BrokenPipe => error::EncryptError::BrokenPipe { is_stdout, - source: e, + source: (e.kind(), e.to_string()), }, _ => e.into(), }; @@ -328,16 +330,16 @@ fn decrypt(opts: AgeOptions) -> Result<(), error::DecryptError> { Err(pinentry::Error::Timeout) => Err(error::DecryptError::PassphraseTimedOut), Err(pinentry::Error::Encoding(e)) => { // Pretend it is an I/O error - Err(error::DecryptError::Io(io::Error::new( + Err(error::DecryptError::Io( io::ErrorKind::InvalidData, - e, - ))) + e.to_string(), + )) } Err(pinentry::Error::Gpg(e)) => { // Pretend it is an I/O error - Err(error::DecryptError::Io(io::Error::other(format!("{e}")))) + Err(error::DecryptError::Io(io::ErrorKind::Other, e.to_string())) } - Err(pinentry::Error::Io(e)) => Err(error::DecryptError::Io(e)), + Err(pinentry::Error::Io(e)) => Err(error::DecryptError::Io(e.kind(), e.to_string())), } } else { if identities.is_empty() { @@ -369,7 +371,7 @@ fn main() -> Result<(), error::Error> { if console::user_attended() && args().len() == 1 { AgeOptions::command() .print_help() - .map_err(error::EncryptError::Io)?; + .map_err(|e| error::EncryptError::Io(e.kind(), e.to_string()))?; return Ok(()); }