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
185pub const DETAILS_TRUNCATED_KEY: &str = "details_truncated";
192
193#[derive(Clone, Debug, PartialEq, Eq)]
211pub struct Details {
212 entries: alloc::vec::Vec<(Cow<'static, str>, Cow<'static, str>)>,
213 dropped: Option<usize>,
219}
220
221impl Details {
222 pub fn new<I>(pairs: I) -> Self
232 where
233 I: IntoIterator<Item = (&'static str, &'static str)>,
234 {
235 let all: alloc::vec::Vec<(&'static str, &'static str)> = pairs.into_iter().collect();
236 Self::from_owned(
237 all.into_iter()
238 .map(|(k, v)| (Cow::Borrowed(k), Cow::Borrowed(v))),
239 )
240 }
241
242 fn from_owned<I>(pairs: I) -> Self
246 where
247 I: IntoIterator<Item = (Cow<'static, str>, Cow<'static, str>)>,
248 {
249 let mut ordinary: alloc::vec::Vec<(Cow<'static, str>, Cow<'static, str>)> =
250 alloc::vec::Vec::new();
251 let mut total_ordinary: usize = 0;
252 let mut collisions: usize = 0;
253 for (k, v) in pairs {
254 if k.as_ref() == DETAILS_TRUNCATED_KEY {
255 collisions += 1;
256 } else {
257 total_ordinary += 1;
258 if ordinary.len() < 8 {
259 ordinary.push((k, v));
260 }
261 }
262 }
263 Self::build(ordinary, total_ordinary, collisions)
264 }
265
266 fn build(
269 ordinary: alloc::vec::Vec<(Cow<'static, str>, Cow<'static, str>)>,
270 total_ordinary: usize,
271 collisions: usize,
272 ) -> Self {
273 if total_ordinary <= 8 && collisions == 0 {
274 return Self {
275 entries: ordinary,
276 dropped: None,
277 };
278 }
279 let keep = total_ordinary.min(7);
280 let dropped = (total_ordinary - keep) + collisions;
281 let mut entries: alloc::vec::Vec<_> = ordinary.into_iter().take(keep).collect();
282 entries.push((
283 Cow::Borrowed(DETAILS_TRUNCATED_KEY),
284 Cow::Owned(alloc::format!("{dropped}")),
285 ));
286 Self {
287 entries,
288 dropped: Some(dropped),
289 }
290 }
291
292 pub fn get(&self, key: &str) -> Option<&str> {
294 self.entries
295 .iter()
296 .find(|(k, _)| k.as_ref() == key)
297 .map(|(_, v)| v.as_ref())
298 }
299
300 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> + '_ {
302 self.entries.iter().map(|(k, v)| (k.as_ref(), v.as_ref()))
303 }
304
305 pub fn dropped_count(&self) -> Option<usize> {
314 self.dropped
315 }
316}
317
318#[cfg(feature = "serde")]
319impl Serialize for Details {
320 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
321 use serde::ser::SerializeMap;
322 let mut map = s.serialize_map(Some(self.entries.len()))?;
323 for (k, v) in &self.entries {
324 map.serialize_entry(k.as_ref(), v.as_ref())?;
325 }
326 map.end()
327 }
328}
329
330#[cfg(feature = "serde")]
331impl<'de> Deserialize<'de> for Details {
332 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
333 use serde::de::{MapAccess, Visitor};
334
335 struct DetailsVisitor;
336
337 impl<'de> Visitor<'de> for DetailsVisitor {
338 type Value = Details;
339
340 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341 f.write_str("a map of string key-value pairs")
342 }
343
344 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Details, A::Error> {
345 let mut ordinary: alloc::vec::Vec<(Cow<'static, str>, Cow<'static, str>)> =
351 alloc::vec::Vec::new();
352 let mut total_ordinary: usize = 0;
353 let mut reserved_count: usize = 0;
354 let mut reserved_is_trailing = false;
355 let mut reserved_follows_seven = false;
356 let mut last_reserved_value: Option<String> = None;
357 while let Some((k, v)) = map.next_entry::<String, String>()? {
358 if k == DETAILS_TRUNCATED_KEY {
359 reserved_count += 1;
360 reserved_is_trailing = true;
361 reserved_follows_seven = total_ordinary == 7;
362 last_reserved_value = Some(v);
363 } else {
364 reserved_is_trailing = false;
365 total_ordinary += 1;
366 if ordinary.len() < 8 {
367 ordinary.push((Cow::Owned(k), Cow::Owned(v)));
368 }
369 }
370 }
371 if reserved_count == 1 && reserved_is_trailing && reserved_follows_seven {
372 if let Some(dropped) =
373 last_reserved_value.as_deref().and_then(|s| s.parse().ok())
374 {
375 let mut entries = ordinary;
376 entries.push((
377 Cow::Borrowed(DETAILS_TRUNCATED_KEY),
378 Cow::Owned(alloc::format!("{dropped}")),
379 ));
380 return Ok(Details {
381 entries,
382 dropped: Some(dropped),
383 });
384 }
385 }
386 Ok(Details::build(ordinary, total_ordinary, reserved_count))
387 }
388 }
389
390 d.deserialize_map(DetailsVisitor)
391 }
392}
393
394#[derive(Clone, Debug)]
411#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
412pub struct KhiveError {
413 kind: ErrorKind,
414 message: String,
415 code: Option<ErrorCode>,
416 details: Option<Details>,
417}
418
419impl KhiveError {
420 pub fn not_found(resource: impl fmt::Display, id: impl fmt::Display) -> Self {
424 Self {
425 kind: ErrorKind::NotFound,
426 message: alloc::format!("{resource} not found: {id}"),
427 code: None,
428 details: None,
429 }
430 }
431
432 pub fn invalid_input(message: impl Into<String>) -> Self {
434 Self {
435 kind: ErrorKind::InvalidInput,
436 message: alloc::format!("invalid input: {}", message.into()),
437 code: None,
438 details: None,
439 }
440 }
441
442 pub fn unauthorized(message: impl Into<String>) -> Self {
444 Self {
445 kind: ErrorKind::Unauthorized,
446 message: alloc::format!("unauthorized: {}", message.into()),
447 code: None,
448 details: None,
449 }
450 }
451
452 pub fn conflict(message: impl Into<String>) -> Self {
454 Self {
455 kind: ErrorKind::Conflict,
456 message: alloc::format!("conflict: {}", message.into()),
457 code: None,
458 details: None,
459 }
460 }
461
462 pub fn unavailable(message: impl Into<String>) -> Self {
464 Self {
465 kind: ErrorKind::Unavailable,
466 message: alloc::format!("unavailable: {}", message.into()),
467 code: None,
468 details: None,
469 }
470 }
471
472 pub fn internal(message: impl Into<String>) -> Self {
474 Self {
475 kind: ErrorKind::Internal,
476 message: alloc::format!("internal: {}", message.into()),
477 code: None,
478 details: None,
479 }
480 }
481
482 pub fn with_code(mut self, code: ErrorCode) -> Self {
486 self.code = Some(code);
487 self
488 }
489
490 pub fn with_details(mut self, details: Details) -> Self {
492 self.details = Some(details);
493 self
494 }
495
496 pub fn kind(&self) -> ErrorKind {
500 self.kind
501 }
502
503 pub fn message(&self) -> &str {
505 &self.message
506 }
507
508 pub fn code(&self) -> Option<ErrorCode> {
510 self.code
511 }
512
513 pub fn details(&self) -> Option<&Details> {
515 self.details.as_ref()
516 }
517
518 pub fn retry_hint(&self) -> RetryHint {
520 match self.kind {
521 ErrorKind::Unavailable => RetryHint::Retryable,
522 _ => RetryHint::NoRetry,
523 }
524 }
525}
526
527impl fmt::Display for KhiveError {
528 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
529 write!(f, "{}", self.message)
530 }
531}
532
533#[cfg(feature = "std")]
534impl std::error::Error for KhiveError {}