huskies: merge 1026
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
//! 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<ValidationError>` 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:?}"))
|
||||
}
|
||||
Reference in New Issue
Block a user