use scim_server::schema::{Schema, SchemaRegistry};
use std::env;
use std::fs;
use std::path::Path;
use std::process;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Usage: {} <schema-file-or-directory>", args[0]);
eprintln!();
eprintln!("Examples:");
eprintln!(" {} path/to/User.json", args[0]);
eprintln!(" {} ./path/to/schemas/", args[0]);
process::exit(1);
}
let path = &args[1];
let path = Path::new(path);
if path.is_file() {
validate_single_file(path);
} else if path.is_dir() {
validate_directory(path);
} else {
eprintln!(
"Error: '{}' is not a valid file or directory",
path.display()
);
process::exit(1);
}
}
fn validate_single_file(file_path: &Path) {
println!("Validating schema file: {}", file_path.display());
match load_and_validate_schema(file_path) {
Ok(schema) => {
println!("✓ Schema is valid!");
print_schema_summary(&schema);
}
Err(e) => {
eprintln!("❌ Schema validation failed: {}", e);
process::exit(1);
}
}
}
fn validate_directory(dir_path: &Path) {
println!("Validating schemas in directory: {}", dir_path.display());
let mut valid_count = 0;
let mut error_count = 0;
match fs::read_dir(dir_path) {
Ok(entries) => {
for entry in entries {
if let Ok(entry) = entry {
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("json") {
println!(
"\nValidating: {}",
path.file_name().unwrap().to_string_lossy()
);
match load_and_validate_schema(&path) {
Ok(schema) => {
println!(" ✓ Valid - {} ({})", schema.name, schema.id);
valid_count += 1;
}
Err(e) => {
eprintln!(" ❌ Invalid - {}", e);
error_count += 1;
}
}
}
}
}
}
Err(e) => {
eprintln!("Error reading directory: {}", e);
process::exit(1);
}
}
println!("\nValidation Summary:");
println!(" Valid schemas: {}", valid_count);
println!(" Invalid schemas: {}", error_count);
if error_count > 0 {
process::exit(1);
}
println!("\nTesting schema registry loading...");
match SchemaRegistry::from_schema_dir(dir_path) {
Ok(registry) => {
println!("✓ Schema registry loaded successfully");
let schemas = registry.get_schemas();
println!(" Total schemas loaded: {}", schemas.len());
for schema in schemas {
println!(" - {} ({})", schema.name, schema.id);
}
}
Err(e) => {
eprintln!("❌ Failed to load schema registry: {}", e);
process::exit(1);
}
}
}
fn load_and_validate_schema(file_path: &Path) -> Result<Schema, Box<dyn std::error::Error>> {
let content = fs::read_to_string(file_path)?;
let json_value: serde_json::Value = serde_json::from_str(&content)?;
let obj = json_value
.as_object()
.ok_or("Schema must be a JSON object")?;
if !obj.contains_key("id") {
return Err("Schema missing required 'id' field".into());
}
if !obj.contains_key("name") {
return Err("Schema missing required 'name' field".into());
}
if !obj.contains_key("attributes") {
return Err("Schema missing required 'attributes' field".into());
}
let schema: Schema = serde_json::from_str(&content)?;
validate_schema_structure(&schema)?;
Ok(schema)
}
fn validate_schema_structure(schema: &Schema) -> Result<(), Box<dyn std::error::Error>> {
if schema.id.is_empty() {
return Err("Schema ID cannot be empty".into());
}
if !schema.id.starts_with("urn:") && !schema.id.starts_with("http") {
return Err("Schema ID should be a URI (starting with 'urn:' or 'http')".into());
}
if schema.name.is_empty() {
return Err("Schema name cannot be empty".into());
}
if schema.attributes.is_empty() {
return Err("Schema must have at least one attribute".into());
}
for (i, attr) in schema.attributes.iter().enumerate() {
validate_attribute(attr, &format!("attribute[{}]", i))?;
}
Ok(())
}
fn validate_attribute(
attr: &scim_server::schema::AttributeDefinition,
context: &str,
) -> Result<(), Box<dyn std::error::Error>> {
if attr.name.is_empty() {
return Err(format!("{}: Attribute name cannot be empty", context).into());
}
if !attr.canonical_values.is_empty() {
if !matches!(attr.data_type, scim_server::schema::AttributeType::String) {
return Err(format!(
"{}: Canonical values only allowed for string attributes",
context
)
.into());
}
}
if matches!(attr.data_type, scim_server::schema::AttributeType::Complex) {
if attr.sub_attributes.is_empty() {
return Err(format!("{}: Complex attributes must have sub-attributes", context).into());
}
for (i, sub_attr) in attr.sub_attributes.iter().enumerate() {
let sub_context = format!("{}.subAttributes[{}]", context, i);
validate_attribute(sub_attr, &sub_context)?;
}
} else if !attr.sub_attributes.is_empty() {
return Err(format!(
"{}: Non-complex attributes cannot have sub-attributes",
context
)
.into());
}
Ok(())
}
fn print_schema_summary(schema: &Schema) {
println!();
println!("Schema Summary:");
println!(" ID: {}", schema.id);
println!(" Name: {}", schema.name);
println!(" Description: {}", schema.description);
println!(" Attributes: {}", schema.attributes.len());
let mut type_counts = std::collections::HashMap::new();
let mut required_count = 0;
let mut multi_valued_count = 0;
for attr in &schema.attributes {
*type_counts
.entry(format!("{:?}", attr.data_type))
.or_insert(0) += 1;
if attr.required {
required_count += 1;
}
if attr.multi_valued {
multi_valued_count += 1;
}
}
println!(" Required attributes: {}", required_count);
println!(" Multi-valued attributes: {}", multi_valued_count);
println!(" Attribute types:");
for (attr_type, count) in type_counts {
println!(" - {}: {}", attr_type, count);
}
let required_attrs: Vec<&str> = schema
.attributes
.iter()
.filter(|attr| attr.required)
.map(|attr| attr.name.as_str())
.collect();
if !required_attrs.is_empty() {
println!(" Required attribute names: {}", required_attrs.join(", "));
}
}