use super::schema_cache::{CachedSchema, ToolSchemaCache};
use crate::error::NikaError;
pub struct ErrorEnhancer<'a> {
cache: &'a ToolSchemaCache,
}
impl<'a> ErrorEnhancer<'a> {
pub fn new(cache: &'a ToolSchemaCache) -> Self {
Self { cache }
}
pub fn enhance(&self, server: &str, tool: &str, error: NikaError) -> NikaError {
let NikaError::McpToolError {
tool: tool_name,
reason,
error_code,
} = &error
else {
return error; };
let enhanced_reason = self.enhance_reason(server, tool, reason);
NikaError::McpToolError {
tool: tool_name.clone(),
reason: enhanced_reason,
error_code: *error_code,
}
}
fn enhance_reason(&self, server: &str, tool: &str, reason: &str) -> String {
let Some(schema_ref) = self.cache.get(server, tool) else {
return reason.to_string();
};
let schema = schema_ref.value();
let reason_lower = reason.to_lowercase();
if reason_lower.contains("missing field") {
return self.enhance_missing_field(reason, schema);
}
if reason_lower.contains("unknown field") || reason_lower.contains("unexpected") {
return self.enhance_unknown_field(reason, schema);
}
if !schema.required.is_empty() {
format!(
"{}. Required: [{}]. Available: [{}]",
reason,
schema.required.join(", "),
schema.properties.join(", ")
)
} else {
reason.to_string()
}
}
fn enhance_missing_field(&self, reason: &str, schema: &CachedSchema) -> String {
format!(
"{}. Required: [{}]. Available: [{}]",
reason,
schema.required.join(", "),
schema.properties.join(", ")
)
}
fn enhance_unknown_field(&self, reason: &str, schema: &CachedSchema) -> String {
format!(
"{}. Valid fields: [{}]",
reason,
schema.properties.join(", ")
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mcp::types::ToolDefinition;
use serde_json::json;
#[test]
fn test_enhance_missing_field_error() {
let cache = ToolSchemaCache::new();
cache
.populate(
"novanet",
&[
ToolDefinition::new("novanet_context").with_input_schema(json!({
"type": "object",
"properties": {
"entity": { "type": "string" },
"locale": { "type": "string" }
},
"required": ["entity"]
})),
],
)
.unwrap();
let enhancer = ErrorEnhancer::new(&cache);
let original = NikaError::McpToolError {
tool: "novanet_context".to_string(),
reason: "missing field `entity`".to_string(),
error_code: None,
};
let enhanced = enhancer.enhance("novanet", "novanet_context", original);
let NikaError::McpToolError { reason, .. } = enhanced else {
panic!("Expected McpToolError");
};
assert!(reason.contains("Required:"));
assert!(reason.contains("entity"));
assert!(reason.contains("Available:"));
}
#[test]
fn test_enhance_unknown_field_error() {
let cache = ToolSchemaCache::new();
cache
.populate(
"novanet",
&[ToolDefinition::new("tool").with_input_schema(json!({
"type": "object",
"properties": {
"entity": {},
"locale": {}
}
}))],
)
.unwrap();
let enhancer = ErrorEnhancer::new(&cache);
let original = NikaError::McpToolError {
tool: "tool".to_string(),
reason: "unknown field `wrong_name`".to_string(),
error_code: None,
};
let enhanced = enhancer.enhance("novanet", "tool", original);
let NikaError::McpToolError { reason, .. } = enhanced else {
panic!("Expected McpToolError");
};
assert!(reason.contains("Valid fields:"));
assert!(reason.contains("entity"));
assert!(reason.contains("locale"));
}
#[test]
fn test_enhance_passes_through_non_mcp_errors() {
let cache = ToolSchemaCache::new();
let enhancer = ErrorEnhancer::new(&cache);
let original = NikaError::ParseError {
details: "test".to_string(),
};
let enhanced = enhancer.enhance("s", "t", original);
assert!(matches!(enhanced, NikaError::ParseError { .. }));
}
#[test]
fn test_enhance_no_schema_returns_original() {
let cache = ToolSchemaCache::new();
let enhancer = ErrorEnhancer::new(&cache);
let original = NikaError::McpToolError {
tool: "unknown".to_string(),
reason: "error".to_string(),
error_code: None,
};
let enhanced = enhancer.enhance("s", "unknown", original);
let NikaError::McpToolError { reason, .. } = enhanced else {
panic!("Expected McpToolError");
};
assert_eq!(reason, "error");
}
#[test]
fn test_enhance_generic_error_adds_hint() {
let cache = ToolSchemaCache::new();
cache
.populate(
"s",
&[ToolDefinition::new("t").with_input_schema(json!({
"type": "object",
"properties": {
"a": {},
"b": {}
},
"required": ["a"]
}))],
)
.unwrap();
let enhancer = ErrorEnhancer::new(&cache);
let original = NikaError::McpToolError {
tool: "t".to_string(),
reason: "some generic error".to_string(),
error_code: None,
};
let enhanced = enhancer.enhance("s", "t", original);
let NikaError::McpToolError { reason, .. } = enhanced else {
panic!("Expected McpToolError");
};
assert!(reason.contains("Required:"));
assert!(reason.contains("a"));
assert!(reason.contains("Available:"));
}
#[test]
fn test_enhance_no_required_no_hint() {
let cache = ToolSchemaCache::new();
cache
.populate(
"s",
&[ToolDefinition::new("t").with_input_schema(json!({
"type": "object",
"properties": {
"optional_field": {}
}
}))],
)
.unwrap();
let enhancer = ErrorEnhancer::new(&cache);
let original = NikaError::McpToolError {
tool: "t".to_string(),
reason: "some error".to_string(),
error_code: None,
};
let enhanced = enhancer.enhance("s", "t", original);
let NikaError::McpToolError { reason, .. } = enhanced else {
panic!("Expected McpToolError");
};
assert_eq!(reason, "some error");
}
#[test]
fn test_enhance_case_insensitive() {
let cache = ToolSchemaCache::new();
cache
.populate(
"s",
&[ToolDefinition::new("t").with_input_schema(json!({
"type": "object",
"properties": { "field": {} },
"required": ["field"]
}))],
)
.unwrap();
let enhancer = ErrorEnhancer::new(&cache);
let original = NikaError::McpToolError {
tool: "t".to_string(),
reason: "Missing Field `field`".to_string(),
error_code: None,
};
let enhanced = enhancer.enhance("s", "t", original);
let NikaError::McpToolError { reason, .. } = enhanced else {
panic!("Expected McpToolError");
};
assert!(reason.contains("Required:"));
}
}