Skip to main content

relay_knowledge/api/
error.rs

1use serde::{Deserialize, Serialize};
2
3use super::ApiMetadata;
4
5/// Stable error categories used across interface adapters.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum ErrorKind {
9    InvalidArgument,
10    StorageUnavailable,
11    Timeout,
12    Internal,
13}
14
15/// API error shape suitable for JSON and streaming output.
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct ApiError {
18    pub error_kind: ErrorKind,
19    pub message: String,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub metadata: Option<ApiMetadata>,
22}
23
24impl ApiError {
25    /// Creates an invalid argument error.
26    pub fn invalid_argument(message: impl Into<String>) -> Self {
27        Self {
28            error_kind: ErrorKind::InvalidArgument,
29            message: message.into(),
30            metadata: None,
31        }
32    }
33
34    /// Creates a storage boundary error without exposing backend internals.
35    pub fn storage_unavailable(message: impl Into<String>) -> Self {
36        Self {
37            error_kind: ErrorKind::StorageUnavailable,
38            message: message.into(),
39            metadata: None,
40        }
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn builds_stable_error_shapes() {
50        let invalid = ApiError::invalid_argument("bad input");
51        let storage = ApiError::storage_unavailable("database busy");
52
53        assert_eq!(invalid.error_kind, ErrorKind::InvalidArgument);
54        assert_eq!(invalid.message, "bad input");
55        assert_eq!(storage.error_kind, ErrorKind::StorageUnavailable);
56        assert_eq!(storage.message, "database busy");
57    }
58}