use crate::merge::sdl::{IdentityRule, MergeStrategyType, PropertyDef, PropertyType, SdlDocument};
use crate::validation::{ValidationError, ValidationResult};
pub fn validate_sdl(schema: &SdlDocument) -> ValidationResult {
let mut errors = Vec::new();
if schema.schema.version.is_empty() {
errors.push(ValidationError::global("schema version must not be empty"));
}
for (path, def) in &schema.schema.properties {
if path.contains('.') {
}
if path.is_empty() {
errors.push(ValidationError::new(
"property path must not be empty",
path,
));
}
errors.extend(validate_merge_strategy(path, def));
}
for required_path in &schema.schema.required {
if !schema.schema.properties.contains_key(required_path)
&& !required_path.starts_with("properties.")
{
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
fn validate_merge_strategy(path: &str, def: &PropertyDef) -> Vec<ValidationError> {
let mut errors = Vec::new();
match def.merge.strategy_type {
MergeStrategyType::Replace | MergeStrategyType::Atomic => {
}
MergeStrategyType::DeepMerge => {
if !matches!(def.type_, PropertyType::Object) {
errors.push(ValidationError::new(
"deep_merge strategy is only valid for object type properties",
path,
));
}
}
MergeStrategyType::OrderedUnique | MergeStrategyType::SetUnion => {
if def.merge.identity.is_none() {
errors.push(ValidationError::new(
"ordered_unique and set_union strategies require an identity rule",
path,
));
}
if let Some(ref identity) = def.merge.identity {
errors.extend(validate_identity_rule(path, identity));
}
}
MergeStrategyType::EdgeList => {
if def.merge.source_key.is_none() {
errors.push(ValidationError::new(
"edge_list strategy requires a source_key",
path,
));
}
if def.merge.target_key.is_none() {
errors.push(ValidationError::new(
"edge_list strategy requires a target_key",
path,
));
}
if def.merge.identity.is_none() {
errors.push(ValidationError::new(
"edge_list strategy requires an identity rule",
path,
));
}
if let Some(ref identity) = def.merge.identity {
errors.extend(validate_identity_rule(path, identity));
}
}
}
errors
}
fn validate_identity_rule(path: &str, identity: &IdentityRule) -> Vec<ValidationError> {
let mut errors = Vec::new();
match identity {
IdentityRule::PrimitiveValue => {
}
IdentityRule::Key { key } => {
if key.is_empty() {
errors.push(ValidationError::new("identity key must not be empty", path));
}
}
}
errors
}
#[cfg(test)]
mod tests {
use super::*;
use crate::merge::sdl::SdlDocument;
#[test]
fn test_validate_valid_sdl() {
let yaml = r#"
schema:
version: "1.0"
required: []
properties:
name:
type: string
merge:
type: replace
tags:
type: array
merge:
type: ordered_unique
identity:
mode: primitive_value
metadata:
type: object
merge:
type: deep_merge
version:
type: number
merge:
type: atomic
edges:
type: array
merge:
type: edge_list
source_key: from
target_key: to
identity:
mode: key
key: id
"#;
let doc = SdlDocument::from_yaml(yaml).unwrap();
assert!(validate_sdl(&doc).is_ok());
}
#[test]
fn test_validate_missing_identity_for_ordered_unique() {
let yaml = r#"
schema:
version: "1.0"
required: []
properties:
tags:
type: array
merge:
type: ordered_unique
"#;
let doc = SdlDocument::from_yaml(yaml).unwrap();
let result = validate_sdl(&doc);
assert!(result.is_err());
let errors = result.unwrap_err();
assert!(errors.iter().any(|e| e.message.contains("identity")));
}
#[test]
fn test_validate_missing_identity_for_set_union() {
let yaml = r#"
schema:
version: "1.0"
required: []
properties:
tags:
type: array
merge:
type: set_union
"#;
let doc = SdlDocument::from_yaml(yaml).unwrap();
let result = validate_sdl(&doc);
assert!(result.is_err());
}
#[test]
fn test_validate_missing_source_key_for_edge_list() {
let yaml = r#"
schema:
version: "1.0"
required: []
properties:
edges:
type: array
merge:
type: edge_list
target_key: to
identity:
mode: key
key: id
"#;
let doc = SdlDocument::from_yaml(yaml).unwrap();
let result = validate_sdl(&doc);
assert!(result.is_err());
let errors = result.unwrap_err();
assert!(errors.iter().any(|e| e.message.contains("source_key")));
}
#[test]
fn test_validate_deep_merge_non_object() {
let yaml = r#"
schema:
version: "1.0"
required: []
properties:
name:
type: string
merge:
type: deep_merge
"#;
let doc = SdlDocument::from_yaml(yaml).unwrap();
let result = validate_sdl(&doc);
assert!(result.is_err());
}
#[test]
fn test_validate_empty_key_identity() {
let yaml = r#"
schema:
version: "1.0"
required: []
properties:
items:
type: array
merge:
type: ordered_unique
identity:
mode: key
key: ""
"#;
let doc = SdlDocument::from_yaml(yaml).unwrap();
let result = validate_sdl(&doc);
assert!(result.is_err());
}
#[test]
fn test_validate_empty_version() {
let yaml = r#"
schema:
version: ""
required: []
properties:
name:
type: string
merge:
type: replace
"#;
let doc = SdlDocument::from_yaml(yaml).unwrap();
let result = validate_sdl(&doc);
assert!(result.is_err());
}
}