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    QosRejected,
12    Timeout,
13    Internal,
14}
15
16/// API error shape suitable for JSON and streaming output.
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18pub struct ApiError {
19    pub error_kind: ErrorKind,
20    pub message: String,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub metadata: Option<ApiMetadata>,
23}
24
25impl ApiError {
26    /// Creates an invalid argument error.
27    pub fn invalid_argument(message: impl Into<String>) -> Self {
28        Self {
29            error_kind: ErrorKind::InvalidArgument,
30            message: message.into(),
31            metadata: None,
32        }
33    }
34
35    /// Creates a storage boundary error without exposing backend internals.
36    pub fn storage_unavailable(message: impl Into<String>) -> Self {
37        Self {
38            error_kind: ErrorKind::StorageUnavailable,
39            message: message.into(),
40            metadata: None,
41        }
42    }
43
44    /// Creates a QoS admission error for bounded overload responses.
45    pub fn qos_rejected(message: impl Into<String>) -> Self {
46        Self {
47            error_kind: ErrorKind::QosRejected,
48            message: message.into(),
49            metadata: None,
50        }
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn builds_stable_error_shapes() {
60        let invalid = ApiError::invalid_argument("bad input");
61        let storage = ApiError::storage_unavailable("database busy");
62        let qos = ApiError::qos_rejected("request budget exhausted");
63
64        assert_eq!(invalid.error_kind, ErrorKind::InvalidArgument);
65        assert_eq!(invalid.message, "bad input");
66        assert_eq!(storage.error_kind, ErrorKind::StorageUnavailable);
67        assert_eq!(storage.message, "database busy");
68        assert_eq!(qos.error_kind, ErrorKind::QosRejected);
69        assert_eq!(qos.message, "request budget exhausted");
70    }
71}