use super::rules::validate_custom_rules;
use super::schemas::{get_node_config_schema, get_project_schema};
use crate::error::{Mecha10Error, Result};
use anyhow::Context as _;
use jsonschema::{Draft, ValidationError};
use serde_json::Value;
use std::fs;
use std::path::Path;
use tracing::{debug, info};
pub fn validate_project_json(config: &Value) -> Result<()> {
validate_json_against_schema(
config,
&get_project_schema(),
"Project configuration",
"Configuration validation failed",
)
}
pub fn validate_project_config<P: AsRef<Path>>(path: P) -> Result<()> {
let path = path.as_ref();
info!("Validating project configuration: {}", path.display());
let content = fs::read_to_string(path)
.with_context(|| format!("Failed to read config file: {}", path.display()))
.map_err(|e| Mecha10Error::Configuration(format!("{:#}", e)))?;
let config: Value = serde_json::from_str(&content)
.with_context(|| format!("Failed to parse JSON from: {}", path.display()))
.map_err(|e| Mecha10Error::Configuration(format!("{:#}", e)))?;
validate_project_json(&config)?;
validate_custom_rules(&config)?;
info!("Configuration validation passed");
Ok(())
}
pub fn validate_node_config_json(config: &Value) -> Result<()> {
validate_json_against_schema(
config,
&get_node_config_schema(),
"Node configuration",
"Node configuration validation failed",
)
}
fn validate_json_against_schema(
config: &Value,
schema: &Value,
success_label: &str,
failure_prefix: &str,
) -> Result<()> {
let validator = jsonschema::options()
.with_draft(Draft::Draft7)
.build(schema)
.map_err(|e| Mecha10Error::Configuration(format!("Invalid schema: {}", e)))?;
if validator.is_valid(config) {
debug!("{} validation passed", success_label);
Ok(())
} else {
let error_messages: Vec<String> = validator
.iter_errors(config)
.map(|e| format_validation_error(&e))
.collect();
Err(Mecha10Error::Configuration(format!(
"{}:\n{}",
failure_prefix,
error_messages.join("\n")
)))
}
}
fn format_validation_error(error: &ValidationError) -> String {
let path_str = error.instance_path().to_string();
let path = if path_str.is_empty() || path_str == "/" {
"root".to_string()
} else {
path_str
};
format!(" - {}: {} ({})", path, error, error.instance())
}