open-agent-profile 1.0.4

Rust support library for Open Agent Profile 1.0
Documentation
use std::{path::PathBuf, process::ExitCode};

use clap::Parser;
use open_agent_profile::{
    ApplyOptions, OapFormat, apply_delta, load, serialize, validate, write_atomically,
};

#[derive(Parser)]
#[command(version, about = "Safely apply an OAP state delta")]
struct Arguments {
    #[arg(long)]
    approve: bool,
    #[arg(long)]
    dry_run: bool,
    #[arg(long, default_value = "oap-rust")]
    actor: String,
    profile: PathBuf,
    delta: PathBuf,
}

fn main() -> ExitCode {
    let args = Arguments::parse();
    let profile = match load(&args.profile) {
        Ok(value) => value,
        Err(error) => {
            eprintln!("error: {error}");
            return ExitCode::FAILURE;
        }
    };
    let delta = match load(&args.delta) {
        Ok(value) => value,
        Err(error) => {
            eprintln!("error: {error}");
            return ExitCode::FAILURE;
        }
    };
    for (path, document) in [(&args.profile, &profile), (&args.delta, &delta)] {
        let report = validate(document, Some(path));
        if !report.ok {
            for error in report.errors {
                eprintln!("{}: {}: {}", path.display(), error.pointer, error.message);
            }
            return ExitCode::FAILURE;
        }
    }
    let result = match apply_delta(
        &profile,
        &delta,
        ApplyOptions {
            approved: args.approve,
            actor: Some(&args.actor),
            now: None,
        },
    ) {
        Ok(value) => value,
        Err(error) => {
            eprintln!("error: {error}");
            return ExitCode::FAILURE;
        }
    };
    let format = OapFormat::from_path(&args.profile);
    let encoded = match serialize(&result.profile, format) {
        Ok(value) => value,
        Err(error) => {
            eprintln!("error: {error}");
            return ExitCode::FAILURE;
        }
    };
    for warning in result.warnings {
        eprintln!("warn: {warning}");
    }
    if !result.pending_proposals.is_empty() {
        eprintln!(
            "{} proposal(s) require human review and were not applied",
            result.pending_proposals.len()
        );
    }
    if args.dry_run {
        print!("{encoded}");
    } else if let Err(error) = write_atomically(&args.profile, encoded.as_bytes()) {
        eprintln!("error: {error}");
        return ExitCode::FAILURE;
    }
    eprintln!(
        "{} revision {}",
        if args.dry_run { "would write" } else { "wrote" },
        result
            .profile
            .get("metadata")
            .and_then(|value| value.get("revision"))
            .and_then(|value| value.as_u64())
            .unwrap_or_default()
    );
    ExitCode::SUCCESS
}