1use std::fmt;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12#[repr(u32)]
13pub enum ErrorCode {
14 NotFound = 1,
15 AlreadyExists = 2,
16 InvalidArgument = 3,
17 PermissionDenied = 4,
18 FailedPrecondition = 5,
19 ResourceExhausted = 6,
20 Unavailable = 7,
21 InternalError = 8,
22 NotSupported = 9,
23 Unknown = 10,
24}
25
26impl From<u32> for ErrorCode {
27 fn from(value: u32) -> Self {
28 match value {
29 1 => Self::NotFound,
30 2 => Self::AlreadyExists,
31 3 => Self::InvalidArgument,
32 4 => Self::PermissionDenied,
33 5 => Self::FailedPrecondition,
34 6 => Self::ResourceExhausted,
35 7 => Self::Unavailable,
36 8 => Self::InternalError,
37 9 => Self::NotSupported,
38 _ => Self::Unknown,
39 }
40 }
41}
42
43impl From<ErrorCode> for u32 {
44 fn from(value: ErrorCode) -> Self {
45 value as u32
46 }
47}
48
49impl fmt::Display for ErrorCode {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 let name = match self {
52 Self::NotFound => "NotFound",
53 Self::AlreadyExists => "AlreadyExists",
54 Self::InvalidArgument => "InvalidArgument",
55 Self::PermissionDenied => "PermissionDenied",
56 Self::FailedPrecondition => "FailedPrecondition",
57 Self::ResourceExhausted => "ResourceExhausted",
58 Self::Unavailable => "Unavailable",
59 Self::InternalError => "InternalError",
60 Self::NotSupported => "NotSupported",
61 Self::Unknown => "Unknown",
62 };
63 f.write_str(name)
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct Error {
70 pub code: ErrorCode,
72 pub message: String,
74}
75
76impl Error {
77 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
79 Self {
80 code,
81 message: message.into(),
82 }
83 }
84
85 pub fn not_found(message: impl Into<String>) -> Self {
86 Self::new(ErrorCode::NotFound, message)
87 }
88
89 pub fn already_exists(message: impl Into<String>) -> Self {
90 Self::new(ErrorCode::AlreadyExists, message)
91 }
92
93 pub fn invalid_argument(message: impl Into<String>) -> Self {
94 Self::new(ErrorCode::InvalidArgument, message)
95 }
96
97 pub fn permission_denied(message: impl Into<String>) -> Self {
98 Self::new(ErrorCode::PermissionDenied, message)
99 }
100
101 pub fn failed_precondition(message: impl Into<String>) -> Self {
102 Self::new(ErrorCode::FailedPrecondition, message)
103 }
104
105 pub fn internal(message: impl Into<String>) -> Self {
106 Self::new(ErrorCode::InternalError, message)
107 }
108
109 pub fn resource_exhausted(message: impl Into<String>) -> Self {
110 Self::new(ErrorCode::ResourceExhausted, message)
111 }
112
113 pub fn not_supported(message: impl Into<String>) -> Self {
114 Self::new(ErrorCode::NotSupported, message)
115 }
116
117 pub fn is_not_found(&self) -> bool {
118 self.code == ErrorCode::NotFound
119 }
120
121 pub fn is_already_exists(&self) -> bool {
122 self.code == ErrorCode::AlreadyExists
123 }
124
125 pub fn is_invalid_argument(&self) -> bool {
126 self.code == ErrorCode::InvalidArgument
127 }
128}
129
130impl fmt::Display for Error {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 write!(f, "a3s-vec error {}: {}", self.code, self.message)
133 }
134}
135
136impl std::error::Error for Error {}
137
138pub type Result<T> = std::result::Result<T, Error>;
140
141impl From<std::io::Error> for Error {
142 fn from(value: std::io::Error) -> Self {
143 let code = match value.kind() {
144 std::io::ErrorKind::NotFound => ErrorCode::NotFound,
145 std::io::ErrorKind::PermissionDenied => ErrorCode::PermissionDenied,
146 std::io::ErrorKind::AlreadyExists => ErrorCode::AlreadyExists,
147 _ => ErrorCode::InternalError,
148 };
149 Self::new(code, value.to_string())
150 }
151}
152
153impl From<serde_json::Error> for Error {
154 fn from(value: serde_json::Error) -> Self {
155 Self::new(ErrorCode::InternalError, format!("JSON error: {value}"))
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn status_codes_are_stable() {
165 for code in [
166 ErrorCode::NotFound,
167 ErrorCode::AlreadyExists,
168 ErrorCode::InvalidArgument,
169 ErrorCode::PermissionDenied,
170 ErrorCode::FailedPrecondition,
171 ErrorCode::ResourceExhausted,
172 ErrorCode::Unavailable,
173 ErrorCode::InternalError,
174 ErrorCode::NotSupported,
175 ErrorCode::Unknown,
176 ] {
177 assert_eq!(ErrorCode::from(u32::from(code)), code);
178 assert!(!format!("{code}").is_empty());
179 }
180 assert_eq!(u32::from(ErrorCode::InvalidArgument), 3);
181 assert_eq!(ErrorCode::from(99), ErrorCode::Unknown);
182 }
183
184 #[test]
185 fn helpers_preserve_context() {
186 let error = Error::not_found("document x");
187 assert!(error.is_not_found());
188 assert!(error.to_string().contains("document x"));
189 assert_eq!(Error::already_exists("dup").code, ErrorCode::AlreadyExists);
190 assert_eq!(
191 Error::invalid_argument("bad").code,
192 ErrorCode::InvalidArgument
193 );
194 assert_eq!(
195 Error::permission_denied("no").code,
196 ErrorCode::PermissionDenied
197 );
198 assert_eq!(
199 Error::failed_precondition("pre").code,
200 ErrorCode::FailedPrecondition
201 );
202 assert_eq!(
203 Error::resource_exhausted("oom").code,
204 ErrorCode::ResourceExhausted
205 );
206 assert_eq!(Error::internal("boom").code, ErrorCode::InternalError);
207 assert_eq!(Error::not_supported("yet").code, ErrorCode::NotSupported);
208 assert_eq!(
209 Error::new(ErrorCode::Unavailable, "down").code,
210 ErrorCode::Unavailable
211 );
212 }
213
214 #[test]
215 fn json_errors_map_to_internal() {
216 let err: Error = serde_json::from_str::<serde_json::Value>("{")
217 .unwrap_err()
218 .into();
219 assert_eq!(err.code, ErrorCode::InternalError);
220 assert!(err.message.contains("JSON"));
221 }
222
223 #[test]
224 fn io_and_predicate_helpers_cover_surface() {
225 let not_found: Error = std::io::Error::new(std::io::ErrorKind::NotFound, "gone").into();
226 assert!(not_found.is_not_found());
227 let exists: Error = std::io::Error::new(std::io::ErrorKind::AlreadyExists, "here").into();
228 assert!(exists.is_already_exists());
229 let denied: Error = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "no").into();
230 assert_eq!(denied.code, ErrorCode::PermissionDenied);
231 let other: Error = std::io::Error::other("boom").into();
232 assert_eq!(other.code, ErrorCode::InternalError);
233 assert!(Error::invalid_argument("x").is_invalid_argument());
234 assert!(!Error::not_found("y").is_invalid_argument());
235 }
236}