Skip to main content

kcode_intelligence_router/
error.rs

1use std::fmt;
2
3/// Broad failure category for callers that need to choose retry or user feedback.
4#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5pub enum ErrorKind {
6    InvalidInput,
7    Unavailable,
8    Conflict,
9    Cancelled,
10    Provider,
11    Internal,
12}
13
14/// A bounded, transport-independent intelligence failure.
15#[derive(Debug)]
16pub struct Error {
17    kind: ErrorKind,
18    code: &'static str,
19    message: String,
20}
21
22impl Error {
23    pub(crate) fn new(kind: ErrorKind, code: &'static str, message: impl Into<String>) -> Self {
24        Self {
25            kind,
26            code,
27            message: message.into(),
28        }
29    }
30
31    pub fn invalid(message: impl Into<String>) -> Self {
32        Self::new(ErrorKind::InvalidInput, "invalid_request", message)
33    }
34
35    pub(crate) fn unavailable(code: &'static str, message: impl Into<String>) -> Self {
36        Self::new(ErrorKind::Unavailable, code, message)
37    }
38
39    pub(crate) fn conflict(code: &'static str, message: impl Into<String>) -> Self {
40        Self::new(ErrorKind::Conflict, code, message)
41    }
42
43    pub(crate) fn cancelled() -> Self {
44        Self::new(
45            ErrorKind::Cancelled,
46            "operation_cancelled",
47            "The operation was stopped by the user.",
48        )
49    }
50
51    pub(crate) fn provider(code: &'static str, message: impl Into<String>) -> Self {
52        Self::new(ErrorKind::Provider, code, message)
53    }
54
55    pub(crate) fn internal(code: &'static str, message: impl Into<String>) -> Self {
56        Self::new(ErrorKind::Internal, code, message)
57    }
58
59    pub fn kind(&self) -> ErrorKind {
60        self.kind
61    }
62
63    pub fn code(&self) -> &'static str {
64        self.code
65    }
66
67    pub fn message(&self) -> &str {
68        &self.message
69    }
70
71    /// Whether retrying the provider operation can reasonably succeed.
72    pub fn retryable(&self) -> bool {
73        matches!(self.kind, ErrorKind::Provider | ErrorKind::Conflict)
74            || (self.kind == ErrorKind::Unavailable && self.code != "provider_not_configured")
75    }
76}
77
78impl fmt::Display for Error {
79    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
80        formatter.write_str(&self.message)
81    }
82}
83
84impl std::error::Error for Error {}
85
86pub type Result<T> = std::result::Result<T, Error>;
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn retryability_distinguishes_configuration_from_transient_provider_failure() {
94        assert!(!Error::unavailable("provider_not_configured", "missing key").retryable());
95        assert!(Error::unavailable("provider_rate_limited", "try later").retryable());
96        assert!(Error::provider("provider_timeout", "try later").retryable());
97        assert!(!Error::invalid("bad request").retryable());
98    }
99}