use json_eval_rs::jsoneval::json_parser;
use json_eval_rs::{JSONEval, ParsedSchema};
use rmp_serde;
use serde_json::Value;
use std::fs;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Instant;
fn print_parsed_schema_info(
parsed_schema: &ParsedSchema,
print_sorted: bool,
print_deps: bool,
print_tables: bool,
print_evals: bool,
) {
if !print_sorted && !print_deps && !print_tables && !print_evals {
return;
}
println!("\n{}", "=".repeat(60));
println!("📋 PARSED SCHEMA INFORMATION");
println!("{}", "=".repeat(60));
if print_sorted {
println!("\n🔄 Sorted Evaluations:");
println!(
" {} batches total",
parsed_schema.sorted_evaluations.len()
);
for (batch_idx, batch) in parsed_schema.sorted_evaluations.iter().enumerate() {
println!(
"\n Batch {} ({} evaluations):",
batch_idx + 1,
batch.len()
);
for eval_key in batch {
println!(" - {}", eval_key);
}
}
}
if print_deps {
println!("\n🔗 Dependencies:");
println!(
" {} evaluation(s) with dependencies",
parsed_schema.dependencies.len()
);
for (eval_key, deps) in parsed_schema.dependencies.iter() {
if !deps.is_empty() {
println!("\n {} depends on:", eval_key);
for dep in deps {
println!(" → {}", dep);
}
}
}
}
if print_tables {
println!("\n📊 Tables:");
println!(" {} table(s) defined", parsed_schema.tables.len());
for (table_key, table_value) in parsed_schema.tables.iter() {
println!("\n {}:", table_key);
println!(
" {}",
serde_json::to_string_pretty(table_value).unwrap_or_default()
);
}
}
if print_evals {
println!("\n⚙️ Evaluations:");
println!(
" {} evaluation(s) compiled",
parsed_schema.evaluations.len()
);
for (eval_key, logic_id) in parsed_schema.evaluations.iter() {
println!(" {} → {:?}", eval_key, logic_id);
}
}
println!("\n{}", "=".repeat(60));
println!();
}
fn print_help(program_name: &str) {
println!("\n🚀 JSON Evaluation CLI\n");
println!("USAGE:");
println!(" {} <SCHEMA_FILE> [OPTIONS]\n", program_name);
println!("ARGUMENTS:");
println!(" <SCHEMA_FILE> Path to schema file (.json or .bform)\n");
println!("OPTIONS:");
println!(" -h, --help Show this help message");
println!(" -d, --data <FILE> Input data file (JSON or .bform)");
println!(" -c, --compare <FILE> Expected output file for comparison");
println!(" --compare-path <PATH> JSON pointer path for comparison (default: \"$.$params.others\")");
println!(" -p, --parsed Use ParsedSchema for efficient caching");
println!(" -i, --iterations <N> Number of evaluation iterations (default: 1)");
println!(" -o, --output <FILE> Output file for evaluated schema (default: stdout)");
println!(" --no-output Suppress output (for benchmarking)");
println!("\nPARSED SCHEMA INSPECTION:");
println!(" --print-sorted-evaluations Print sorted evaluation batches");
println!(" --print-dependencies Print dependency graph");
println!(" --print-tables Print table definitions");
println!(" --print-evaluations Print all evaluations with compiled logic IDs");
println!(" --print-all Print all parsed schema information\n");
println!("EXAMPLES:");
println!(" # Simple evaluation");
println!(" {} schema.json -d data.json\n", program_name);
println!(" # With comparison");
println!(
" {} schema.json -d data.json -c expected.json\n",
program_name
);
println!(" # Using ParsedSchema with iterations");
println!(
" {} schema.json -d data.json --parsed -i 100\n",
program_name
);
println!(" # Full benchmark with custom comparison path");
println!(" {} schema.json -d data.json -c expected.json --compare-path \"$.result\" --parsed -i 100", program_name);
}
fn main() {
let args: Vec<String> = std::env::args().collect();
let program_name = args.get(0).map(|s| s.as_str()).unwrap_or("json-eval-cli");
let mut schema_file: Option<PathBuf> = None;
let mut data_file: Option<PathBuf> = None;
let mut compare_file: Option<PathBuf> = None;
let mut compare_path = "$.$params.others".to_string();
let mut use_parsed = false;
let mut iterations = 1usize;
let mut output_file: Option<PathBuf> = None;
let mut no_output = false;
let mut print_sorted_evaluations = false;
let mut print_dependencies = false;
let mut print_tables = false;
let mut print_evaluations = false;
let mut i = 1;
while i < args.len() {
let arg = &args[i];
if arg == "-h" || arg == "--help" {
print_help(program_name);
return;
} else if arg == "-d" || arg == "--data" {
if i + 1 >= args.len() {
eprintln!("Error: {} requires a value", arg);
print_help(program_name);
std::process::exit(1);
}
i += 1;
data_file = Some(PathBuf::from(&args[i]));
} else if arg == "-c" || arg == "--compare" {
if i + 1 >= args.len() {
eprintln!("Error: {} requires a value", arg);
print_help(program_name);
std::process::exit(1);
}
i += 1;
compare_file = Some(PathBuf::from(&args[i]));
} else if arg == "--compare-path" {
if i + 1 >= args.len() {
eprintln!("Error: {} requires a value", arg);
print_help(program_name);
std::process::exit(1);
}
i += 1;
compare_path = args[i].clone();
} else if arg == "-p" || arg == "--parsed" {
use_parsed = true;
} else if arg == "-i" || arg == "--iterations" {
if i + 1 >= args.len() {
eprintln!("Error: {} requires a value", arg);
print_help(program_name);
std::process::exit(1);
}
i += 1;
match args[i].parse::<usize>() {
Ok(n) if n > 0 => iterations = n,
_ => {
eprintln!(
"Error: iterations must be a positive integer, got '{}'",
args[i]
);
std::process::exit(1);
}
}
} else if arg == "-o" || arg == "--output" {
if i + 1 >= args.len() {
eprintln!("Error: {} requires a value", arg);
print_help(program_name);
std::process::exit(1);
}
i += 1;
output_file = Some(PathBuf::from(&args[i]));
} else if arg == "--no-output" {
no_output = true;
} else if arg == "--print-sorted-evaluations" {
print_sorted_evaluations = true;
} else if arg == "--print-dependencies" {
print_dependencies = true;
} else if arg == "--print-tables" {
print_tables = true;
} else if arg == "--print-evaluations" {
print_evaluations = true;
} else if arg == "--print-all" {
print_sorted_evaluations = true;
print_dependencies = true;
print_tables = true;
print_evaluations = true;
} else if !arg.starts_with('-') {
if schema_file.is_none() {
schema_file = Some(PathBuf::from(arg));
} else {
eprintln!("Error: unexpected positional argument '{}'", arg);
print_help(program_name);
std::process::exit(1);
}
} else {
eprintln!("Error: unknown option '{}'", arg);
print_help(program_name);
std::process::exit(1);
}
i += 1;
}
let schema_file = match schema_file {
Some(f) => f,
None => {
eprintln!("Error: schema file is required\n");
print_help(program_name);
std::process::exit(1);
}
};
if !schema_file.exists() {
eprintln!("Error: schema file '{}' not found", schema_file.display());
std::process::exit(1);
}
let is_schema_msgpack = schema_file
.extension()
.and_then(|e| e.to_str())
.map(|e| e == "bform")
.unwrap_or(false);
let (data_str, is_data_msgpack) = if let Some(ref data_path) = data_file {
if !data_path.exists() {
eprintln!("Error: data file '{}' not found", data_path.display());
std::process::exit(1);
}
let is_msgpack = data_path
.extension()
.and_then(|e| e.to_str())
.map(|e| e == "bform")
.unwrap_or(false);
if is_msgpack {
let data_bytes = fs::read(data_path).unwrap_or_else(|e| {
eprintln!(
"Error: failed to read data file '{}': {}",
data_path.display(),
e
);
std::process::exit(1);
});
let data_value: Value = rmp_serde::from_slice(&data_bytes).unwrap_or_else(|e| {
eprintln!(
"Error: failed to parse MessagePack data file '{}': {}",
data_path.display(),
e
);
std::process::exit(1);
});
(
serde_json::to_string(&data_value).unwrap_or_else(|_| "{}".to_string()),
true,
)
} else {
let data_str = fs::read_to_string(data_path).unwrap_or_else(|e| {
eprintln!(
"Error: failed to read data file '{}': {}",
data_path.display(),
e
);
std::process::exit(1);
});
(data_str, false)
}
} else {
("{}".to_string(), false)
};
println!("\n🚀 JSON Evaluation CLI\n");
println!(
"📄 Schema: {} ({})",
schema_file.display(),
if is_schema_msgpack {
"MessagePack"
} else {
"JSON"
}
);
if let Some(ref data_path) = data_file {
println!(
"📊 Data: {} ({})",
data_path.display(),
if is_data_msgpack {
"MessagePack"
} else {
"JSON"
}
);
}
if use_parsed {
println!("📦 Mode: ParsedSchema (parse once, reuse)");
}
if iterations > 1 {
println!("🔄 Iterations: {}", iterations);
}
if compare_file.is_some() {
println!("🔍 Comparison: enabled (path: {})", compare_path);
}
println!();
let total_start = Instant::now();
let (evaluated_schema, _parse_time, eval_time) = if use_parsed {
let parse_start = Instant::now();
let parsed_schema = if is_schema_msgpack {
let schema_bytes = fs::read(&schema_file).unwrap_or_else(|e| {
eprintln!("Error: failed to read schema: {}", e);
std::process::exit(1);
});
Arc::new(
ParsedSchema::parse_msgpack(&schema_bytes).unwrap_or_else(|e| {
eprintln!("Error: failed to parse MessagePack schema: {}", e);
std::process::exit(1);
}),
)
} else {
let schema_str = fs::read_to_string(&schema_file).unwrap_or_else(|e| {
eprintln!("Error: failed to read schema: {}", e);
std::process::exit(1);
});
Arc::new(ParsedSchema::parse(&schema_str).unwrap_or_else(|e| {
eprintln!("Error: failed to parse schema: {}", e);
std::process::exit(1);
}))
};
let parse_time = parse_start.elapsed();
println!("⏱️ Schema parsing: {:?}", parse_time);
print_parsed_schema_info(
&parsed_schema,
print_sorted_evaluations,
print_dependencies,
print_tables,
print_evaluations,
);
let eval_start = Instant::now();
let mut result_schema = Value::Null;
for iter in 0..iterations {
let mut eval =
JSONEval::with_parsed_schema(parsed_schema.clone(), Some("{}"), Some(&data_str))
.unwrap_or_else(|e| {
eprintln!("Error: failed to create JSONEval: {}", e);
std::process::exit(1);
});
eval.evaluate(&data_str, Some("{}"), None, None)
.unwrap_or_else(|e| {
eprintln!("Error: evaluation failed: {}", e);
std::process::exit(1);
});
result_schema = eval.get_evaluated_schema_resolved();
if iterations > 1 && (iter + 1) % 10 == 0 {
print!(".");
if (iter + 1) % 50 == 0 {
println!(" {}/{}", iter + 1, iterations);
}
}
}
if iterations > 1 && iterations % 50 != 0 {
println!(" {}/{}", iterations, iterations);
}
let eval_time = eval_start.elapsed();
(result_schema, parse_time, eval_time)
} else {
let start = Instant::now();
let mut eval = if is_schema_msgpack {
let schema_bytes = fs::read(&schema_file).unwrap_or_else(|e| {
eprintln!("Error: failed to read schema: {}", e);
std::process::exit(1);
});
JSONEval::new_from_msgpack(&schema_bytes, None, Some(&data_str)).unwrap_or_else(|e| {
eprintln!("Error: failed to create JSONEval from MessagePack: {}", e);
std::process::exit(1);
})
} else {
let schema_str = fs::read_to_string(&schema_file).unwrap_or_else(|e| {
eprintln!("Error: failed to read schema: {}", e);
std::process::exit(1);
});
JSONEval::new(&schema_str, None, Some(&data_str)).unwrap_or_else(|e| {
eprintln!("Error: failed to create JSONEval: {}", e);
std::process::exit(1);
})
};
let parse_time = start.elapsed();
println!("⏱️ Schema parsing & compilation: {:?}", parse_time);
let eval_start = Instant::now();
let mut result_schema = Value::Null;
for iter in 0..iterations {
eval.evaluate(&data_str, Some("{}"), None, None)
.unwrap_or_else(|e| {
eprintln!("Error: evaluation failed: {}", e);
std::process::exit(1);
});
result_schema = eval.get_evaluated_schema_resolved();
if iterations > 1 && (iter + 1) % 10 == 0 {
print!(".");
if (iter + 1) % 50 == 0 {
println!(" {}/{}", iter + 1, iterations);
}
}
}
if iterations > 1 && iterations % 50 != 0 {
println!(" {}/{}", iterations, iterations);
}
let eval_time = eval_start.elapsed();
(result_schema, parse_time, eval_time)
};
let total_time = total_start.elapsed();
println!("\n⏱️ Evaluation: {:?}", eval_time);
if iterations > 1 {
println!(
" Average per iteration: {:?}",
eval_time / iterations as u32
);
}
println!("⏱️ Total time: {:?}\n", total_time);
if !no_output {
let output_json = serde_json::to_string_pretty(&evaluated_schema).unwrap_or_else(|e| {
eprintln!("Error: failed to serialize output: {}", e);
std::process::exit(1);
});
if let Some(output_path) = output_file {
fs::write(&output_path, output_json).unwrap_or_else(|e| {
eprintln!("Error: failed to write output file: {}", e);
std::process::exit(1);
});
println!("✅ Output written to: {}\n", output_path.display());
} else {
println!("📋 Evaluated Schema:");
println!("{}\n", output_json);
}
}
if let Some(compare_path_file) = compare_file {
if !compare_path_file.exists() {
eprintln!(
"Warning: comparison file '{}' not found",
compare_path_file.display()
);
} else {
let expected_str = fs::read_to_string(&compare_path_file).unwrap_or_else(|e| {
eprintln!("Error: failed to read comparison file: {}", e);
std::process::exit(1);
});
let expected: Value = json_parser::parse_json_str(&expected_str).unwrap_or_else(|e| {
eprintln!("Error: failed to parse comparison file: {}", e);
std::process::exit(1);
});
let actual_value = evaluated_schema.pointer(&compare_path);
let expected_value = expected
.pointer(&compare_path)
.or_else(|| expected.get("others"));
match (actual_value, expected_value) {
(Some(actual), Some(expected)) => {
if actual == expected {
println!("✅ Comparison passed: values match at '{}'", compare_path);
} else {
println!("❌ Comparison failed: values differ at '{}'", compare_path);
println!(
" Expected: {}",
serde_json::to_string(expected).unwrap_or_default()
);
println!(
" Actual: {}",
serde_json::to_string(actual).unwrap_or_default()
);
std::process::exit(1);
}
}
(None, Some(_)) => {
println!(
"❌ Comparison failed: path '{}' not found in evaluated schema",
compare_path
);
std::process::exit(1);
}
(Some(_), None) => {
println!(
"❌ Comparison failed: path '{}' not found in expected output",
compare_path
);
std::process::exit(1);
}
(None, None) => {
println!(
"⚠️ Warning: path '{}' not found in either schema",
compare_path
);
}
}
}
}
println!("✅ Evaluation completed successfully!\n");
}