277 lines
8.0 KiB
Rust
277 lines
8.0 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) {
|
|
if !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);
|
|
if let Err(error) = validate_parameter_value(&item_name, item, items_schema) {
|
|
return Err(error);
|
|
}
|
|
}
|
|
}
|
|
|
|
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,
|
|
pattern: None,
|
|
items: None,
|
|
properties: None,
|
|
});
|
|
|
|
let parameters = HashMap::new();
|
|
let result = validate_parameters(¶meters, &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),
|
|
pattern: None,
|
|
items: None,
|
|
properties: None,
|
|
});
|
|
|
|
let mut parameters = HashMap::new();
|
|
parameters.insert("test".to_string(), json!("hello"));
|
|
|
|
let result = validate_parameters(¶meters, &schemas);
|
|
assert!(result.valid);
|
|
}
|
|
}
|