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 pub fn new_owned<I>(pairs: I) -> Self
247 where
248 I: IntoIterator<Item = (&'static str, String)>,
249 {
250 Self::from_owned(
251 pairs
252 .into_iter()
253 .map(|(k, v)| (Cow::Borrowed(k), Cow::Owned(v))),
254 )
255 }
256
257 fn from_owned<I>(pairs: I) -> Self
261 where
262 I: IntoIterator<Item = (Cow<'static, str>, Cow<'static, str>)>,
263 {
264 let mut ordinary: alloc::vec::Vec<(Cow<'static, str>, Cow<'static, str>)> =
265 alloc::vec::Vec::new();
266 let mut total_ordinary: usize = 0;
267 let mut collisions: usize = 0;
268 for (k, v) in pairs {
269 if k.as_ref() == DETAILS_TRUNCATED_KEY {
270 collisions += 1;
271 } else {
272 total_ordinary += 1;
273 if ordinary.len() < 8 {
274 ordinary.push((k, v));
275 }
276 }
277 }
278 Self::build(ordinary, total_ordinary, collisions)
279 }
280
281 fn build(
284 ordinary: alloc::vec::Vec<(Cow<'static, str>, Cow<'static, str>)>,
285 total_ordinary: usize,
286 collisions: usize,
287 ) -> Self {
288 if total_ordinary <= 8 && collisions == 0 {
289 return Self {
290 entries: ordinary,
291 dropped: None,
292 };
293 }
294 let keep = total_ordinary.min(7);
295 let dropped = (total_ordinary - keep) + collisions;
296 let mut entries: alloc::vec::Vec<_> = ordinary.into_iter().take(keep).collect();
297 entries.push((
298 Cow::Borrowed(DETAILS_TRUNCATED_KEY),
299 Cow::Owned(alloc::format!("{dropped}")),
300 ));
301 Self {
302 entries,
303 dropped: Some(dropped),
304 }
305 }
306
307 pub fn get(&self, key: &str) -> Option<&str> {
309 self.entries
310 .iter()
311 .find(|(k, _)| k.as_ref() == key)
312 .map(|(_, v)| v.as_ref())
313 }
314
315 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> + '_ {
317 self.entries.iter().map(|(k, v)| (k.as_ref(), v.as_ref()))
318 }
319
320 pub fn dropped_count(&self) -> Option<usize> {
329 self.dropped
330 }
331}
332
333#[cfg(feature = "serde")]
334impl Serialize for Details {
335 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
336 use serde::ser::SerializeMap;
337 let mut map = s.serialize_map(Some(self.entries.len()))?;
338 for (k, v) in &self.entries {
339 map.serialize_entry(k.as_ref(), v.as_ref())?;
340 }
341 map.end()
342 }
343}
344
345#[cfg(feature = "serde")]
346impl<'de> Deserialize<'de> for Details {
347 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
348 use serde::de::{MapAccess, Visitor};
349
350 struct DetailsVisitor;
351
352 impl<'de> Visitor<'de> for DetailsVisitor {
353 type Value = Details;
354
355 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
356 f.write_str("a map of string key-value pairs")
357 }
358
359 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Details, A::Error> {
360 let mut ordinary: alloc::vec::Vec<(Cow<'static, str>, Cow<'static, str>)> =
366 alloc::vec::Vec::new();
367 let mut total_ordinary: usize = 0;
368 let mut reserved_count: usize = 0;
369 let mut reserved_is_trailing = false;
370 let mut reserved_follows_seven = false;
371 let mut last_reserved_value: Option<String> = None;
372 while let Some((k, v)) = map.next_entry::<String, String>()? {
373 if k == DETAILS_TRUNCATED_KEY {
374 reserved_count += 1;
375 reserved_is_trailing = true;
376 reserved_follows_seven = total_ordinary == 7;
377 last_reserved_value = Some(v);
378 } else {
379 reserved_is_trailing = false;
380 total_ordinary += 1;
381 if ordinary.len() < 8 {
382 ordinary.push((Cow::Owned(k), Cow::Owned(v)));
383 }
384 }
385 }
386 if reserved_count == 1 && reserved_is_trailing && reserved_follows_seven {
387 if let Some(dropped) =
388 last_reserved_value.as_deref().and_then(|s| s.parse().ok())
389 {
390 let mut entries = ordinary;
391 entries.push((
392 Cow::Borrowed(DETAILS_TRUNCATED_KEY),
393 Cow::Owned(alloc::format!("{dropped}")),
394 ));
395 return Ok(Details {
396 entries,
397 dropped: Some(dropped),
398 });
399 }
400 }
401 Ok(Details::build(ordinary, total_ordinary, reserved_count))
402 }
403 }
404
405 d.deserialize_map(DetailsVisitor)
406 }
407}
408
409#[derive(Clone, Debug)]
426#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
427pub struct KhiveError {
428 kind: ErrorKind,
429 message: String,
430 code: Option<ErrorCode>,
431 details: Option<Details>,
432}
433
434impl KhiveError {
435 pub fn not_found(resource: impl fmt::Display, id: impl fmt::Display) -> Self {
439 Self {
440 kind: ErrorKind::NotFound,
441 message: alloc::format!("{resource} not found: {id}"),
442 code: None,
443 details: None,
444 }
445 }
446
447 pub fn invalid_input(message: impl Into<String>) -> Self {
449 Self {
450 kind: ErrorKind::InvalidInput,
451 message: alloc::format!("invalid input: {}", message.into()),
452 code: None,
453 details: None,
454 }
455 }
456
457 pub fn unauthorized(message: impl Into<String>) -> Self {
459 Self {
460 kind: ErrorKind::Unauthorized,
461 message: alloc::format!("unauthorized: {}", message.into()),
462 code: None,
463 details: None,
464 }
465 }
466
467 pub fn conflict(message: impl Into<String>) -> Self {
469 Self {
470 kind: ErrorKind::Conflict,
471 message: alloc::format!("conflict: {}", message.into()),
472 code: None,
473 details: None,
474 }
475 }
476
477 pub fn unavailable(message: impl Into<String>) -> Self {
479 Self {
480 kind: ErrorKind::Unavailable,
481 message: alloc::format!("unavailable: {}", message.into()),
482 code: None,
483 details: None,
484 }
485 }
486
487 pub fn internal(message: impl Into<String>) -> Self {
489 Self {
490 kind: ErrorKind::Internal,
491 message: alloc::format!("internal: {}", message.into()),
492 code: None,
493 details: None,
494 }
495 }
496
497 pub fn with_code(mut self, code: ErrorCode) -> Self {
501 self.code = Some(code);
502 self
503 }
504
505 pub fn with_details(mut self, details: Details) -> Self {
507 self.details = Some(details);
508 self
509 }
510
511 pub fn kind(&self) -> ErrorKind {
515 self.kind
516 }
517
518 pub fn message(&self) -> &str {
520 &self.message
521 }
522
523 pub fn code(&self) -> Option<ErrorCode> {
525 self.code
526 }
527
528 pub fn details(&self) -> Option<&Details> {
530 self.details.as_ref()
531 }
532
533 pub fn retry_hint(&self) -> RetryHint {
535 match self.kind {
536 ErrorKind::Unavailable => RetryHint::Retryable,
537 _ => RetryHint::NoRetry,
538 }
539 }
540}
541
542impl fmt::Display for KhiveError {
543 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
544 write!(f, "{}", self.message)
545 }
546}
547
548#[cfg(feature = "std")]
549impl std::error::Error for KhiveError {}