use std::fs;
use std::path::{Path, PathBuf};
use anyhow::Context;
use clap::{Parser, Subcommand};
use serde::de::DeserializeOwned;
use modelo::models::Model;
use modelo::models::ort::license_classifications::LicenseClassifications;
use modelo::models::ort::ort_result::OrtResult;
use modelo::models::ort::repository_configuration::RepositoryConfiguration;
#[derive(Parser, Debug)]
#[command(author, version, about = "Validate python-ort YAML data files")]
pub struct Args {
#[arg(short, long, global = true)]
pub debug: bool,
#[command(subcommand)]
pub command: Option<Command>,
}
#[derive(Subcommand, Debug)]
pub enum Command {
LicenseClassifications { datafile: PathBuf },
RepositoryConfiguration { datafile: PathBuf },
OrtResult { datafile: PathBuf },
Tui {
datafile: Option<PathBuf>,
},
}
pub fn run() -> anyhow::Result<()> {
let args = Args::parse();
match args.command {
Some(Command::LicenseClassifications { datafile }) => {
validate_file::<LicenseClassifications>(
"license-classifications",
&datafile,
args.debug,
)
}
Some(Command::RepositoryConfiguration { datafile }) => {
validate_file::<RepositoryConfiguration>(
"repository-configuration",
&datafile,
args.debug,
)
}
Some(Command::OrtResult { datafile }) => {
validate_file::<OrtResult>("ort-result", &datafile, args.debug)
}
Some(Command::Tui { datafile }) => crate::tui::run(datafile),
None => crate::tui::run(None),
}
}
fn validate_file<T>(kind: &str, path: &Path, debug: bool) -> anyhow::Result<()>
where
T: Model + DeserializeOwned,
{
let contents =
fs::read_to_string(path).with_context(|| format!("failed to read {}", path.display()))?;
validate_str::<T>(kind, &contents, debug)
.with_context(|| format!("{} is not a valid {kind} file", path.display()))
}
fn validate_str<T>(kind: &str, contents: &str, debug: bool) -> anyhow::Result<()>
where
T: Model + DeserializeOwned,
{
let value: T =
serde_yaml::from_str(contents).with_context(|| format!("failed to parse as {kind}"))?;
value.validate()?;
println!("valid {kind} file.");
if debug {
println!("{value:#?}");
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_license_classifications_yaml_passes() {
let yaml = r#"
categories:
- name: permissive
categorizations:
- id: MIT
categories: [permissive]
"#;
assert!(
validate_str::<LicenseClassifications>("license-classifications", yaml, false).is_ok()
);
}
#[test]
fn duplicate_category_name_fails_validation() {
let yaml = r#"
categories:
- name: permissive
- name: permissive
categorizations: []
"#;
assert!(
validate_str::<LicenseClassifications>("license-classifications", yaml, false).is_err()
);
}
#[test]
fn malformed_yaml_fails_to_parse() {
let yaml = "not: [valid: yaml";
assert!(
validate_str::<LicenseClassifications>("license-classifications", yaml, false).is_err()
);
}
}