ocpi-tariffs-cli 0.42.0

CLI application for OCPI tariff calculation
Documentation
use std::{
    fmt, fs,
    io::{self, Read as _},
    path::PathBuf,
};

use console::style;
use ocpi_tariffs::{cdr, guess, json, tariff};
use tracing::debug;

use crate::{opts::DEFAULT_STDIO_BUF_SIZE, print, Error, ObjectKind};

#[derive(clap::Parser)]
pub struct Command {
    #[command(flatten)]
    args: Args,
}

#[derive(clap::Args)]
pub struct Args {
    /// The type of OCPI object contained in the given JSON.
    #[arg(short = 't', long = "type")]
    kind: ObjectKind,

    /// A path to json file for the specified object.
    #[arg(short = 'f', long)]
    file: Option<PathBuf>,

    /// Check if all fields are allowed to be there according to the OCPI spec.
    #[arg(long)]
    validate: bool,
}

impl fmt::Display for ObjectKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ObjectKind::Cdr => f.write_str("CDR"),
            ObjectKind::Tariff => f.write_str("tariff"),
        }
    }
}

enum Outcome<'buf> {
    Version {
        object_kind: ObjectKind,

        version: guess::Version<ocpi_tariffs::Version, ()>,
    },
    Report {
        object_kind: ObjectKind,

        /// A list of fields that were not expected: The schema did not define them.
        ///
        /// This list will always be empty if the guessed `Version` is `Uncertain`.
        unexpected_fields: json::UnexpectedFields<'buf>,

        version: guess::Version<ocpi_tariffs::Version, ()>,
    },
}

impl Command {
    pub fn run(self) -> Result<(), Error> {
        let Self {
            args:
                Args {
                    kind: object_kind,
                    file: path,
                    validate,
                },
        } = self;

        let json = if let Some(path) = path {
            debug!("Loading {object_kind} from file");
            let json = fs::read_to_string(&*path).map_err(Error::then_file_io(path))?;
            debug!(bytes_read = json.len(), "{object_kind} read from file");
            json
        } else {
            debug!("Loading {object_kind} from stdin");
            let mut stdin = io::stdin().lock();
            let mut json = String::with_capacity(DEFAULT_STDIO_BUF_SIZE);
            let bytes_read = stdin.read_to_string(&mut json).map_err(Error::stdin)?;
            debug!(bytes_read, "{object_kind} read from stdin");
            json
        };

        let outcome = match object_kind {
            ObjectKind::Cdr => {
                if validate {
                    let report = cdr::parse_and_report(&json)?;
                    let guess::Report {
                        unexpected_fields,
                        version,
                    } = report;

                    Outcome::Report {
                        object_kind,
                        unexpected_fields,
                        version: version.into_version(),
                    }
                } else {
                    let version = cdr::parse(&json)?;
                    Outcome::Version {
                        object_kind,
                        version: version.into_version(),
                    }
                }
            }
            ObjectKind::Tariff => {
                if validate {
                    let report = tariff::parse_and_report(&json)?;
                    let guess::Report {
                        unexpected_fields,
                        version,
                    } = report;

                    Outcome::Report {
                        object_kind,
                        unexpected_fields,
                        version: version.into_version(),
                    }
                } else {
                    let version = tariff::parse(&json)?;
                    Outcome::Version {
                        object_kind,
                        version: version.into_version(),
                    }
                }
            }
        };

        match outcome {
            Outcome::Version {
                object_kind,
                version,
            } => print_version(object_kind, &version),
            Outcome::Report {
                object_kind,
                unexpected_fields,
                version,
            } => {
                print::unexpected_fields(object_kind, &unexpected_fields);
                print_version(object_kind, &version);
            }
        }

        Ok(())
    }
}

fn print_version(object_kind: ObjectKind, version: &guess::Version<ocpi_tariffs::Version, ()>) {
    match version {
        guess::Version::Uncertain(()) => {
            eprintln!(
                "Unable to guess the version of the given {} JSON",
                style(object_kind).green()
            );
        }
        guess::Version::Certain(version) => {
            println!("{version}");
        }
    }
}