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}