//! Typed validation error enum returned from all MCP write-tool input validation. use serde::{Deserialize, Serialize}; use std::fmt; /// Structured error from input validation. /// /// Each variant carries exactly the data a caller needs to act on the error. /// Serialises to serde's default externally-tagged form, e.g. /// `{"FieldTooLong":{"field":"description","max":200,"actual":287}}`. /// Callers can pattern-match on the JSON tag without parsing prose. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ValidationError { /// A required field was absent from the input. FieldMissing { field: String }, /// A field value is empty (or whitespace-only) after trimming. EmptyAfterTrim { field: String }, /// A field value exceeds the maximum allowed length. FieldTooLong { field: String, max: usize, actual: usize, }, /// A field value contains a character outside the allowed set. InvalidCharacter { field: String, ch: char, position: usize, }, /// A field value contains a tool-call grammar fragment that must be rejected. AntiGrammarToken { field: String, token: String }, /// A numeric field value is outside its allowed range. OutOfRange { field: String, min: i64, max: i64, actual: i64, }, /// A list field has fewer items than the minimum. TooFewItems { field: String, min: usize, actual: usize, }, /// A list field has more items than the maximum. TooManyItems { field: String, max: usize, actual: usize, }, /// A field value is not valid UTF-8. InvalidUtf8 { field: String }, /// A `depends_on` entry references the same item being created or updated. SelfReference { field: String }, } impl fmt::Display for ValidationError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::FieldMissing { field } => { write!(f, "field '{field}' is required but was not provided") } Self::EmptyAfterTrim { field } => { write!(f, "field '{field}' must not be empty or whitespace-only") } Self::FieldTooLong { field, max, actual } => { write!(f, "field '{field}' is too long ({actual} chars, max {max})") } Self::InvalidCharacter { field, ch, position, } => { write!( f, "field '{field}' contains invalid character {ch:?} at position {position}" ) } Self::AntiGrammarToken { field, token } => { write!( f, "field '{field}' contains a tool-call grammar fragment: {token:?}" ) } Self::OutOfRange { field, min, max, actual, } => { write!( f, "field '{field}' value {actual} is out of allowed range [{min}, {max}]" ) } Self::TooFewItems { field, min, actual } => { write!( f, "field '{field}' has too few items ({actual}; minimum {min})" ) } Self::TooManyItems { field, max, actual } => { write!( f, "field '{field}' has too many items ({actual}; maximum {max})" ) } Self::InvalidUtf8 { field } => { write!(f, "field '{field}' contains invalid UTF-8") } Self::SelfReference { field } => { write!( f, "field '{field}' contains a self-reference (depends on itself)" ) } } } } /// Serialise a slice of validation errors as a pretty-printed JSON string. /// /// Used to turn `Vec` into the `Err(String)` value returned by /// MCP tool handlers. pub fn format_errors_as_json(errors: &[ValidationError]) -> String { serde_json::to_string_pretty(errors).unwrap_or_else(|_| format!("{errors:?}")) }