provenance-cli 0.2.1

Command-line interface for Provenance requirements traceability
use std::io::Read as _;

use crate::cli::sdk::SdkCommand;
use crate::output;
use provenance_core::{ScopeId, StableId};
use provenance_store::operations;
use provenance_store::state_store::{BeginVerificationInput, CompleteVerificationInput};

mod check_statement;
mod query;
mod render;

pub(super) fn handle(command: SdkCommand) -> anyhow::Result<()> {
    match command {
        SdkCommand::CheckStatement { format } => check_statement::handle(format)?,
        SdkCommand::Info { repo, format } => {
            output::print(format, &operations::engine_info(repo)?)?;
        }
        SdkCommand::Plan {
            repo,
            scope,
            format,
        } => {
            let repo = Some(operations::discover_repository(repo)?);
            let input = read_stdin_json()?;
            let plan = operations::plan(repo, &ScopeId::new(scope)?, input)?;
            match format {
                output::OutputFormat::Json | output::OutputFormat::Jsonl => {
                    output::print(format, &plan)?;
                }
                output::OutputFormat::Markdown
                | output::OutputFormat::Table
                | output::OutputFormat::Toon => {
                    print!("{}", render::render(&plan));
                }
            }
        }
        SdkCommand::Apply {
            repo,
            scope,
            format,
        } => {
            let repo = Some(operations::discover_repository(repo)?);
            let input = read_stdin_json()?;
            let result = operations::apply(repo, &ScopeId::new(scope)?, input)?;
            output::print(format, &result)?;
        }
        SdkCommand::BeginVerification {
            repo,
            scope,
            format,
        } => {
            let repo = Some(operations::discover_repository(repo)?);
            let input = read_stdin_json::<BeginVerificationInput>()?;
            let run = operations::begin_verification(repo, ScopeId::new(scope)?, input)?;
            output::print(format, &run)?;
        }
        SdkCommand::CompleteVerification {
            repo,
            scope,
            format,
        } => {
            let repo = Some(operations::discover_repository(repo)?);
            let input = read_stdin_json::<CompleteVerificationInput>()?;
            let run = operations::complete_verification(repo, &ScopeId::new(scope)?, input)?;
            output::print(format, &run)?;
        }
        SdkCommand::VerificationRuns {
            repo,
            scope,
            rule,
            format,
        } => {
            let repo = Some(operations::discover_repository(repo)?);
            let rule = rule.map(StableId::new).transpose()?;
            let runs = operations::verification_runs(repo, &ScopeId::new(scope)?, rule.as_ref())?;
            output::print(format, &runs)?;
        }
        SdkCommand::Get { query } => query::handle(query::Operation::Get, query)?,
        SdkCommand::Search { query } => query::handle(query::Operation::Search, query)?,
        SdkCommand::Neighbors { query } => query::handle(query::Operation::Neighbors, query)?,
        SdkCommand::Trace { query } => query::handle(query::Operation::Trace, query)?,
        SdkCommand::Impact { query } => query::handle(query::Operation::Impact, query)?,
        SdkCommand::Evidence { query } => query::handle(query::Operation::Evidence, query)?,
        SdkCommand::Stale { query } => query::handle(query::Operation::Stale, query)?,
        SdkCommand::ResolveSymbol { query } => {
            query::handle(query::Operation::ResolveSymbol, query)?;
        }
        SdkCommand::VerificationBindings {
            repo,
            scope,
            rule,
            format,
        } => {
            let repo = Some(operations::discover_repository(repo)?);
            let rule = rule.map(StableId::new).transpose()?;
            let bindings =
                operations::verification_bindings(repo, &ScopeId::new(scope)?, rule.as_ref())?;
            output::print(format, &bindings)?;
        }
    }
    Ok(())
}

fn read_stdin_json<T: serde::de::DeserializeOwned>() -> anyhow::Result<T> {
    let mut input = String::new();
    std::io::stdin().read_to_string(&mut input)?;
    anyhow::ensure!(
        !input.trim().is_empty(),
        "expected a JSON document on stdin"
    );
    serde_json::from_str(&input).map_err(Into::into)
}