Skip to main content

age_crypto/errors/
mod.rs

1//! # Error handling for the `age_crypto` library
2//!
3//! This module defines the unified error type and the alias `Result` used
4//! across the entire library. It re‑exports the sub‑module errors so that
5//! users can conveniently match on specific failure reasons if they wish.
6//!
7//! # Structure
8//!
9//! ```text
10//! errors
11//! ├── decrypt.rs  → DecryptError (four variants for decrypt-specific failures)
12//! ├── encrypt.rs  → EncryptError (four variants for encrypt-specific failures)
13//! └── mod.rs      → Error (top-level enum), Result type alias
14//! ```
15//!
16//! The top‑level [`Error`] is an enum with two variants:
17//! - `Error::Encrypt(EncryptError)`
18//! - `Error::Decrypt(DecryptError)`
19//!
20//! Both variants are created automatically from their respective
21//! sub‑errors thanks to the `#[from]` attribute. This means:
22//!
23//! 1. Functions that work internally with `DecryptError` (e.g., the
24//!    `parse_identity` helper) can return `DecryptError` without worrying
25//!    about the outer type.
26//! 2. The public API functions (e.g., `crate::decrypt`) have a return type
27//!    of `Result<T>` (i.e., `std::result::Result<T, Error>`). When they
28//!    use `?` on an expression that returns `Result<_, DecryptError>`, the
29//!    conversion to `Error::Decrypt` happens automatically.
30//!
31//! # Usage for library consumers
32//!
33//! ## Basic error handling
34//!
35//! ```rust
36//! use age_crypto::{decrypt_with_passphrase, Error};
37//!
38//! // Contoh: ciphertext dummy untuk demonstrasi
39//! let ciphertext = b"AGE-ENC..."; // hasil dari encrypt_with_passphrase
40//! let passphrase = "my-secret-pass";
41//!
42//! match decrypt_with_passphrase(ciphertext, passphrase) {
43//!     Ok(plaintext) => println!("Decrypted: {:?}", std::str::from_utf8(&plaintext)),
44//!     Err(Error::Decrypt(e)) => eprintln!("Decrypt error: {}", e),
45//!     Err(Error::Encrypt(e)) => eprintln!("Unexpected encrypt error: {}", e),
46//! }
47//! ```
48//!
49//! ## Distinguishing error kinds
50//!
51//! ```rust
52//! use age_crypto::{decrypt_with_passphrase, Error};
53//! use age_crypto::errors::DecryptError;
54//!
55//! let ciphertext = b"invalid-ciphertext";
56//! let passphrase = "wrong-pass";
57//!
58//! match decrypt_with_passphrase(ciphertext, passphrase) {
59//!     Ok(plain) => {
60//!         // Handle successful decryption
61//!         let _ = plain;
62//!     }
63//!     Err(Error::Decrypt(DecryptError::InvalidCiphertext(_))) => {
64//!         // Handle corrupted or malformed ciphertext
65//!         eprintln!("Ciphertext is invalid or corrupted");
66//!     }
67//!     Err(Error::Decrypt(DecryptError::Failed(_))) => {
68//!         // Handle wrong passphrase or authentication failure
69//!         eprintln!("Wrong passphrase or integrity check failed");
70//!     }
71//!     Err(Error::Decrypt(DecryptError::Io(e))) => {
72//!         // Handle I/O errors during decryption
73//!         eprintln!("I/O error: {}", e);
74//!     }
75//!     Err(other) => eprintln!("Unexpected error: {}", other),
76//! }
77//! ```
78//!
79//! # Why two‑level error design?
80//!
81//! - **Separation of concerns**: Encryption and decryption are distinct
82//!   operations with different failure modes. Keeping them in separate
83//!   enums makes the code self‑documenting.
84//! - **Extensibility**: In the future, we could add a `ConfigError` or
85//!   `KeyGenerationError` variant to the top‑level `Error` without
86//!   touching the encrypt/decrypt types.
87//! - **Backward compatibility**: Adding a new variant to a public enum is
88//!   a breaking change. With the two‑level approach, we can sometimes
89//!   add variants to `DecryptError` without directly breaking the top‑level
90//!   `Error` ABI (though it still breaks exhaustive matches on
91//!   `DecryptError`). The outer `Error` remains stable as long as we
92//!   don't add a new variant there.
93
94pub mod decrypt;
95pub mod encrypt;
96
97pub use decrypt::DecryptError;
98pub use encrypt::EncryptError;
99
100use thiserror::Error;
101
102/// The universal error type for this crate.
103///
104/// This enum represents every possible error that a function in this
105/// library can return. It is a thin wrapper around the domain‑specific
106/// error types:
107///
108/// - [`EncryptError`] for encryption‑related failures.
109/// - [`DecryptError`] for decryption‑related failures.
110///
111/// The `#[from]` attribute generates `From<EncryptError>` and
112/// `From<DecryptError>` implementations, so conversions are automatic
113/// (see [module documentation](self) for details). This keeps the
114/// public API clean – all functions return a single error type, yet
115/// callers can still inspect the underlying cause.
116///
117/// # Display format
118///
119/// Each variant prefixes the error message with the domain:
120///
121/// ```text
122/// Encrypt error: Invalid recipient 'abc': reason
123/// Decrypt error: Invalid identity: something
124/// ```
125///
126/// # Example of error propagation with `?`
127///
128/// ```rust
129/// use age_crypto::{encrypt_with_passphrase, Error};
130///
131/// fn safe_encrypt(plaintext: &[u8], passphrase: &str) -> Result<Vec<u8>, Error> {
132///     // encrypt_with_passphrase already returns Result<_, Error>
133///     // so we can use `?` directly for propagation
134///     let encrypted = encrypt_with_passphrase(plaintext, passphrase)?;
135///     Ok(encrypted.as_bytes().to_vec())
136/// }
137/// ```
138#[derive(Debug, Error)]
139pub enum Error {
140    /// An encryption‑related error occurred.
141    ///
142    /// The inner [`EncryptError`] provides detailed information about
143    /// missing recipients, invalid keys, or internal failures.
144    #[error("Encrypt error: {0}")]
145    Encrypt(#[from] EncryptError),
146
147    /// A decryption‑related error occurred.
148    ///
149    /// The inner [`DecryptError`] provides detailed information about
150    /// invalid identities, broken ciphertext, or decryption mismatches.
151    #[error("Decrypt error: {0}")]
152    Decrypt(#[from] DecryptError),
153}
154
155/// Convenient alias for `std::result::Result<T, crate::errors::Error>`.
156///
157/// All public API functions that can fail use this type. By importing
158/// `crate::errors::Result`, you can write:
159///
160/// ```rust
161/// use age_crypto::errors::Result;
162///
163/// fn my_function() -> Result<String> {
164///     // Return Ok with a value
165///     Ok(String::from("success"))
166/// }
167/// ```
168///
169/// without needing to specify `Error` explicitly. The type is re‑exported
170/// from the crate root, so `age_crypto::Result` is also available.
171///
172/// # Example with error propagation
173///
174/// ```rust
175/// use age_crypto::{encrypt_with_passphrase, errors::Result};
176///
177/// fn process_and_encrypt(data: &str, pass: &str) -> Result<Vec<u8>> {
178///     // Any error from encrypt_with_passphrase is automatically
179///     // converted to our crate::errors::Error via the `?` operator
180///     let encrypted = encrypt_with_passphrase(data.as_bytes(), pass)?;
181///     Ok(encrypted.as_bytes().to_vec())
182/// }
183/// ```
184pub type Result<T> = std::result::Result<T, Error>;
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn test_error_display_encrypt() {
192        let err = Error::from(EncryptError::Failed("test".into()));
193        assert_eq!(format!("{}", err), "Encrypt error: Encryption failed: test");
194    }
195
196    #[test]
197    fn test_error_display_decrypt() {
198        let err = Error::from(DecryptError::Failed("test".into()));
199        assert_eq!(format!("{}", err), "Decrypt error: Decryption failed: test");
200    }
201
202    #[test]
203    fn test_error_from_encrypt_error() {
204        let encrypt_err = EncryptError::InvalidRecipient {
205            recipient: "key".into(),
206            reason: "invalid format".into(),
207        };
208
209        let err: Error = encrypt_err.into();
210        assert!(matches!(
211            err,
212            Error::Encrypt(EncryptError::InvalidRecipient { .. })
213        ));
214    }
215
216    #[test]
217    fn test_error_from_decrypt_error() {
218        let decrypt_err = DecryptError::InvalidIdentity("id".into());
219        let err: Error = decrypt_err.into();
220        assert!(matches!(
221            err,
222            Error::Decrypt(DecryptError::InvalidIdentity(_))
223        ));
224    }
225}