1extern crate alloc;
4use alloc::borrow::Cow;
5use alloc::string::String;
6use core::fmt;
7
8#[cfg(feature = "serde")]
9use alloc::string::ToString;
10
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
30#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
31pub enum ErrorKind {
32 NotFound,
33 InvalidInput,
34 Unauthorized,
35 Conflict,
36 Unavailable,
37 Internal,
38}
39
40impl ErrorKind {
41 pub fn http_status(self) -> u16 {
43 match self {
44 Self::NotFound => 404,
45 Self::InvalidInput => 400,
46 Self::Unauthorized => 403,
47 Self::Conflict => 409,
48 Self::Unavailable => 503,
49 Self::Internal => 500,
50 }
51 }
52
53 pub fn as_str(self) -> &'static str {
55 match self {
56 Self::NotFound => "not_found",
57 Self::InvalidInput => "invalid_input",
58 Self::Unauthorized => "unauthorized",
59 Self::Conflict => "conflict",
60 Self::Unavailable => "unavailable",
61 Self::Internal => "internal",
62 }
63 }
64}
65
66impl fmt::Display for ErrorKind {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 f.write_str(self.as_str())
69 }
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
81#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
82#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
83pub enum ErrorDomain {
84 Db,
85 Query,
86 Runtime,
87 Types,
88}
89
90impl ErrorDomain {
91 pub fn as_str(self) -> &'static str {
93 match self {
94 Self::Db => "db",
95 Self::Query => "query",
96 Self::Runtime => "runtime",
97 Self::Types => "types",
98 }
99 }
100}
101
102impl fmt::Display for ErrorDomain {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 f.write_str(self.as_str())
105 }
106}
107
108#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
114pub struct ErrorCode {
115 domain: ErrorDomain,
116 code: u32,
117}
118
119impl ErrorCode {
120 pub fn new(domain: ErrorDomain, code: u32) -> Self {
122 Self { domain, code }
123 }
124
125 pub fn domain(self) -> ErrorDomain {
127 self.domain
128 }
129
130 pub fn code(self) -> u32 {
132 self.code
133 }
134}
135
136impl fmt::Display for ErrorCode {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 write!(f, "{}:{}", self.domain, self.code)
139 }
140}
141
142#[cfg(feature = "serde")]
143impl Serialize for ErrorCode {
144 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
145 s.serialize_str(&self.to_string())
146 }
147}
148
149#[cfg(feature = "serde")]
150impl<'de> Deserialize<'de> for ErrorCode {
151 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
152 let s = alloc::string::String::deserialize(d)?;
153 let (domain_str, code_str) = s
154 .split_once(':')
155 .ok_or_else(|| serde::de::Error::custom("expected 'domain:N'"))?;
156 let domain = match domain_str {
157 "db" => ErrorDomain::Db,
158 "query" => ErrorDomain::Query,
159 "runtime" => ErrorDomain::Runtime,
160 "types" => ErrorDomain::Types,
161 other => {
162 return Err(serde::de::Error::custom(alloc::format!(
163 "unknown domain: {other}"
164 )))
165 }
166 };
167 let code: u32 = code_str.parse().map_err(serde::de::Error::custom)?;
168 Ok(ErrorCode::new(domain, code))
169 }
170}
171
172#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
176#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
177#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
178pub enum RetryHint {
179 NoRetry,
181 Retryable,
183}
184
185#[derive(Clone, Debug, PartialEq, Eq)]
193pub struct Details {
194 entries: alloc::vec::Vec<(Cow<'static, str>, Cow<'static, str>)>,
195}
196
197impl Details {
198 pub fn new<I>(pairs: I) -> Self
201 where
202 I: IntoIterator<Item = (&'static str, &'static str)>,
203 {
204 let entries: alloc::vec::Vec<_> = pairs
205 .into_iter()
206 .take(8)
207 .map(|(k, v)| (Cow::Borrowed(k), Cow::Borrowed(v)))
208 .collect();
209 Self { entries }
210 }
211
212 pub fn get(&self, key: &str) -> Option<&str> {
214 self.entries
215 .iter()
216 .find(|(k, _)| k.as_ref() == key)
217 .map(|(_, v)| v.as_ref())
218 }
219
220 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> + '_ {
222 self.entries.iter().map(|(k, v)| (k.as_ref(), v.as_ref()))
223 }
224}
225
226#[cfg(feature = "serde")]
227impl Serialize for Details {
228 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
229 use serde::ser::SerializeMap;
230 let mut map = s.serialize_map(Some(self.entries.len()))?;
231 for (k, v) in &self.entries {
232 map.serialize_entry(k.as_ref(), v.as_ref())?;
233 }
234 map.end()
235 }
236}
237
238#[cfg(feature = "serde")]
239impl<'de> Deserialize<'de> for Details {
240 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
241 use serde::de::{MapAccess, Visitor};
242
243 struct DetailsVisitor;
244
245 impl<'de> Visitor<'de> for DetailsVisitor {
246 type Value = Details;
247
248 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249 f.write_str("a map of string key-value pairs")
250 }
251
252 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Details, A::Error> {
253 let mut entries: alloc::vec::Vec<(Cow<'static, str>, Cow<'static, str>)> =
254 alloc::vec::Vec::new();
255 while let Some((k, v)) = map.next_entry::<String, String>()? {
256 if entries.len() >= 8 {
257 break;
258 }
259 entries.push((Cow::Owned(k), Cow::Owned(v)));
260 }
261 Ok(Details { entries })
262 }
263 }
264
265 d.deserialize_map(DetailsVisitor)
266 }
267}
268
269#[derive(Clone, Debug)]
286#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
287pub struct KhiveError {
288 kind: ErrorKind,
289 message: String,
290 code: Option<ErrorCode>,
291 details: Option<Details>,
292}
293
294impl KhiveError {
295 pub fn not_found(resource: impl fmt::Display, id: impl fmt::Display) -> Self {
299 Self {
300 kind: ErrorKind::NotFound,
301 message: alloc::format!("{resource} not found: {id}"),
302 code: None,
303 details: None,
304 }
305 }
306
307 pub fn invalid_input(message: impl Into<String>) -> Self {
309 Self {
310 kind: ErrorKind::InvalidInput,
311 message: alloc::format!("invalid input: {}", message.into()),
312 code: None,
313 details: None,
314 }
315 }
316
317 pub fn unauthorized(message: impl Into<String>) -> Self {
319 Self {
320 kind: ErrorKind::Unauthorized,
321 message: alloc::format!("unauthorized: {}", message.into()),
322 code: None,
323 details: None,
324 }
325 }
326
327 pub fn conflict(message: impl Into<String>) -> Self {
329 Self {
330 kind: ErrorKind::Conflict,
331 message: alloc::format!("conflict: {}", message.into()),
332 code: None,
333 details: None,
334 }
335 }
336
337 pub fn unavailable(message: impl Into<String>) -> Self {
339 Self {
340 kind: ErrorKind::Unavailable,
341 message: alloc::format!("unavailable: {}", message.into()),
342 code: None,
343 details: None,
344 }
345 }
346
347 pub fn internal(message: impl Into<String>) -> Self {
349 Self {
350 kind: ErrorKind::Internal,
351 message: alloc::format!("internal: {}", message.into()),
352 code: None,
353 details: None,
354 }
355 }
356
357 pub fn with_code(mut self, code: ErrorCode) -> Self {
361 self.code = Some(code);
362 self
363 }
364
365 pub fn with_details(mut self, details: Details) -> Self {
367 self.details = Some(details);
368 self
369 }
370
371 pub fn kind(&self) -> ErrorKind {
375 self.kind
376 }
377
378 pub fn message(&self) -> &str {
380 &self.message
381 }
382
383 pub fn code(&self) -> Option<ErrorCode> {
385 self.code
386 }
387
388 pub fn details(&self) -> Option<&Details> {
390 self.details.as_ref()
391 }
392
393 pub fn retry_hint(&self) -> RetryHint {
395 match self.kind {
396 ErrorKind::Unavailable => RetryHint::Retryable,
397 _ => RetryHint::NoRetry,
398 }
399 }
400}
401
402impl fmt::Display for KhiveError {
403 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404 write!(f, "{}", self.message)
405 }
406}
407
408#[cfg(feature = "std")]
409impl std::error::Error for KhiveError {}