use clap::Parser;
use clap::Subcommand;
use eyre::Context;
use eyre::Result;
use yaml_schema::Engine;
use yaml_schema::loader;
use yaml_schema::version;
#[derive(Parser, Debug, Default)]
#[command(name = "ys")]
#[command(author = "Alistair Israel <aisrael@gmail.com>")]
#[command(version = clap::crate_version!())]
#[command(about = "A tool for validating YAML against a schema")]
#[command(arg_required_else_help = true)]
pub struct Opts {
#[command(subcommand)]
pub command: Option<Commands>,
#[arg(short = 'f', long = "schema")]
pub schemas: Vec<String>,
#[arg(long = "fail-fast", default_value = "false")]
pub fail_fast: bool,
pub file: Option<String>,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
#[command(about = "Display the ys version")]
Version,
}
fn main() {
env_logger::init();
let opts = Opts::parse();
if let Some(command) = opts.command {
match command {
Commands::Version => {
println!("ys {}", version());
}
}
} else {
match command_validate(opts) {
Ok(return_code) => {
std::process::exit(return_code);
}
Err(e) => {
eprintln!("Validation failed: {e}");
std::process::exit(1);
}
}
}
}
fn command_validate(opts: Opts) -> Result<i32> {
if opts.schemas.is_empty() {
return Err(eyre::eyre!("No schema file(s) specified"));
}
if opts.file.is_none() {
return Err(eyre::eyre!("No YAML file specified"));
}
let schema_filename = opts.schemas.first().expect("No schema file(s) specified");
let root_schema = match loader::load_file(schema_filename) {
Ok(schema) => schema,
Err(e) => {
eprintln!("Failed to read YAML schema file: {schema_filename}");
log::error!("{e}");
return Ok(1);
}
};
let yaml_filename = opts.file.as_ref().expect("No YAML file specified");
let yaml_contents = std::fs::read_to_string(yaml_filename)
.wrap_err_with(|| format!("Failed to read YAML file: {yaml_filename}"))?;
match Engine::evaluate(&root_schema, &yaml_contents, opts.fail_fast) {
Ok(context) => {
if context.has_errors() {
for error in context.errors.borrow().iter() {
eprintln!("{error}");
}
return Ok(1);
}
Ok(0)
}
Err(e) => {
eprintln!("Validation failed: {e}");
Ok(1)
}
}
}