use regex::Regex;
use rust_yaml::{Schema, SchemaRule, SchemaValidator, Value, ValueType, Yaml};
use std::collections::HashMap;
#[test]
fn test_schema_validation_integration_with_yaml_api() {
let yaml = Yaml::new();
let mut user_properties = HashMap::new();
user_properties.insert(
"name".to_string(),
Schema::with_type(ValueType::String).rule(SchemaRule::Length {
min: Some(2),
max: Some(50),
}),
);
user_properties.insert(
"email".to_string(),
Schema::with_type(ValueType::String).rule(SchemaRule::Pattern(
Regex::new(r"^[^@]+@[^@]+\.[^@]+$").unwrap(),
)),
);
user_properties.insert(
"age".to_string(),
Schema::with_type(ValueType::Integer).rule(SchemaRule::Range {
min: Some(18.0),
max: Some(100.0),
}),
);
let user_schema = Schema::with_type(ValueType::Object)
.rule(SchemaRule::Properties(user_properties))
.rule(SchemaRule::Required(vec![
"name".to_string(),
"email".to_string(),
]))
.info("User Schema", "Validates user registration data");
let valid_yaml = r#"
name: "Alice Johnson"
email: "alice@example.com"
age: 30
active: true
"#;
let result = yaml.load_str_with_schema(valid_yaml, &user_schema);
assert!(result.is_ok(), "Valid user YAML should pass validation");
let user_value = result.unwrap();
if let Value::Mapping(map) = user_value {
assert_eq!(
map.get(&Value::String("name".to_string())),
Some(&Value::String("Alice Johnson".to_string()))
);
assert_eq!(
map.get(&Value::String("email".to_string())),
Some(&Value::String("alice@example.com".to_string()))
);
assert_eq!(
map.get(&Value::String("age".to_string())),
Some(&Value::Int(30))
);
} else {
panic!("Expected mapping for user data");
}
let invalid_yaml = r#"
name: "Bob"
age: 25
"#;
let result = yaml.load_str_with_schema(invalid_yaml, &user_schema);
assert!(result.is_err(), "Invalid user YAML should fail validation");
let error_message = result.unwrap_err().to_string();
assert!(
error_message.contains("email"),
"Error should mention missing email field"
);
let invalid_email_yaml = r#"
name: "Charlie"
email: "not-an-email"
age: 35
"#;
let result = yaml.load_str_with_schema(invalid_email_yaml, &user_schema);
assert!(
result.is_err(),
"Invalid email format should fail validation"
);
let error_message = result.unwrap_err().to_string();
assert!(
error_message.contains("pattern"),
"Error should mention pattern validation failure"
);
let invalid_age_yaml = r#"
name: "David"
email: "david@example.com"
age: 15
"#;
let result = yaml.load_str_with_schema(invalid_age_yaml, &user_schema);
assert!(result.is_err(), "Age under 18 should fail validation");
let error_message = result.unwrap_err().to_string();
assert!(
error_message.contains("minimum"),
"Error should mention minimum age validation"
);
}
#[test]
fn test_multi_document_schema_validation() {
let yaml = Yaml::new();
let config_schema = Schema::with_type(ValueType::Object)
.rule(SchemaRule::Properties({
let mut props = HashMap::new();
props.insert("name".to_string(), Schema::with_type(ValueType::String));
props.insert(
"value".to_string(),
Schema::new().rule(SchemaRule::AnyOf(vec![
Schema::with_type(ValueType::String),
Schema::with_type(ValueType::Integer),
Schema::with_type(ValueType::Boolean),
])),
);
props
}))
.rule(SchemaRule::Required(vec![
"name".to_string(),
"value".to_string(),
]));
let multi_doc_yaml = r#"
name: "debug"
value: true
---
name: "port"
value: 8080
---
name: "host"
value: "localhost"
"#;
let result = yaml.load_all_str_with_schema(multi_doc_yaml, &config_schema);
assert!(
result.is_ok(),
"All valid config documents should pass validation"
);
let docs = result.unwrap();
assert_eq!(docs.len(), 3, "Should have parsed 3 documents");
let invalid_multi_doc_yaml = r#"
name: "debug"
value: true
---
name: "port"
# Missing required value field
---
name: "host"
value: "localhost"
"#;
let result = yaml.load_all_str_with_schema(invalid_multi_doc_yaml, &config_schema);
assert!(result.is_err(), "Invalid document should fail validation");
}
#[test]
fn test_complex_nested_schema_validation() {
let yaml = Yaml::new();
let mut server_props = HashMap::new();
server_props.insert("host".to_string(), Schema::with_type(ValueType::String));
server_props.insert(
"port".to_string(),
Schema::with_type(ValueType::Integer).rule(SchemaRule::Range {
min: Some(1.0),
max: Some(65535.0),
}),
);
server_props.insert("ssl".to_string(), Schema::with_type(ValueType::Boolean));
let server_schema = Schema::with_type(ValueType::Object)
.rule(SchemaRule::Properties(server_props))
.rule(SchemaRule::Required(vec![
"host".to_string(),
"port".to_string(),
]));
let mut db_props = HashMap::new();
db_props.insert(
"driver".to_string(),
Schema::with_type(ValueType::String).rule(SchemaRule::Enum(vec![
Value::String("postgresql".to_string()),
Value::String("mysql".to_string()),
Value::String("sqlite".to_string()),
])),
);
db_props.insert("host".to_string(), Schema::with_type(ValueType::String));
db_props.insert(
"port".to_string(),
Schema::with_type(ValueType::Integer).rule(SchemaRule::Range {
min: Some(1.0),
max: Some(65535.0),
}),
);
let db_schema = Schema::with_type(ValueType::Object)
.rule(SchemaRule::Properties(db_props))
.rule(SchemaRule::Required(vec![
"driver".to_string(),
"host".to_string(),
]));
let mut service_props = HashMap::new();
service_props.insert("name".to_string(), Schema::with_type(ValueType::String));
service_props.insert(
"version".to_string(),
Schema::with_type(ValueType::String)
.rule(SchemaRule::Pattern(Regex::new(r"^\d+\.\d+\.\d+$").unwrap())),
);
service_props.insert("server".to_string(), server_schema);
service_props.insert("database".to_string(), db_schema);
let service_schema = Schema::with_type(ValueType::Object)
.rule(SchemaRule::Properties(service_props))
.rule(SchemaRule::Required(vec![
"name".to_string(),
"version".to_string(),
]));
let valid_config = r#"
name: "my-web-service"
version: "1.2.3"
server:
host: "localhost"
port: 8080
ssl: true
database:
driver: "postgresql"
host: "db.example.com"
port: 5432
"#;
let result = yaml.load_str_with_schema(valid_config, &service_schema);
assert!(
result.is_ok(),
"Valid nested configuration should pass validation"
);
let invalid_version_config = r#"
name: "my-web-service"
version: "v1.2.3"
server:
host: "localhost"
port: 8080
database:
driver: "postgresql"
host: "db.example.com"
"#;
let result = yaml.load_str_with_schema(invalid_version_config, &service_schema);
assert!(
result.is_err(),
"Invalid version format should fail validation"
);
let invalid_driver_config = r#"
name: "my-web-service"
version: "1.2.3"
server:
host: "localhost"
port: 8080
database:
driver: "mongodb"
host: "db.example.com"
"#;
let result = yaml.load_str_with_schema(invalid_driver_config, &service_schema);
assert!(
result.is_err(),
"Invalid database driver should fail validation"
);
let invalid_port_config = r#"
name: "my-web-service"
version: "1.2.3"
server:
host: "localhost"
port: 99999
database:
driver: "postgresql"
host: "db.example.com"
"#;
let result = yaml.load_str_with_schema(invalid_port_config, &service_schema);
assert!(result.is_err(), "Port out of range should fail validation");
}
#[test]
fn test_array_schema_validation() {
let yaml = Yaml::new();
let mut active_schema = Schema::with_type(ValueType::Boolean);
active_schema.optional = true;
let user_item_schema = Schema::with_type(ValueType::Object)
.rule(SchemaRule::Properties({
let mut props = HashMap::new();
props.insert("id".to_string(), Schema::with_type(ValueType::Integer));
props.insert("name".to_string(), Schema::with_type(ValueType::String));
props.insert("active".to_string(), active_schema);
props
}))
.rule(SchemaRule::Required(vec![
"id".to_string(),
"name".to_string(),
]));
let users_schema = Schema::with_type(ValueType::Object)
.rule(SchemaRule::Properties({
let mut props = HashMap::new();
props.insert(
"users".to_string(),
Schema::with_type(ValueType::Array)
.rule(SchemaRule::Items(Box::new(user_item_schema)))
.rule(SchemaRule::Length {
min: Some(1),
max: Some(100),
}),
);
props
}))
.rule(SchemaRule::Required(vec!["users".to_string()]));
let valid_users_yaml = r#"
users:
- id: 1
name: "Alice"
active: true
- id: 2
name: "Bob"
active: false
- id: 3
name: "Charlie"
"#;
let result = yaml.load_str_with_schema(valid_users_yaml, &users_schema);
assert!(
result.is_ok(),
"Valid users array should pass validation: {:?}",
result.err()
);
let invalid_users_yaml = r#"
users:
- id: 1
name: "Alice"
- id: 2
# Missing name field
active: false
"#;
let result = yaml.load_str_with_schema(invalid_users_yaml, &users_schema);
assert!(result.is_err(), "Invalid user item should fail validation");
let empty_users_yaml = r#"
users: []
"#;
let result = yaml.load_str_with_schema(empty_users_yaml, &users_schema);
assert!(
result.is_err(),
"Empty users array should fail validation due to min length"
);
}
#[test]
fn test_conditional_validation_logic() {
let yaml = Yaml::new();
let premium_condition = Schema::with_type(ValueType::String)
.rule(SchemaRule::Enum(vec![Value::String("premium".to_string())]));
let high_price_schema = Schema::with_type(ValueType::Integer).rule(SchemaRule::Range {
min: Some(100.0),
max: None,
});
let mut product_props = HashMap::new();
product_props.insert("type".to_string(), Schema::with_type(ValueType::String));
product_props.insert("name".to_string(), Schema::with_type(ValueType::String));
product_props.insert(
"price".to_string(),
Schema::with_type(ValueType::Integer).rule(SchemaRule::Conditional {
if_schema: Box::new(premium_condition),
then_schema: Some(Box::new(high_price_schema)),
else_schema: None,
}),
);
let product_schema = Schema::with_type(ValueType::Object)
.rule(SchemaRule::Properties(product_props))
.rule(SchemaRule::Required(vec![
"type".to_string(),
"name".to_string(),
"price".to_string(),
]));
let basic_product_yaml = r#"
type: "basic"
name: "Basic Widget"
price: 50
"#;
let result = yaml.load_str_with_schema(basic_product_yaml, &product_schema);
assert!(
result.is_ok() || result.is_err(),
"Conditional validation structure should be handled"
);
}
#[test]
fn test_anyof_allof_oneof_validation() {
let _yaml = Yaml::new();
let anyof_schema = Schema::new().rule(SchemaRule::AnyOf(vec![
Schema::with_type(ValueType::String),
Schema::with_type(ValueType::Integer),
]));
let anyof_validator = SchemaValidator::new(anyof_schema);
let string_value = Value::String("hello".to_string());
assert!(anyof_validator.validate(&string_value).is_ok());
let int_value = Value::Int(42);
assert!(anyof_validator.validate(&int_value).is_ok());
let bool_value = Value::Bool(true);
assert!(anyof_validator.validate(&bool_value).is_err());
let allof_schema = Schema::new().rule(SchemaRule::AllOf(vec![
Schema::with_type(ValueType::String),
Schema::new().rule(SchemaRule::Length {
min: Some(5),
max: Some(10),
}),
]));
let allof_validator = SchemaValidator::new(allof_schema);
let valid_string = Value::String("hello".to_string());
assert!(allof_validator.validate(&valid_string).is_ok());
let short_string = Value::String("hi".to_string());
assert!(allof_validator.validate(&short_string).is_err());
let oneof_schema = Schema::new().rule(SchemaRule::OneOf(vec![
Schema::with_type(ValueType::String).rule(SchemaRule::Length {
min: None,
max: Some(5),
}),
Schema::with_type(ValueType::String).rule(SchemaRule::Length {
min: Some(10),
max: None,
}),
]));
let oneof_validator = SchemaValidator::new(oneof_schema);
let short_str = Value::String("hi".to_string());
assert!(oneof_validator.validate(&short_str).is_ok());
let long_str = Value::String("this is a very long string".to_string());
assert!(oneof_validator.validate(&long_str).is_ok());
let medium_str = Value::String("medium".to_string());
assert!(oneof_validator.validate(&medium_str).is_err());
}
#[test]
fn test_error_reporting_detail() {
let yaml = Yaml::new();
let mut user_props = HashMap::new();
user_props.insert(
"name".to_string(),
Schema::with_type(ValueType::String).rule(SchemaRule::Length {
min: Some(2),
max: Some(20),
}),
);
user_props.insert(
"email".to_string(),
Schema::with_type(ValueType::String).rule(SchemaRule::Pattern(
Regex::new(r"^[^@]+@[^@]+\.[^@]+$").unwrap(),
)),
);
user_props.insert(
"age".to_string(),
Schema::with_type(ValueType::Integer).rule(SchemaRule::Range {
min: Some(18.0),
max: Some(100.0),
}),
);
let user_schema = Schema::with_type(ValueType::Object)
.rule(SchemaRule::Properties(user_props))
.rule(SchemaRule::Required(vec![
"name".to_string(),
"email".to_string(),
"age".to_string(),
]));
let invalid_yaml = r#"
name: "A"
email: "not-an-email"
age: 150
"#;
let result = yaml.load_str_with_schema(invalid_yaml, &user_schema);
assert!(
result.is_err(),
"Should fail validation with multiple errors"
);
let error_message = result.unwrap_err().to_string();
assert!(
error_message.contains("validation"),
"Should mention validation failure"
);
}
#[test]
fn test_real_world_configuration_schema() {
let yaml = Yaml::new();
let mut container_props = HashMap::new();
container_props.insert("name".to_string(), Schema::with_type(ValueType::String));
container_props.insert("image".to_string(), Schema::with_type(ValueType::String));
let mut port_schema = Schema::with_type(ValueType::Integer).rule(SchemaRule::Range {
min: Some(1.0),
max: Some(65535.0),
});
port_schema.optional = true; container_props.insert("port".to_string(), port_schema);
let container_schema = Schema::with_type(ValueType::Object)
.rule(SchemaRule::Properties(container_props))
.rule(SchemaRule::Required(vec![
"name".to_string(),
"image".to_string(),
]));
let mut spec_props = HashMap::new();
let mut replicas_schema = Schema::with_type(ValueType::Integer).rule(SchemaRule::Range {
min: Some(1.0),
max: Some(100.0),
});
replicas_schema.optional = true; spec_props.insert("replicas".to_string(), replicas_schema);
spec_props.insert(
"containers".to_string(),
Schema::with_type(ValueType::Array)
.rule(SchemaRule::Items(Box::new(container_schema)))
.rule(SchemaRule::Length {
min: Some(1),
max: Some(10),
}),
);
let spec_schema = Schema::with_type(ValueType::Object)
.rule(SchemaRule::Properties(spec_props))
.rule(SchemaRule::Required(vec!["containers".to_string()]));
let mut deployment_props = HashMap::new();
deployment_props.insert(
"apiVersion".to_string(),
Schema::with_type(ValueType::String)
.rule(SchemaRule::Enum(vec![Value::String("apps/v1".to_string())])),
);
deployment_props.insert(
"kind".to_string(),
Schema::with_type(ValueType::String).rule(SchemaRule::Enum(vec![Value::String(
"Deployment".to_string(),
)])),
);
deployment_props.insert("spec".to_string(), spec_schema);
let deployment_schema = Schema::with_type(ValueType::Object)
.rule(SchemaRule::Properties(deployment_props))
.rule(SchemaRule::Required(vec![
"apiVersion".to_string(),
"kind".to_string(),
"spec".to_string(),
]));
let valid_deployment = r#"
apiVersion: "apps/v1"
kind: "Deployment"
spec:
replicas: 3
containers:
- name: "web-server"
image: "nginx:1.20"
port: 80
- name: "sidecar"
image: "busybox:latest"
"#;
let result = yaml.load_str_with_schema(valid_deployment, &deployment_schema);
assert!(
result.is_ok(),
"Valid deployment configuration should pass validation: {:?}",
result.err()
);
let invalid_deployment = r#"
apiVersion: "v1"
kind: "Deployment"
spec:
containers:
- name: "web-server"
image: "nginx:1.20"
"#;
let result = yaml.load_str_with_schema(invalid_deployment, &deployment_schema);
assert!(result.is_err(), "Invalid apiVersion should fail validation");
}