Skip to main content

dcrypt_api/error/
traits.rs

1//! Error handling traits for the cryptographic ecosystem
2
3use super::registry::ERROR_REGISTRY;
4use super::types::{Error, Result};
5use dcrypt_internal::constant_time::ConditionallySelectable;
6
7/// Extension trait for Result types
8pub trait ResultExt<T, E>: Sized {
9    /// Wrap an error with additional context
10    fn wrap_err<F, E2>(self, f: F) -> core::result::Result<T, E2>
11    where
12        F: FnOnce() -> E2;
13
14    /// Add context to an error when converting to Error
15    fn with_context(self, context: &'static str) -> Result<T>
16    where
17        E: Into<Error>;
18
19    #[cfg(feature = "std")]
20    /// Add message to an error when converting to Error
21    fn with_message(self, message: impl Into<String>) -> Result<T>
22    where
23        E: Into<Error>;
24}
25
26impl<T, E> ResultExt<T, E> for core::result::Result<T, E> {
27    fn wrap_err<F, E2>(self, f: F) -> core::result::Result<T, E2>
28    where
29        F: FnOnce() -> E2,
30    {
31        self.map_err(|_| f())
32    }
33
34    fn with_context(self, context: &'static str) -> Result<T>
35    where
36        E: Into<Error>,
37    {
38        self.map_err(|e| {
39            let err = e.into();
40            err.with_context(context)
41        })
42    }
43
44    #[cfg(feature = "std")]
45    fn with_message(self, message: impl Into<String>) -> Result<T>
46    where
47        E: Into<Error>,
48    {
49        self.map_err(|e| {
50            let err = e.into();
51            err.with_message(message)
52        })
53    }
54}
55
56/// Result extension for recording an error before returning a fallback value.
57///
58/// This operation uses ordinary branching on the `Result` discriminant. It is
59/// intended for diagnostics and must not be used when the success/failure state
60/// itself is secret.
61pub trait ErrorRegistryExt<T, E>: Sized {
62    /// Return the successful value, or record `on_error()` and return `default`.
63    fn unwrap_or_record_with<F>(self, default: T, on_error: F) -> T
64    where
65        F: FnOnce() -> E,
66        E: Send + 'static;
67}
68
69impl<T, E> ErrorRegistryExt<T, E> for core::result::Result<T, E> {
70    fn unwrap_or_record_with<F>(self, default: T, on_error: F) -> T
71    where
72        F: FnOnce() -> E,
73        E: Send + 'static,
74    {
75        match self {
76            Ok(value) => value,
77            Err(_) => {
78                ERROR_REGISTRY.store(on_error());
79                default
80            }
81        }
82    }
83}
84
85/// Legacy name for [`ErrorRegistryExt`].
86///
87/// Despite its historical name, this trait has never provided constant-time
88/// execution: it branches on `Result`, invokes only the selected path, and
89/// records an error only on failure.
90pub trait SecureErrorHandling<T, E>: Sized {
91    /// Return the successful value, or record `on_error()` and return `default`.
92    ///
93    /// This method is not constant-time and must not be used when the `Result`
94    /// variant is secret.
95    #[deprecated(
96        note = "secure_unwrap is not constant-time; use ErrorRegistryExt::unwrap_or_record_with for non-secret control flow"
97    )]
98    fn secure_unwrap<F>(self, default: T, on_error: F) -> T
99    where
100        F: FnOnce() -> E,
101        E: Send + 'static;
102}
103
104#[allow(deprecated)]
105impl<T, E> SecureErrorHandling<T, E> for core::result::Result<T, E> {
106    fn secure_unwrap<F>(self, default: T, on_error: F) -> T
107    where
108        F: FnOnce() -> E,
109        E: Send + 'static,
110    {
111        self.unwrap_or_record_with(default, on_error)
112    }
113}
114
115/// Deprecated compatibility helpers for inspecting a `Result`.
116///
117/// These methods perform ordinary, data-dependent branching. Their historical
118/// `ct_` prefix is inaccurate; use `Result::is_ok`, `Result::is_err`, or
119/// `Result::map_or_else` instead. No generic helper can make arbitrary closures
120/// and enum-variant control flow constant-time.
121pub trait ConstantTimeResult<T, E> {
122    /// Equivalent to [`Result::is_ok`]; this is not constant-time.
123    #[deprecated(
124        note = "ct_is_ok branches on the Result variant; use Result::is_ok and do not treat the variant as secret"
125    )]
126    fn ct_is_ok(&self) -> bool;
127
128    /// Equivalent to [`Result::is_err`]; this is not constant-time.
129    #[deprecated(
130        note = "ct_is_err branches on the Result variant; use Result::is_err and do not treat the variant as secret"
131    )]
132    fn ct_is_err(&self) -> bool;
133
134    /// Map the selected variant; only one closure is called.
135    ///
136    /// This is not constant-time. The `ConditionallySelectable` bound remains
137    /// only to avoid breaking the legacy method signature.
138    #[deprecated(
139        note = "ct_map invokes only the selected closure; use Result::map_or_else and do not treat the variant as secret"
140    )]
141    fn ct_map<U, F, G>(self, ok_fn: F, err_fn: G) -> U
142    where
143        F: FnOnce(T) -> U,
144        G: FnOnce(E) -> U,
145        U: ConditionallySelectable;
146}
147
148#[allow(deprecated)]
149impl<T, E> ConstantTimeResult<T, E> for core::result::Result<T, E> {
150    fn ct_is_ok(&self) -> bool {
151        self.is_ok()
152    }
153
154    fn ct_is_err(&self) -> bool {
155        self.is_err()
156    }
157
158    fn ct_map<U, F, G>(self, ok_fn: F, err_fn: G) -> U
159    where
160        F: FnOnce(T) -> U,
161        G: FnOnce(E) -> U,
162        U: ConditionallySelectable,
163    {
164        match self {
165            Ok(value) => ok_fn(value),
166            Err(error) => err_fn(error),
167        }
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::ErrorRegistryExt;
174    use crate::error::ERROR_REGISTRY;
175    use core::sync::atomic::{AtomicUsize, Ordering};
176
177    #[test]
178    fn record_extension_invokes_error_factory_only_on_error() {
179        static CALLS: AtomicUsize = AtomicUsize::new(0);
180        ERROR_REGISTRY.clear();
181
182        let ok: core::result::Result<u8, &'static str> = Ok(7);
183        assert_eq!(
184            ok.unwrap_or_record_with(9, || {
185                CALLS.fetch_add(1, Ordering::SeqCst);
186                "recorded error"
187            }),
188            7
189        );
190        assert_eq!(CALLS.load(Ordering::SeqCst), 0);
191
192        let error: core::result::Result<u8, &'static str> = Err("source error");
193        assert_eq!(
194            error.unwrap_or_record_with(9, || {
195                CALLS.fetch_add(1, Ordering::SeqCst);
196                "recorded error"
197            }),
198            9
199        );
200        assert_eq!(CALLS.load(Ordering::SeqCst), 1);
201        ERROR_REGISTRY.clear();
202    }
203}