use super::*;
#[derive(Clone, Copy, Debug, Eq, IntoStaticStr, PartialEq)]
#[strum(serialize_all = "snake_case", const_into_str)]
pub enum McpValidationScope {
Form,
Field,
Element,
Filter,
}
impl McpValidationScope {
pub const fn as_str(self) -> &'static str {
self.into_str()
}
}
#[derive(Clone, Copy, Debug, Eq, IntoStaticStr, PartialEq)]
#[strum(serialize_all = "snake_case", const_into_str)]
pub enum McpValidationTarget {
Default,
Full,
Unwrapped,
}
impl McpValidationTarget {
pub const fn as_str(self) -> &'static str {
self.into_str()
}
}
#[derive(Clone, Copy, Debug, Eq, IntoStaticStr, PartialEq)]
#[strum(serialize_all = "snake_case", const_into_str)]
pub enum McpValidationTypeArgMode {
None,
Infer,
Explicit,
}
impl McpValidationTypeArgMode {
pub const fn as_str(self) -> &'static str {
self.into_str()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct McpValidationParam {
name: &'static str,
literal: Option<&'static str>,
expr: Option<&'static str>,
}
impl McpValidationParam {
pub const fn literal(name: &'static str, literal: &'static str) -> Self {
Self {
name,
literal: Some(literal),
expr: None,
}
}
pub const fn expr(name: &'static str, expr: &'static str) -> Self {
Self {
name,
literal: None,
expr: Some(expr),
}
}
pub const fn name(self) -> &'static str {
self.name
}
pub const fn literal_value(self) -> Option<&'static str> {
self.literal
}
pub const fn expr_value(self) -> Option<&'static str> {
self.expr
}
pub fn to_value(self) -> Value {
let mut object = Map::new();
object.insert("name".to_string(), Value::String(self.name.to_string()));
if let Some(literal) = self.literal {
object.insert("value".to_string(), Value::String(literal.to_string()));
}
if let Some(expr) = self.expr {
object.insert("expr".to_string(), Value::String(expr.to_string()));
}
Value::Object(object)
}
}
pub const MCP_VALIDATION_PARAMS_NONE: &[McpValidationParam] = &[];
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct McpValidationRule {
scope: McpValidationScope,
validator: &'static str,
path: &'static str,
label: Option<&'static str>,
target: Option<McpValidationTarget>,
type_arg_mode: McpValidationTypeArgMode,
params: &'static [McpValidationParam],
}
impl McpValidationRule {
pub const fn new(
scope: McpValidationScope,
validator: &'static str,
path: &'static str,
label: Option<&'static str>,
type_arg_mode: McpValidationTypeArgMode,
params: &'static [McpValidationParam],
) -> Self {
Self {
scope,
validator,
path,
label,
target: None,
type_arg_mode,
params,
}
}
pub const fn with_target(mut self, target: McpValidationTarget) -> Self {
self.target = Some(target);
self
}
pub const fn scope(self) -> McpValidationScope {
self.scope
}
pub const fn validator(self) -> &'static str {
self.validator
}
pub const fn path(self) -> &'static str {
self.path
}
pub const fn label(self) -> Option<&'static str> {
self.label
}
pub const fn target(self) -> Option<McpValidationTarget> {
self.target
}
pub const fn type_arg_mode(self) -> McpValidationTypeArgMode {
self.type_arg_mode
}
pub const fn params(self) -> &'static [McpValidationParam] {
self.params
}
pub fn to_value(self) -> Value {
let mut object = Map::new();
object.insert(
"scope".to_string(),
Value::String(self.scope.as_str().to_string()),
);
object.insert(
"validator".to_string(),
Value::String(self.validator.to_string()),
);
object.insert("path".to_string(), Value::String(self.path.to_string()));
if let Some(label) = self.label {
object.insert("label".to_string(), Value::String(label.to_string()));
}
if let Some(target) = self.target {
object.insert(
"target".to_string(),
Value::String(target.as_str().to_string()),
);
}
object.insert(
"type_arg_mode".to_string(),
Value::String(self.type_arg_mode.as_str().to_string()),
);
if !self.params.is_empty() {
object.insert(
"params".to_string(),
Value::Array(self.params.iter().map(|param| param.to_value()).collect()),
);
}
Value::Object(object)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct McpValidationIssue {
field: Option<String>,
filter: Option<String>,
scope: McpValidationScope,
validator: Option<String>,
path: Option<String>,
label: Option<String>,
target: Option<McpValidationTarget>,
element_index: Option<usize>,
message: String,
params: Vec<McpValidationParam>,
}
impl McpValidationIssue {
pub fn form(message: impl Into<String>) -> Self {
Self::custom(McpValidationScope::Form, message)
}
pub fn custom(scope: McpValidationScope, message: impl Into<String>) -> Self {
Self {
field: None,
filter: None,
scope,
validator: None,
path: None,
label: None,
target: None,
element_index: None,
message: message.into(),
params: Vec::new(),
}
}
pub fn required(field: impl AsRef<str>) -> Self {
let field = field.as_ref();
Self::custom(
McpValidationScope::Field,
format!("missing required field `{field}`"),
)
.with_field(field)
.with_validator("required")
}
pub fn for_rule(
field: impl AsRef<str>,
rule: McpValidationRule,
message: impl Into<String>,
) -> Self {
Self::custom(rule.scope(), message)
.with_field(field)
.with_rule(rule)
}
pub fn for_filter_rule(
filter: impl Into<String>,
rule: McpValidationRule,
message: impl Into<String>,
) -> Self {
Self::custom(rule.scope(), message)
.with_filter(filter)
.with_rule(rule)
}
fn with_rule(mut self, rule: McpValidationRule) -> Self {
self.validator = Some(rule.validator().to_string());
self.path = Some(rule.path().to_string());
self.label = rule.label().map(str::to_string);
self.target = rule.target();
self.params = rule.params().to_vec();
self
}
pub fn with_element_index(mut self, element_index: usize) -> Self {
self.element_index = Some(element_index);
self
}
pub fn with_field(mut self, field: impl AsRef<str>) -> Self {
self.field = Some(field.as_ref().to_string());
self
}
pub fn with_filter(mut self, filter: impl Into<String>) -> Self {
self.filter = Some(filter.into());
self
}
pub fn with_validator(mut self, validator: impl Into<String>) -> Self {
self.validator = Some(validator.into());
self
}
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn field(&self) -> Option<&str> {
self.field.as_deref()
}
pub fn filter(&self) -> Option<&str> {
self.filter.as_deref()
}
pub fn message(&self) -> &str {
&self.message
}
pub fn to_value(&self) -> Value {
let mut object = Map::new();
object.insert(
"scope".to_string(),
Value::String(self.scope.as_str().to_string()),
);
object.insert("message".to_string(), Value::String(self.message.clone()));
if let Some(field) = &self.field {
object.insert("field".to_string(), Value::String(field.clone()));
}
if let Some(filter) = &self.filter {
object.insert("filter".to_string(), Value::String(filter.clone()));
}
if let Some(validator) = &self.validator {
object.insert("validator".to_string(), Value::String(validator.clone()));
}
if let Some(path) = &self.path {
object.insert("path".to_string(), Value::String(path.clone()));
}
if let Some(label) = &self.label {
object.insert("label".to_string(), Value::String(label.clone()));
}
if let Some(target) = self.target {
object.insert(
"target".to_string(),
Value::String(target.as_str().to_string()),
);
}
if let Some(element_index) = self.element_index {
object.insert(
"element_index".to_string(),
Value::Number((element_index as u64).into()),
);
}
if !self.params.is_empty() {
object.insert(
"params".to_string(),
Value::Array(self.params.iter().map(|param| param.to_value()).collect()),
);
}
Value::Object(object)
}
}
pub fn validation_issues_error(issues: Vec<McpValidationIssue>) -> McpToolError {
let message = issues
.iter()
.map(McpValidationIssue::message)
.collect::<Vec<_>>()
.join("; ");
McpToolError::validation_structured_details(
message,
issues.into_iter().map(|issue| issue.to_value()),
)
}