llm_sentinel_core/
error.rs1use std::fmt;
7
8pub type Result<T> = std::result::Result<T, Error>;
10
11#[derive(Debug, thiserror::Error)]
13pub enum Error {
14 #[error("Configuration error: {0}")]
16 Config(String),
17
18 #[error("Serialization error: {0}")]
20 Serialization(#[from] serde_json::Error),
21
22 #[error("Validation error: {0}")]
24 Validation(String),
25
26 #[error("IO error: {0}")]
28 Io(#[from] std::io::Error),
29
30 #[error("Connection error: {0}")]
32 Connection(String),
33
34 #[error("Storage error: {0}")]
36 Storage(String),
37
38 #[error("Ingestion error: {0}")]
40 Ingestion(String),
41
42 #[error("Detection error: {0}")]
44 Detection(String),
45
46 #[error("Alerting error: {0}")]
48 Alerting(String),
49
50 #[error("Internal error: {0}")]
52 Internal(String),
53
54 #[error("{0} not found")]
56 NotFound(String),
57
58 #[error("{0} already exists")]
60 AlreadyExists(String),
61
62 #[error("Operation timed out: {0}")]
64 Timeout(String),
65
66 #[error("Rate limit exceeded: {0}")]
68 RateLimit(String),
69
70 #[error("{context}: {source}")]
72 WithContext {
73 context: String,
75 source: Box<Error>,
77 },
78}
79
80impl Error {
81 pub fn context<C: fmt::Display>(self, context: C) -> Self {
83 Error::WithContext {
84 context: context.to_string(),
85 source: Box::new(self),
86 }
87 }
88
89 pub fn config<S: Into<String>>(msg: S) -> Self {
91 Error::Config(msg.into())
92 }
93
94 pub fn validation<S: Into<String>>(msg: S) -> Self {
96 Error::Validation(msg.into())
97 }
98
99 pub fn connection<S: Into<String>>(msg: S) -> Self {
101 Error::Connection(msg.into())
102 }
103
104 pub fn storage<S: Into<String>>(msg: S) -> Self {
106 Error::Storage(msg.into())
107 }
108
109 pub fn ingestion<S: Into<String>>(msg: S) -> Self {
111 Error::Ingestion(msg.into())
112 }
113
114 pub fn detection<S: Into<String>>(msg: S) -> Self {
116 Error::Detection(msg.into())
117 }
118
119 pub fn alerting<S: Into<String>>(msg: S) -> Self {
121 Error::Alerting(msg.into())
122 }
123
124 pub fn internal<S: Into<String>>(msg: S) -> Self {
126 Error::Internal(msg.into())
127 }
128
129 pub fn not_found<S: Into<String>>(item: S) -> Self {
131 Error::NotFound(item.into())
132 }
133
134 pub fn already_exists<S: Into<String>>(item: S) -> Self {
136 Error::AlreadyExists(item.into())
137 }
138
139 pub fn timeout<S: Into<String>>(msg: S) -> Self {
141 Error::Timeout(msg.into())
142 }
143
144 pub fn rate_limit<S: Into<String>>(msg: S) -> Self {
146 Error::RateLimit(msg.into())
147 }
148
149 pub fn is_retryable(&self) -> bool {
151 matches!(
152 self,
153 Error::Connection(_) | Error::Timeout(_) | Error::Storage(_)
154 )
155 }
156
157 pub fn is_transient(&self) -> bool {
159 matches!(
160 self,
161 Error::Connection(_) | Error::Timeout(_) | Error::RateLimit(_)
162 )
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_error_creation() {
172 let err = Error::config("test error");
173 assert!(matches!(err, Error::Config(_)));
174 }
175
176 #[test]
177 fn test_error_context() {
178 let err = Error::storage("database error").context("Failed to save event");
179 assert!(matches!(err, Error::WithContext { .. }));
180 }
181
182 #[test]
183 fn test_error_retryable() {
184 assert!(Error::connection("test").is_retryable());
185 assert!(Error::timeout("test").is_retryable());
186 assert!(!Error::validation("test").is_retryable());
187 }
188
189 #[test]
190 fn test_error_transient() {
191 assert!(Error::connection("test").is_transient());
192 assert!(Error::rate_limit("test").is_transient());
193 assert!(!Error::validation("test").is_transient());
194 }
195}