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(());
}