age-crypto 0.2.0

A safe, ergonomic Rust wrapper around the age encryption library with strong typing, comprehensive error handling, and passphrase support.
Documentation
use std::io;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum EncryptError {
    #[error("No recipients provided")]
    NoRecipients,
    #[error("Invalid recipient '{recipient}': {reason}")]
    InvalidRecipient { recipient: String, reason: String },
    #[error("Encryption failed: {0}")]
    Failed(String),
    #[error("I/O error: {0}")]
    Io(#[from] io::Error),
}
impl EncryptError {
    #[must_use]
    pub fn is_user_correctable(&self) -> bool {
        matches!(
            self,
            EncryptError::NoRecipients | EncryptError::InvalidRecipient { .. }
        )
    }
    #[must_use]
    pub fn invalid_recipient(&self) -> Option<&str> {
        match self {
            EncryptError::InvalidRecipient { recipient, .. } => Some(recipient),
            _ => None,
        }
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use std::io::{self, ErrorKind};
    #[test]
    fn test_no_recipients_display() {
        let err = EncryptError::NoRecipients;
        assert_eq!(format!("{}", err), "No recipients provided");
    }
    #[test]
    fn test_invalid_recipient_display() {
        let err = EncryptError::InvalidRecipient {
            recipient: "age1badkey".into(),
            reason: "invalid checksum".into(),
        };
        assert_eq!(
            format!("{}", err),
            "Invalid recipient 'age1badkey': invalid checksum"
        );
    }
    #[test]
    fn test_failed_display() {
        let err = EncryptError::Failed("internal crypto error".into());
        assert_eq!(
            format!("{}", err),
            "Encryption failed: internal crypto error"
        );
    }
    #[test]
    fn test_io_error_display() {
        let io_err = io::Error::new(ErrorKind::OutOfMemory, "alloc failed");
        let err = EncryptError::Io(io_err);
        assert_eq!(format!("{}", err), "I/O error: alloc failed");
    }
    #[test]
    fn test_from_io_error_conversion() {
        let io_err: io::Error = ErrorKind::PermissionDenied.into();
        let encrypt_err: EncryptError = io_err.into();
        assert!(matches!(encrypt_err, EncryptError::Io(_)));
    }
    #[test]
    fn test_io_error_preserves_kind() {
        let io_err = io::Error::new(ErrorKind::UnexpectedEof, "test");
        let encrypt_err = EncryptError::Io(io_err);
        if let EncryptError::Io(e) = encrypt_err {
            assert_eq!(e.kind(), ErrorKind::UnexpectedEof);
        } else {
            panic!("Expected Io variant");
        }
    }
    #[test]
    fn test_is_user_correctable_true() {
        assert!(EncryptError::NoRecipients.is_user_correctable());
        let invalid = EncryptError::InvalidRecipient {
            recipient: "bad".into(),
            reason: "x".into(),
        };
        assert!(invalid.is_user_correctable());
    }
    #[test]
    fn test_is_user_correctable_false() {
        assert!(!EncryptError::Failed("x".into()).is_user_correctable());
        assert!(!EncryptError::Io(io::Error::last_os_error()).is_user_correctable());
    }
    #[test]
    fn test_invalid_recipient_some() {
        let err = EncryptError::InvalidRecipient {
            recipient: "age1test".into(),
            reason: "bad".into(),
        };
        assert_eq!(err.invalid_recipient(), Some("age1test"));
    }
    #[test]
    fn test_invalid_recipient_none() {
        assert_eq!(EncryptError::NoRecipients.invalid_recipient(), None);
        assert_eq!(EncryptError::Failed("x".into()).invalid_recipient(), None);
    }
    #[test]
    fn test_error_is_send_sync() {
        fn assert_send_sync<T: Send + Sync>() {}
        assert_send_sync::<EncryptError>();
    }
    #[test]
    fn test_error_implements_std_error() {
        fn assert_error<T: std::error::Error>() {}
        assert_error::<EncryptError>();
    }
    #[test]
    fn test_error_source_chain() {
        use std::error::Error as StdError;
        let io_err = io::Error::other("underlying cause");
        let encrypt_err = EncryptError::Io(io_err);
        assert!(encrypt_err.source().is_some());
    }
    #[test]
    fn test_debug_format_contains_variant_name() {
        let err = EncryptError::InvalidRecipient {
            recipient: "key".into(),
            reason: "bad".into(),
        };
        let debug = format!("{:?}", err);
        assert!(debug.contains("InvalidRecipient"));
        assert!(debug.contains("key"));
    }
}