ocpi-tariffs-cli 0.49.0

CLI application for OCPI tariff calculation
Documentation
//! Explain a tariff in human language and print the explanation.

use std::{io::IsTerminal as _, path::PathBuf};

use clap::{Args, Parser};
use console::style;
use ocpi_tariffs::{json, tariff};

use crate::{
    load_object_file, load_object_from_stdin,
    opts::{FilePath, Version},
    print, Error, ObjectKind,
};

/// The name of the tariff source when using `stdin`.
const STDIN_NAME: &str = "<stdin>";

/// The arguments and logic for the `explain::Command`.
#[derive(Parser)]
pub struct Command {
    /// Arguments for the `explain::Command`.
    #[command(flatten)]
    args: Arguments,
}

/// Arguments for the explain command.
#[derive(Args)]
struct Arguments {
    /// The OCPI version that should be used for the input tariff.
    #[arg(short = 'o', long, value_enum)]
    ocpi_version: Version,

    /// Pretty print the source tariff JSON before the explanation.
    #[arg(long)]
    pretty: bool,

    /// A path to the tariff structure in JSON format.
    #[arg(short = 'f', long)]
    file: Option<PathBuf>,
}

impl Command {
    /// Run the `explain` Command.
    pub fn run(self) -> Result<(), Error> {
        let Self {
            args:
                Arguments {
                    ocpi_version,
                    pretty,
                    file: tariff,
                },
        } = self;

        let ocpi_version = ocpi_version.into();
        let tariff_file_path = tariff.as_deref().map(FilePath::from_path).transpose()?;
        let tariff_name = tariff_file_path
            .as_ref()
            .map(FilePath::file_name)
            .unwrap_or(STDIN_NAME);

        eprintln!(
            "{} tariff `{}` as version `{}`",
            style("Explaining").green().bold(),
            style(&tariff_name).blue(),
            style(ocpi_version).blue()
        );

        let tariff_json = if let Some(path) = tariff_file_path.as_ref() {
            load_object_file(path, ObjectKind::Tariff)?
        } else if std::io::stdin().is_terminal() {
            return Err(Error::TariffRequired);
        } else {
            load_object_from_stdin(ObjectKind::Tariff)?
        };

        let tariff_doc = json::parse_object(&tariff_json)?;
        let (tariff, warnings) = tariff::build(tariff_doc, ocpi_version).into_parts();

        print::warning_set("tariff validation", &warnings);

        if pretty {
            eprintln!("{}", style("# Source Tariff:").green());
            eprintln!("{}\n", json::write::Pretty::from_doc(tariff.as_doc()));
        }

        // Raise an error when a hard parse failure leaves us unable to explain the tariff. The
        // accompanying warnings pinpoint the offending field, so print them before the error.
        let (explanation, warnings) = match tariff::explain(&tariff) {
            Ok(caveat) => caveat.into_parts(),
            Err(error_set) => {
                let (error, warnings) = error_set.into_parts();
                print::warning_set("the tariff explanation", &warnings);
                return Err(Error::Explain(error));
            }
        };

        eprintln!("Completed {} tariff\n", style("explaining").green().bold());

        print::warning_set("the tariff explanation", &warnings);

        // The explanation itself goes to `stdout` so it can be piped or redirected on its own.
        println!("{explanation}");

        Ok(())
    }
}