mixvideo-v2/cargos/comfyui-sdk/utils/validation.rs

288 lines
8.3 KiB
Rust

//! Parameter validation utilities
use std::collections::HashMap;
use crate::types::{ParameterSchema, ParameterValues, ValidationResult, ValidationError, ParameterType};
use regex::Regex;
/// Validates parameters against their schemas
pub fn validate_parameters(
parameters: &ParameterValues,
schemas: &HashMap<String, ParameterSchema>,
) -> ValidationResult {
let mut errors = Vec::new();
// Check required parameters
for (name, schema) in schemas {
if schema.required.unwrap_or(false)
&& !parameters.contains_key(name) {
errors.push(ValidationError::new(
name,
"Required parameter is missing",
));
continue;
}
// Validate parameter if present
if let Some(value) = parameters.get(name) {
if let Err(error) = validate_parameter_value(name, value, schema) {
errors.push(error);
}
}
}
// Check for unknown parameters
for name in parameters.keys() {
if !schemas.contains_key(name) {
errors.push(ValidationError::new(
name,
"Unknown parameter",
));
}
}
if errors.is_empty() {
ValidationResult::success()
} else {
ValidationResult::failure(errors)
}
}
/// Validates a single parameter value against its schema
pub fn validate_parameter_value(
name: &str,
value: &serde_json::Value,
schema: &ParameterSchema,
) -> Result<(), ValidationError> {
// Check type
match (&schema.param_type, value) {
(ParameterType::String, serde_json::Value::String(s)) => {
validate_string_constraints(name, s, schema)?;
}
(ParameterType::Number, serde_json::Value::Number(n)) => {
validate_number_constraints(name, n, schema)?;
}
(ParameterType::Boolean, serde_json::Value::Bool(_)) => {
// Boolean values are always valid
}
(ParameterType::Array, serde_json::Value::Array(arr)) => {
validate_array_constraints(name, arr, schema)?;
}
(ParameterType::Object, serde_json::Value::Object(_)) => {
// Object validation could be more complex, but for now just check type
}
_ => {
return Err(ValidationError::with_value(
name,
format!("Expected type {:?}, got {:?}", schema.param_type, get_value_type(value)),
value.clone(),
));
}
}
// Check enum constraints
if let Some(enum_values) = &schema.r#enum {
if !enum_values.contains(value) {
return Err(ValidationError::with_value(
name,
format!("Value must be one of: {enum_values:?}"),
value.clone(),
));
}
}
Ok(())
}
/// Validates string constraints
fn validate_string_constraints(
name: &str,
value: &str,
schema: &ParameterSchema,
) -> Result<(), ValidationError> {
// Check pattern
if let Some(pattern) = &schema.pattern {
let regex = Regex::new(pattern).map_err(|_| {
ValidationError::new(name, "Invalid regex pattern in schema")
})?;
if !regex.is_match(value) {
return Err(ValidationError::with_value(
name,
format!("String does not match pattern: {pattern}"),
serde_json::Value::String(value.to_string()),
));
}
}
// Check length constraints (using min/max as length bounds for strings)
if let Some(min) = schema.min {
if (value.len() as f64) < min {
return Err(ValidationError::with_value(
name,
format!("String length must be at least {min}"),
serde_json::Value::String(value.to_string()),
));
}
}
if let Some(max) = schema.max {
if (value.len() as f64) > max {
return Err(ValidationError::with_value(
name,
format!("String length must be at most {max}"),
serde_json::Value::String(value.to_string()),
));
}
}
Ok(())
}
/// Validates number constraints
fn validate_number_constraints(
name: &str,
value: &serde_json::Number,
schema: &ParameterSchema,
) -> Result<(), ValidationError> {
let num_value = value.as_f64().unwrap_or(0.0);
if let Some(min) = schema.min {
if num_value < min {
return Err(ValidationError::with_value(
name,
format!("Number must be at least {min}"),
serde_json::Value::Number(value.clone()),
));
}
}
if let Some(max) = schema.max {
if num_value > max {
return Err(ValidationError::with_value(
name,
format!("Number must be at most {max}"),
serde_json::Value::Number(value.clone()),
));
}
}
Ok(())
}
/// Validates array constraints
fn validate_array_constraints(
name: &str,
value: &[serde_json::Value],
schema: &ParameterSchema,
) -> Result<(), ValidationError> {
// Check length constraints
if let Some(min) = schema.min {
if (value.len() as f64) < min {
return Err(ValidationError::with_value(
name,
format!("Array length must be at least {min}"),
serde_json::Value::Array(value.to_vec()),
));
}
}
if let Some(max) = schema.max {
if (value.len() as f64) > max {
return Err(ValidationError::with_value(
name,
format!("Array length must be at most {max}"),
serde_json::Value::Array(value.to_vec()),
));
}
}
// Validate items if schema is provided
if let Some(items_schema) = &schema.items {
for (index, item) in value.iter().enumerate() {
let item_name = format!("{name}[{index}]");
validate_parameter_value(&item_name, item, items_schema)?
}
}
Ok(())
}
/// Gets the type of a JSON value
fn get_value_type(value: &serde_json::Value) -> &'static str {
match value {
serde_json::Value::Null => "null",
serde_json::Value::Bool(_) => "boolean",
serde_json::Value::Number(_) => "number",
serde_json::Value::String(_) => "string",
serde_json::Value::Array(_) => "array",
serde_json::Value::Object(_) => "object",
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_validate_required_parameter() {
let mut schemas = HashMap::new();
schemas.insert("test".to_string(), ParameterSchema {
param_type: ParameterType::String,
required: Some(true),
default: None,
description: None,
r#enum: None,
min: None,
max: None,
step: None,
pattern: None,
items: None,
properties: None,
accept: None,
max_size: None,
width: None,
height: None,
duration: None,
node_mapping: None,
});
let parameters = HashMap::new();
let result = validate_parameters(&parameters, &schemas);
assert!(!result.valid);
assert_eq!(result.errors.len(), 1);
assert_eq!(result.errors[0].path, "test");
}
#[test]
fn test_validate_string_parameter() {
let mut schemas = HashMap::new();
schemas.insert("test".to_string(), ParameterSchema {
param_type: ParameterType::String,
required: Some(true),
default: None,
description: None,
r#enum: None,
min: Some(3.0),
max: Some(10.0),
step: None,
pattern: None,
items: None,
properties: None,
accept: None,
max_size: None,
width: None,
height: None,
duration: None,
node_mapping: None,
});
let mut parameters = HashMap::new();
parameters.insert("test".to_string(), json!("hello"));
let result = validate_parameters(&parameters, &schemas);
assert!(result.valid);
}
}