Skip to main content

storage_kit/core/
error.rs

1use thiserror::Error;
2
3/// Portable error type returned by the storage Kit.
4///
5/// Portable request families report unsupported backend behavior explicitly
6/// instead of synthesizing successful responses. Use [`StorageError::InvalidRequest`]
7/// for semantically invalid caller input that is already structurally valid Rust.
8///
9/// # Examples
10///
11/// ```
12/// let error = storage_kit::StorageError::InvalidRequest(
13///     "vector value cannot be coerced without precision loss".into(),
14/// );
15///
16/// assert!(error.to_string().contains("invalid request"));
17/// ```
18///
19#[derive(Debug, Clone, Error)]
20pub enum StorageError {
21    /// The supplied configuration is structurally invalid.
22    #[error("invalid configuration: {0}")]
23    InvalidConfig(String),
24
25    /// A portable request is structurally valid Rust but semantically invalid.
26    ///
27    /// Vector coercion policy failures and malformed typed requests use this
28    /// variant before a backend operation is attempted.
29    #[error("invalid request: {0}")]
30    InvalidRequest(String),
31
32    /// The selected backend does not expose the requested operation yet.
33    #[error("database operation is not implemented yet: {note}")]
34    NotYetImplemented {
35        /// Human-readable note describing the missing support.
36        note: String,
37    },
38
39    /// The backend or mode does not support a requested capability.
40    #[error("unsupported capability `{capability}`{context}")]
41    UnsupportedCapability {
42        /// Missing or unsupported capability name.
43        capability: String,
44        /// Additional human-readable context.
45        context: String,
46    },
47
48    /// A backend-specific operation failed.
49    #[error("database operation `{operation}` failed: {message}")]
50    BackendOperation {
51        /// Portable operation label used by the adapter.
52        operation: String,
53        /// Human-readable backend error message.
54        message: String,
55    },
56
57    /// Serialization or wire-format conversion failed.
58    #[error("serialization error: {0}")]
59    Serialization(String),
60
61    /// Filesystem or OS I/O failed.
62    #[error("io error: {0}")]
63    Io(String),
64}
65
66impl StorageError {
67    /// Creates a [`StorageError::NotYetImplemented`] value.
68    ///
69    /// # Examples
70    ///
71    /// ```
72    /// let error = storage_kit::StorageError::not_yet_implemented(
73    ///     "retained operation mapping is pending",
74    /// );
75    /// assert!(matches!(
76    ///     error,
77    ///     storage_kit::StorageError::NotYetImplemented { .. }
78    /// ));
79    /// ```
80    pub fn not_yet_implemented(note: impl Into<String>) -> Self {
81        Self::NotYetImplemented { note: note.into() }
82    }
83
84    /// Creates a [`StorageError::UnsupportedCapability`] value.
85    ///
86    /// # Examples
87    ///
88    /// ```
89    /// let error = storage_kit::StorageError::unsupported_capability(
90    ///     "FULL_TEXT",
91    ///     "selected backend cannot provide this full-text query",
92    /// );
93    /// assert!(error.to_string().contains("FULL_TEXT"));
94    /// ```
95    pub fn unsupported_capability(
96        capability: impl Into<String>,
97        context: impl Into<String>,
98    ) -> Self {
99        let context = context.into();
100        Self::UnsupportedCapability {
101            capability: capability.into(),
102            context: if context.is_empty() {
103                String::new()
104            } else {
105                format!(" ({context})")
106            },
107        }
108    }
109
110    /// Creates a [`StorageError::BackendOperation`] value.
111    ///
112    /// # Examples
113    ///
114    /// ```
115    /// let error = storage_kit::StorageError::backend_op(
116    ///     "query",
117    ///     "provider returned an error",
118    /// );
119    /// let cloned = error.clone();
120    /// assert_eq!(error.to_string(), cloned.to_string());
121    /// ```
122    pub fn backend_op(operation: impl Into<String>, message: impl Into<String>) -> Self {
123        Self::BackendOperation {
124            operation: operation.into(),
125            message: message.into(),
126        }
127    }
128}
129
130impl From<serde_json::Error> for StorageError {
131    fn from(value: serde_json::Error) -> Self {
132        Self::Serialization(value.to_string())
133    }
134}
135
136impl From<std::io::Error> for StorageError {
137    fn from(value: std::io::Error) -> Self {
138        Self::Io(value.to_string())
139    }
140}