1use thiserror::Error;
2
3pub mod codes {
4 pub const CONFIG_INVALID: &str = "E1001";
5 pub const CONFIG_MISSING: &str = "E1002";
6 pub const VALIDATION_FAILED: &str = "E2001";
7 pub const INVALID_INPUT: &str = "E2002";
8 pub const NOT_FOUND: &str = "E3001";
9 pub const ALREADY_EXISTS: &str = "E3002";
10 pub const CANCELLED: &str = "E4001";
11 pub const TIMEOUT: &str = "E4002";
12 pub const NOT_PERMITTED: &str = "E4003";
13 pub const RATE_LIMITED: &str = "E4004";
14 pub const LLM_REQUEST_FAILED: &str = "E5001";
15 pub const LLM_RATE_LIMITED: &str = "E5002";
16 pub const LLM_CONTEXT_LENGTH: &str = "E5003";
17 pub const LLM_INVALID_RESPONSE: &str = "E5004";
18 pub const TOOL_NOT_FOUND: &str = "E6001";
19 pub const TOOL_EXECUTION_FAILED: &str = "E6002";
20 pub const TOOL_INVALID_INPUT: &str = "E6003";
21 pub const STORAGE_CONNECTION: &str = "E7001";
22 pub const STORAGE_QUERY: &str = "E7002";
23 pub const STORAGE_WRITE: &str = "E7003";
24 pub const INTERNAL: &str = "E9001";
25 pub const NOT_IMPLEMENTED: &str = "E9002";
26}
27
28#[derive(Error, Debug)]
29pub enum Error {
30 #[error("Configuration error: {0}")]
31 Config(String),
32
33 #[error("Missing required config: {0}")]
34 MissingConfig(String),
35
36 #[error("JSON error: {0}")]
37 Json(#[from] serde_json::Error),
38
39 #[error("IO error: {0}")]
40 Io(#[from] std::io::Error),
41
42 #[error("Validation error: {0}")]
43 Validation(String),
44
45 #[error("Invalid input: {field} - {message}")]
46 InvalidInput { field: String, message: String },
47
48 #[error("Not found: {resource_type} with id {id}")]
49 NotFound { resource_type: String, id: String },
50
51 #[error("Already exists: {resource_type} with id {id}")]
52 AlreadyExists { resource_type: String, id: String },
53
54 #[error("Operation cancelled")]
55 Cancelled,
56
57 #[error("Operation timeout after {0}ms")]
58 Timeout(u64),
59
60 #[error("Rate limited: {0}")]
61 RateLimited(String),
62
63 #[error("Operation not permitted: {0}")]
64 NotPermitted(String),
65
66 #[error("LLM error: {provider} - {message}")]
67 Llm { provider: String, message: String },
68
69 #[error("Tool error: {tool} - {message}")]
70 Tool { tool: String, message: String },
71
72 #[error("Storage error: {backend} - {message}")]
73 Storage { backend: String, message: String },
74
75 #[error("Not implemented: {0}")]
76 NotImplemented(String),
77
78 #[error("Internal error: {0}")]
79 Internal(String),
80
81 #[error(transparent)]
82 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
83}
84
85impl Error {
86 pub fn code(&self) -> &'static str {
87 match self {
88 Error::Config(_) => codes::CONFIG_INVALID,
89 Error::MissingConfig(_) => codes::CONFIG_MISSING,
90 Error::Json(_) => codes::VALIDATION_FAILED,
91 Error::Io(_) => codes::INTERNAL,
92 Error::Validation(_) => codes::VALIDATION_FAILED,
93 Error::InvalidInput { .. } => codes::INVALID_INPUT,
94 Error::NotFound { .. } => codes::NOT_FOUND,
95 Error::AlreadyExists { .. } => codes::ALREADY_EXISTS,
96 Error::Cancelled => codes::CANCELLED,
97 Error::Timeout(_) => codes::TIMEOUT,
98 Error::RateLimited(_) => codes::RATE_LIMITED,
99 Error::NotPermitted(_) => codes::NOT_PERMITTED,
100 Error::Llm { .. } => codes::LLM_REQUEST_FAILED,
101 Error::Tool { .. } => codes::TOOL_EXECUTION_FAILED,
102 Error::Storage { .. } => codes::STORAGE_CONNECTION,
103 Error::NotImplemented(_) => codes::NOT_IMPLEMENTED,
104 Error::Internal(_) => codes::INTERNAL,
105 Error::Other(_) => codes::INTERNAL,
106 }
107 }
108
109 pub fn not_found(resource_type: impl Into<String>, id: impl Into<String>) -> Self {
110 Self::NotFound {
111 resource_type: resource_type.into(),
112 id: id.into(),
113 }
114 }
115
116 pub fn already_exists(resource_type: impl Into<String>, id: impl Into<String>) -> Self {
117 Self::AlreadyExists {
118 resource_type: resource_type.into(),
119 id: id.into(),
120 }
121 }
122
123 pub fn invalid_input(field: impl Into<String>, message: impl Into<String>) -> Self {
124 Self::InvalidInput {
125 field: field.into(),
126 message: message.into(),
127 }
128 }
129
130 pub fn llm(provider: impl Into<String>, message: impl Into<String>) -> Self {
131 Self::Llm {
132 provider: provider.into(),
133 message: message.into(),
134 }
135 }
136
137 pub fn tool(tool: impl Into<String>, message: impl Into<String>) -> Self {
138 Self::Tool {
139 tool: tool.into(),
140 message: message.into(),
141 }
142 }
143
144 pub fn storage(backend: impl Into<String>, message: impl Into<String>) -> Self {
145 Self::Storage {
146 backend: backend.into(),
147 message: message.into(),
148 }
149 }
150}
151
152pub type Result<T> = std::result::Result<T, Error>;
153
154pub trait ResultExt<T> {
155 fn with_context<F: FnOnce() -> String>(self, f: F) -> Result<T>;
156}
157
158impl<T, E: Into<Error>> ResultExt<T> for std::result::Result<T, E> {
159 fn with_context<F: FnOnce() -> String>(self, f: F) -> Result<T> {
160 self.map_err(|e| {
161 let inner = e.into();
162 Error::Internal(format!("{}: {}", f(), inner))
163 })
164 }
165}