1extern crate alloc;
17use alloc::borrow::Cow;
18use alloc::string::String;
19use core::fmt;
20
21#[cfg(feature = "serde")]
22use alloc::string::ToString;
23
24#[cfg(feature = "serde")]
25use serde::{Deserialize, Serialize};
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
42#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
43#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
44pub enum ErrorKind {
45 NotFound,
46 InvalidInput,
47 Unauthorized,
48 Conflict,
49 Unavailable,
50 Internal,
51}
52
53impl ErrorKind {
54 pub fn http_status(self) -> u16 {
56 match self {
57 Self::NotFound => 404,
58 Self::InvalidInput => 400,
59 Self::Unauthorized => 403,
60 Self::Conflict => 409,
61 Self::Unavailable => 503,
62 Self::Internal => 500,
63 }
64 }
65
66 pub fn as_str(self) -> &'static str {
68 match self {
69 Self::NotFound => "not_found",
70 Self::InvalidInput => "invalid_input",
71 Self::Unauthorized => "unauthorized",
72 Self::Conflict => "conflict",
73 Self::Unavailable => "unavailable",
74 Self::Internal => "internal",
75 }
76 }
77}
78
79impl fmt::Display for ErrorKind {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 f.write_str(self.as_str())
82 }
83}
84
85#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
94#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
95#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
96pub enum ErrorDomain {
97 Db,
98 Query,
99 Runtime,
100 Types,
101}
102
103impl ErrorDomain {
104 pub fn as_str(self) -> &'static str {
105 match self {
106 Self::Db => "db",
107 Self::Query => "query",
108 Self::Runtime => "runtime",
109 Self::Types => "types",
110 }
111 }
112}
113
114impl fmt::Display for ErrorDomain {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 f.write_str(self.as_str())
117 }
118}
119
120#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
126pub struct ErrorCode {
127 domain: ErrorDomain,
128 code: u32,
129}
130
131impl ErrorCode {
132 pub fn new(domain: ErrorDomain, code: u32) -> Self {
133 Self { domain, code }
134 }
135
136 pub fn domain(self) -> ErrorDomain {
137 self.domain
138 }
139
140 pub fn code(self) -> u32 {
141 self.code
142 }
143}
144
145impl fmt::Display for ErrorCode {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 write!(f, "{}:{}", self.domain, self.code)
148 }
149}
150
151#[cfg(feature = "serde")]
152impl Serialize for ErrorCode {
153 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
154 s.serialize_str(&self.to_string())
155 }
156}
157
158#[cfg(feature = "serde")]
159impl<'de> Deserialize<'de> for ErrorCode {
160 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
161 let s = alloc::string::String::deserialize(d)?;
162 let (domain_str, code_str) = s
163 .split_once(':')
164 .ok_or_else(|| serde::de::Error::custom("expected 'domain:N'"))?;
165 let domain = match domain_str {
166 "db" => ErrorDomain::Db,
167 "query" => ErrorDomain::Query,
168 "runtime" => ErrorDomain::Runtime,
169 "types" => ErrorDomain::Types,
170 other => {
171 return Err(serde::de::Error::custom(alloc::format!(
172 "unknown domain: {other}"
173 )))
174 }
175 };
176 let code: u32 = code_str.parse().map_err(serde::de::Error::custom)?;
177 Ok(ErrorCode::new(domain, code))
178 }
179}
180
181#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
185#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
186#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
187pub enum RetryHint {
188 NoRetry,
190 Retryable,
192}
193
194#[derive(Clone, Debug, PartialEq, Eq)]
202pub struct Details {
203 entries: alloc::vec::Vec<(Cow<'static, str>, Cow<'static, str>)>,
204}
205
206impl Details {
207 pub fn new<I>(pairs: I) -> Self
210 where
211 I: IntoIterator<Item = (&'static str, &'static str)>,
212 {
213 let entries: alloc::vec::Vec<_> = pairs
214 .into_iter()
215 .take(8)
216 .map(|(k, v)| (Cow::Borrowed(k), Cow::Borrowed(v)))
217 .collect();
218 Self { entries }
219 }
220
221 pub fn get(&self, key: &str) -> Option<&str> {
223 self.entries
224 .iter()
225 .find(|(k, _)| k.as_ref() == key)
226 .map(|(_, v)| v.as_ref())
227 }
228
229 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> + '_ {
231 self.entries.iter().map(|(k, v)| (k.as_ref(), v.as_ref()))
232 }
233}
234
235#[cfg(feature = "serde")]
236impl Serialize for Details {
237 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
238 use serde::ser::SerializeMap;
239 let mut map = s.serialize_map(Some(self.entries.len()))?;
240 for (k, v) in &self.entries {
241 map.serialize_entry(k.as_ref(), v.as_ref())?;
242 }
243 map.end()
244 }
245}
246
247#[cfg(feature = "serde")]
248impl<'de> Deserialize<'de> for Details {
249 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
250 use serde::de::{MapAccess, Visitor};
251
252 struct DetailsVisitor;
253
254 impl<'de> Visitor<'de> for DetailsVisitor {
255 type Value = Details;
256
257 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 f.write_str("a map of string key-value pairs")
259 }
260
261 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Details, A::Error> {
262 let mut entries: alloc::vec::Vec<(Cow<'static, str>, Cow<'static, str>)> =
263 alloc::vec::Vec::new();
264 while let Some((k, v)) = map.next_entry::<String, String>()? {
265 if entries.len() >= 8 {
266 break;
267 }
268 entries.push((Cow::Owned(k), Cow::Owned(v)));
269 }
270 Ok(Details { entries })
271 }
272 }
273
274 d.deserialize_map(DetailsVisitor)
275 }
276}
277
278#[derive(Clone, Debug)]
295#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
296pub struct KhiveError {
297 kind: ErrorKind,
298 message: String,
299 code: Option<ErrorCode>,
300 details: Option<Details>,
301}
302
303impl KhiveError {
304 pub fn not_found(resource: impl fmt::Display, id: impl fmt::Display) -> Self {
307 Self {
308 kind: ErrorKind::NotFound,
309 message: alloc::format!("{resource} not found: {id}"),
310 code: None,
311 details: None,
312 }
313 }
314
315 pub fn invalid_input(message: impl Into<String>) -> Self {
316 Self {
317 kind: ErrorKind::InvalidInput,
318 message: alloc::format!("invalid input: {}", message.into()),
319 code: None,
320 details: None,
321 }
322 }
323
324 pub fn unauthorized(message: impl Into<String>) -> Self {
325 Self {
326 kind: ErrorKind::Unauthorized,
327 message: alloc::format!("unauthorized: {}", message.into()),
328 code: None,
329 details: None,
330 }
331 }
332
333 pub fn conflict(message: impl Into<String>) -> Self {
334 Self {
335 kind: ErrorKind::Conflict,
336 message: alloc::format!("conflict: {}", message.into()),
337 code: None,
338 details: None,
339 }
340 }
341
342 pub fn unavailable(message: impl Into<String>) -> Self {
343 Self {
344 kind: ErrorKind::Unavailable,
345 message: alloc::format!("unavailable: {}", message.into()),
346 code: None,
347 details: None,
348 }
349 }
350
351 pub fn internal(message: impl Into<String>) -> Self {
352 Self {
353 kind: ErrorKind::Internal,
354 message: alloc::format!("internal: {}", message.into()),
355 code: None,
356 details: None,
357 }
358 }
359
360 pub fn with_code(mut self, code: ErrorCode) -> Self {
363 self.code = Some(code);
364 self
365 }
366
367 pub fn with_details(mut self, details: Details) -> Self {
368 self.details = Some(details);
369 self
370 }
371
372 pub fn kind(&self) -> ErrorKind {
375 self.kind
376 }
377
378 pub fn message(&self) -> &str {
379 &self.message
380 }
381
382 pub fn code(&self) -> Option<ErrorCode> {
383 self.code
384 }
385
386 pub fn details(&self) -> Option<&Details> {
387 self.details.as_ref()
388 }
389
390 pub fn retry_hint(&self) -> RetryHint {
392 match self.kind {
393 ErrorKind::Unavailable => RetryHint::Retryable,
394 _ => RetryHint::NoRetry,
395 }
396 }
397}
398
399impl fmt::Display for KhiveError {
400 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401 write!(f, "{}", self.message)
402 }
403}
404
405#[cfg(feature = "std")]
406impl std::error::Error for KhiveError {}