didkit/
error.rs

1use std::ffi::CString;
2use std::os::raw::{c_char, c_int};
3use std::ptr;
4
5static UNKNOWN_ERROR: &str = "Unable to create error string\0";
6
7use std::cell::RefCell;
8thread_local! {
9    pub static LAST_ERROR: RefCell<Option<(i32, CString)>> = RefCell::new(None);
10}
11
12#[derive(thiserror::Error, Debug)]
13pub enum Error {
14    #[error(transparent)]
15    VC(#[from] ssi::vc::Error),
16    #[error(transparent)]
17    Zcap(#[from] ssi::zcap::Error),
18    #[error(transparent)]
19    JWK(#[from] ssi::jwk::Error),
20    #[error(transparent)]
21    Null(#[from] std::ffi::NulError),
22    #[error(transparent)]
23    Utf8(#[from] std::str::Utf8Error),
24    #[error(transparent)]
25    Borrow(#[from] std::cell::BorrowError),
26    #[error(transparent)]
27    IO(#[from] std::io::Error),
28    #[error("Unable to generate DID")]
29    UnableToGenerateDID,
30    #[error("Unknown DID method")]
31    UnknownDIDMethod,
32    #[error("Unable to get verification method")]
33    UnableToGetVerificationMethod,
34    #[error("Unknown proof format: {0}")]
35    UnknownProofFormat(String),
36
37    #[doc(hidden)]
38    #[error("")]
39    __Nonexhaustive,
40}
41
42impl Error {
43    pub fn stash(self) {
44        LAST_ERROR.with(|stash| {
45            stash.replace(Some((
46                self.get_code(),
47                CString::new(self.to_string()).unwrap(),
48            )))
49        });
50    }
51
52    fn get_code(&self) -> c_int {
53        // TODO: try to give each individual error its own number
54        match self {
55            Error::VC(_) => 1,
56            Error::Null(_) => 2,
57            Error::Utf8(_) => 3,
58            Error::JWK(_) => 4,
59            Error::Zcap(_) => 5,
60            _ => -1,
61        }
62    }
63}
64
65#[no_mangle]
66/// Retrieve a human-readable description of the most recent error encountered by a DIDKit C
67/// function. The returned string is valid until the next call to a DIDKit function in the current
68/// thread, and should not be mutated or freed. If there has not been any error, `NULL` is returned.
69pub extern "C" fn didkit_error_message() -> *const c_char {
70    LAST_ERROR.with(|error| match error.try_borrow() {
71        Ok(maybe_err_ref) => match &*maybe_err_ref {
72            Some(err) => err.1.as_ptr() as *const c_char,
73            None => ptr::null(),
74        },
75        Err(_) => UNKNOWN_ERROR.as_ptr() as *const c_char,
76    })
77}
78
79#[no_mangle]
80/// Retrieve a numeric code for the most recent error encountered by a DIDKit C function. If there
81/// has not been an error, 0 is returned.
82pub extern "C" fn didkit_error_code() -> c_int {
83    LAST_ERROR.with(|error| match error.try_borrow() {
84        Ok(maybe_err_ref) => match &*maybe_err_ref {
85            Some(err) => err.0,
86            None => 0,
87        },
88        Err(err) => Error::from(err).get_code(),
89    })
90}
91
92impl From<serde_json::Error> for Error {
93    fn from(err: serde_json::Error) -> Error {
94        Error::VC(ssi::vc::Error::from(err))
95    }
96}
97
98impl From<ssi::ldp::Error> for Error {
99    fn from(e: ssi::ldp::Error) -> Error {
100        ssi::vc::Error::from(e).into()
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn errors() {
110        use crate::c::didkit_vc_issue_presentation;
111        use std::ffi::CStr;
112        let presentation = "{}\0".as_ptr() as *const c_char;
113        let options = "{}\0".as_ptr() as *const c_char;
114        let key = "{}\0".as_ptr() as *const c_char;
115        let vp = didkit_vc_issue_presentation(presentation, options, key);
116        assert_eq!(vp, ptr::null());
117        let msg = unsafe { CStr::from_ptr(didkit_error_message()) }
118            .to_str()
119            .unwrap();
120        let code = didkit_error_code();
121        assert_ne!(code, 0);
122        println!("code: {:?} msg: {:?}", code, msg);
123    }
124}