core_crypto_ffi/error/
mod.rs

1pub(crate) mod core_crypto;
2pub(crate) mod mls;
3#[cfg(feature = "proteus")]
4pub(crate) mod proteus;
5#[cfg(target_family = "wasm")]
6pub(crate) mod wasm;
7
8#[cfg(target_family = "wasm")]
9pub type CoreCryptoError = wasm::CoreCryptoError;
10#[cfg(not(target_family = "wasm"))]
11pub type CoreCryptoError = core_crypto::CoreCryptoError;
12
13pub type CoreCryptoResult<T, E = CoreCryptoError> = Result<T, E>;
14
15/// Prepare and dispatch a log message reporting this error.
16///
17/// We want to ensure consistent logging every time we pass a log message across the FFI boundary,
18/// as we cannot guarantee the method, format, or existence of error logging once the result crosses.
19/// Unfortunately, as there is no single point at which we convert internal errors to trans-ffi
20/// errors, we need to extract the logging procedure and ensure it's called at each relevant point.
21///
22/// This has the further disadvantage that we have very little context information at the point of
23/// logging. We'll try this out for now anyway; if it turns out that we need to add more tracing
24/// in the future, we can figure out our techniques then.
25fn log_error(error: &dyn std::error::Error) {
26    // we exclude the original error message from the chain
27    let chain = {
28        let mut error = error;
29        let mut chain = Vec::new();
30        while let Some(inner) = error.source() {
31            chain.push(inner.to_string());
32            error = inner;
33        }
34        chain
35    };
36    let msg = error.to_string();
37    let err = serde_json::json!({"msg": msg, "chain": chain});
38    // even though there exists a `:err` formatter, it only captures the top-level
39    // message from the error, so it's still worth building our own inner error formatter
40    // and using serde here
41    log::warn!(target: "core-crypto", err:serde; "core-crypto returning this error across ffi; see recent log messages for context");
42}
43
44#[cfg(all(test, not(target_family = "wasm")))]
45mod tests {
46    use crate::{CoreCrypto, CoreCryptoError, MlsError};
47
48    use ::core_crypto::{LeafError, RecursiveError};
49
50    #[test]
51    fn test_error_mapping() {
52        let duplicate_message_error = RecursiveError::mls_conversation("test duplicate message error")(
53            core_crypto::mls::conversation::Error::DuplicateMessage,
54        );
55        let mapped_error = CoreCryptoError::from(duplicate_message_error);
56        assert!(matches!(mapped_error, CoreCryptoError::Mls(MlsError::DuplicateMessage)));
57
58        let conversation_exists_error = RecursiveError::mls_conversation("test conversation exists error")(
59            core_crypto::mls::conversation::Error::Leaf(LeafError::ConversationAlreadyExists(
60                "test conversation id".into(),
61            )),
62        );
63        let mapped_error = CoreCryptoError::from(conversation_exists_error);
64        assert!(matches!(
65            mapped_error,
66            CoreCryptoError::Mls(MlsError::ConversationAlreadyExists(_))
67        ));
68    }
69
70    #[tokio::test]
71    async fn test_error_is_logged() {
72        testing_logger::setup();
73        // we shouldn't be able to create a SQLite DB in `/root` unless we are running this test as root
74        // Don't do that!
75        let key = core_crypto_keystore::DatabaseKey::generate().into();
76        let result = CoreCrypto::new("/root/asdf".into(), key, None, None, None, None).await;
77        assert!(
78            result.is_err(),
79            "result must be an error in order to verify that something was logged"
80        );
81        testing_logger::validate(|captured_logs| {
82            assert!(
83                captured_logs.iter().any(|log| log.level == log::Level::Warn
84                    && log.target == "core-crypto"
85                    && log.body.contains("returning this error across ffi")),
86                "log message did not appear within the captured logs"
87            )
88        });
89    }
90}