1use super::schema_cache::{CachedSchema, ToolSchemaCache};
24use crate::error::McpError;
25
26pub struct ErrorEnhancer<'a> {
28 cache: &'a ToolSchemaCache,
29}
30
31impl<'a> ErrorEnhancer<'a> {
32 pub fn new(cache: &'a ToolSchemaCache) -> Self {
34 Self { cache }
35 }
36
37 pub fn enhance(&self, server: &str, tool: &str, error: McpError) -> McpError {
39 let McpError::McpToolError {
40 tool: tool_name,
41 reason,
42 error_code,
43 } = &error
44 else {
45 return error; };
47
48 let enhanced_reason = self.enhance_reason(server, tool, reason);
50
51 McpError::McpToolError {
52 tool: tool_name.clone(),
53 reason: enhanced_reason,
54 error_code: *error_code,
55 }
56 }
57
58 fn enhance_reason(&self, server: &str, tool: &str, reason: &str) -> String {
60 let Some(schema_ref) = self.cache.get(server, tool) else {
61 return reason.to_string();
62 };
63
64 let schema = schema_ref.value();
65 let reason_lower = reason.to_lowercase();
66
67 if reason_lower.contains("missing field") {
69 return self.enhance_missing_field(reason, schema);
70 }
71
72 if reason_lower.contains("unknown field") || reason_lower.contains("unexpected") {
74 return self.enhance_unknown_field(reason, schema);
75 }
76
77 if !schema.required.is_empty() {
79 format!(
80 "{}. Required: [{}]. Available: [{}]",
81 reason,
82 schema.required.join(", "),
83 schema.properties.join(", ")
84 )
85 } else {
86 reason.to_string()
87 }
88 }
89
90 fn enhance_missing_field(&self, reason: &str, schema: &CachedSchema) -> String {
92 format!(
93 "{}. Required: [{}]. Available: [{}]",
94 reason,
95 schema.required.join(", "),
96 schema.properties.join(", ")
97 )
98 }
99
100 fn enhance_unknown_field(&self, reason: &str, schema: &CachedSchema) -> String {
102 format!(
103 "{}. Valid fields: [{}]",
104 reason,
105 schema.properties.join(", ")
106 )
107 }
108}
109
110#[cfg(test)]
115mod tests {
116 use super::*;
117 use crate::types::ToolDefinition;
118 use serde_json::json;
119
120 #[test]
124 fn test_enhance_missing_field_error() {
125 let cache = ToolSchemaCache::new();
126 cache
127 .populate(
128 "novanet",
129 &[
130 ToolDefinition::new("novanet_context").with_input_schema(json!({
131 "type": "object",
132 "properties": {
133 "entity": { "type": "string" },
134 "locale": { "type": "string" }
135 },
136 "required": ["entity"]
137 })),
138 ],
139 )
140 .unwrap();
141
142 let enhancer = ErrorEnhancer::new(&cache);
143 let original = McpError::McpToolError {
144 tool: "novanet_context".to_string(),
145 reason: "missing field `entity`".to_string(),
146 error_code: None,
147 };
148
149 let enhanced = enhancer.enhance("novanet", "novanet_context", original);
150
151 let McpError::McpToolError { reason, .. } = enhanced else {
152 panic!("Expected McpToolError");
153 };
154
155 assert!(reason.contains("Required:"));
156 assert!(reason.contains("entity"));
157 assert!(reason.contains("Available:"));
158 }
159
160 #[test]
164 fn test_enhance_unknown_field_error() {
165 let cache = ToolSchemaCache::new();
166 cache
167 .populate(
168 "novanet",
169 &[ToolDefinition::new("tool").with_input_schema(json!({
170 "type": "object",
171 "properties": {
172 "entity": {},
173 "locale": {}
174 }
175 }))],
176 )
177 .unwrap();
178
179 let enhancer = ErrorEnhancer::new(&cache);
180 let original = McpError::McpToolError {
181 tool: "tool".to_string(),
182 reason: "unknown field `wrong_name`".to_string(),
183 error_code: None,
184 };
185
186 let enhanced = enhancer.enhance("novanet", "tool", original);
187
188 let McpError::McpToolError { reason, .. } = enhanced else {
189 panic!("Expected McpToolError");
190 };
191
192 assert!(reason.contains("Valid fields:"));
193 assert!(reason.contains("entity"));
194 assert!(reason.contains("locale"));
195 }
196
197 #[test]
201 fn test_enhance_passes_through_non_mcp_errors() {
202 let cache = ToolSchemaCache::new();
203 let enhancer = ErrorEnhancer::new(&cache);
204
205 let original = McpError::ParseError {
206 details: "test".to_string(),
207 };
208 let enhanced = enhancer.enhance("s", "t", original);
209
210 assert!(matches!(enhanced, McpError::ParseError { .. }));
211 }
212
213 #[test]
217 fn test_enhance_no_schema_returns_original() {
218 let cache = ToolSchemaCache::new();
219 let enhancer = ErrorEnhancer::new(&cache);
220
221 let original = McpError::McpToolError {
222 tool: "unknown".to_string(),
223 reason: "error".to_string(),
224 error_code: None,
225 };
226
227 let enhanced = enhancer.enhance("s", "unknown", original);
228
229 let McpError::McpToolError { reason, .. } = enhanced else {
230 panic!("Expected McpToolError");
231 };
232 assert_eq!(reason, "error");
233 }
234
235 #[test]
239 fn test_enhance_generic_error_adds_hint() {
240 let cache = ToolSchemaCache::new();
241 cache
242 .populate(
243 "s",
244 &[ToolDefinition::new("t").with_input_schema(json!({
245 "type": "object",
246 "properties": {
247 "a": {},
248 "b": {}
249 },
250 "required": ["a"]
251 }))],
252 )
253 .unwrap();
254
255 let enhancer = ErrorEnhancer::new(&cache);
256 let original = McpError::McpToolError {
257 tool: "t".to_string(),
258 reason: "some generic error".to_string(),
259 error_code: None,
260 };
261
262 let enhanced = enhancer.enhance("s", "t", original);
263
264 let McpError::McpToolError { reason, .. } = enhanced else {
265 panic!("Expected McpToolError");
266 };
267
268 assert!(reason.contains("Required:"));
269 assert!(reason.contains("a"));
270 assert!(reason.contains("Available:"));
271 }
272
273 #[test]
277 fn test_enhance_no_required_no_hint() {
278 let cache = ToolSchemaCache::new();
279 cache
280 .populate(
281 "s",
282 &[ToolDefinition::new("t").with_input_schema(json!({
283 "type": "object",
284 "properties": {
285 "optional_field": {}
286 }
287 }))],
289 )
290 .unwrap();
291
292 let enhancer = ErrorEnhancer::new(&cache);
293 let original = McpError::McpToolError {
294 tool: "t".to_string(),
295 reason: "some error".to_string(),
296 error_code: None,
297 };
298
299 let enhanced = enhancer.enhance("s", "t", original);
300
301 let McpError::McpToolError { reason, .. } = enhanced else {
302 panic!("Expected McpToolError");
303 };
304
305 assert_eq!(reason, "some error");
307 }
308
309 #[test]
313 fn test_enhance_case_insensitive() {
314 let cache = ToolSchemaCache::new();
315 cache
316 .populate(
317 "s",
318 &[ToolDefinition::new("t").with_input_schema(json!({
319 "type": "object",
320 "properties": { "field": {} },
321 "required": ["field"]
322 }))],
323 )
324 .unwrap();
325
326 let enhancer = ErrorEnhancer::new(&cache);
327
328 let original = McpError::McpToolError {
330 tool: "t".to_string(),
331 reason: "Missing Field `field`".to_string(),
332 error_code: None,
333 };
334
335 let enhanced = enhancer.enhance("s", "t", original);
336
337 let McpError::McpToolError { reason, .. } = enhanced else {
338 panic!("Expected McpToolError");
339 };
340
341 assert!(reason.contains("Required:"));
342 }
343}