Skip to main content

confium_store/
error.rs

1//! Error model for the Store crate.
2//!
3//! Mirrors the conventions in `confium-core::error`: a snafu enum with an
4//! `ErrorCode` repr-u32 mirror used to surface a stable numeric result
5//! through the FFI boundary. The Store crate owns its own error type so it
6//! can be compiled and linked independently of the Engine (the Store is a
7//! Confium plugin in its own right, and its backends are registered inside
8//! this crate).
9
10use snafu::Backtrace;
11use snafu::Snafu;
12
13pub type Result<T> = std::result::Result<T, Error>;
14
15#[derive(Snafu, Debug)]
16#[snafu(visibility(pub))]
17pub enum Error {
18    #[snafu(display("NULL pointer on parameter '{}'", param))]
19    NullPointer {
20        param: &'static str,
21        backtrace: Backtrace,
22    },
23    #[snafu(display("Invalid UTF-8"))]
24    InvalidUTF8 {
25        backtrace: Backtrace,
26        source: std::str::Utf8Error,
27    },
28
29    #[snafu(display("Value not found"))]
30    ValueNotFound,
31    #[snafu(display("Invalid compartment: {}", value))]
32    InvalidCompartment { value: u32 },
33    #[snafu(display("Feature not implemented: {}", what))]
34    NotImplemented { what: &'static str },
35
36    #[snafu(display("Unknown backend: '{}'", name))]
37    UnknownBackend { name: String },
38
39    #[snafu(display("Invalid path component: '{}'", component))]
40    InvalidPath { component: String },
41
42    #[snafu(display("Identity signature invalid"))]
43    IdentitySignatureInvalid,
44
45    #[snafu(display("I/O error: {}", source))]
46    Io {
47        source: std::io::Error,
48        backtrace: Backtrace,
49    },
50
51    #[snafu(display("Wrapped error: {}", message))]
52    Wrapped { message: String },
53}
54
55impl Error {
56    pub fn code(&self) -> u32 {
57        error_code(self)
58    }
59}
60
61/// Numeric result codes returned through the FFI. The encoding starts at
62/// `0x1000` to leave room for the Engine's error namespace (whose codes
63/// begin at 1) — the Store is a separate crate and must not collide.
64#[allow(non_camel_case_types)]
65#[repr(u32)]
66pub enum ErrorCode {
67    UNKNOWN = 0x1000,
68    NULL_POINTER = 0x1001,
69    INVALID_UTF8 = 0x1002,
70
71    VALUE_NOT_FOUND = 0x1010,
72    INVALID_COMPARTMENT = 0x1011,
73    NOT_IMPLEMENTED = 0x1012,
74
75    UNKNOWN_BACKEND = 0x1020,
76
77    INVALID_PATH = 0x1031,
78    IO = 0x1032,
79
80    IDENTITY_SIGNATURE_INVALID = 0x1030,
81
82    WRAPPED = 0x1100,
83}
84
85fn error_code(error: &Error) -> u32 {
86    match error {
87        Error::NullPointer { .. } => ErrorCode::NULL_POINTER.into(),
88        Error::InvalidUTF8 { .. } => ErrorCode::INVALID_UTF8.into(),
89
90        Error::ValueNotFound => ErrorCode::VALUE_NOT_FOUND.into(),
91        Error::InvalidCompartment { .. } => ErrorCode::INVALID_COMPARTMENT.into(),
92        Error::NotImplemented { .. } => ErrorCode::NOT_IMPLEMENTED.into(),
93
94        Error::UnknownBackend { .. } => ErrorCode::UNKNOWN_BACKEND.into(),
95
96        Error::InvalidPath { .. } => ErrorCode::INVALID_PATH.into(),
97        Error::Io { .. } => ErrorCode::IO.into(),
98
99        Error::IdentitySignatureInvalid => ErrorCode::IDENTITY_SIGNATURE_INVALID.into(),
100
101        Error::Wrapped { .. } => ErrorCode::WRAPPED.into(),
102    }
103}
104
105impl From<ErrorCode> for u32 {
106    #[inline]
107    fn from(code: ErrorCode) -> u32 {
108        code as u32
109    }
110}
111
112impl From<Error> for u32 {
113    #[inline]
114    fn from(err: Error) -> u32 {
115        error_code(&err)
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use snafu::GenerateImplicitData;
123
124    #[test]
125    fn value_not_found_code() {
126        let err = Error::ValueNotFound;
127        assert_eq!(err.code(), ErrorCode::VALUE_NOT_FOUND as u32);
128    }
129
130    #[test]
131    fn not_implemented_carries_context() {
132        let err = Error::NotImplemented {
133            what: "filesystem backend",
134        };
135        assert!(format!("{err}").contains("filesystem backend"));
136        assert_eq!(err.code(), ErrorCode::NOT_IMPLEMENTED as u32);
137    }
138
139    #[test]
140    fn null_pointer_has_param_in_display() {
141        let err = Error::NullPointer {
142            param: "ks",
143            backtrace: Backtrace::generate(),
144        };
145        assert!(format!("{err}").contains("ks"));
146    }
147}