ocpi_tariffs_cli/
lib.rs

1#![allow(clippy::print_stderr, reason = "The CLI is allowed to use stderr")]
2#![allow(clippy::print_stdout, reason = "The CLI is allowed to use stdout")]
3#![doc = include_str!("../README.md")]
4
5mod analyzse;
6mod opts;
7mod print;
8mod validate;
9
10use std::{
11    env, fmt,
12    io::{self, IsTerminal as _},
13    path::PathBuf,
14};
15
16use clap::Parser;
17
18#[doc(hidden)]
19#[derive(Parser)]
20pub struct Opts {
21    #[clap(subcommand)]
22    command: opts::Command,
23}
24
25impl Opts {
26    pub fn run(self) -> Result<(), Error> {
27        self.command.run()
28    }
29}
30
31#[doc(hidden)]
32#[derive(Debug)]
33pub enum Error {
34    /// The `CDR` or tariff path supplied is not a file.
35    PathNotFile {
36        path: PathBuf,
37    },
38    File {
39        path: PathBuf,
40        error: io::Error,
41    },
42    StdIn {
43        error: io::Error,
44    },
45    Internal(ocpi_tariffs::Error),
46    /// The calculated totals deviate from the totals in the CDR.
47    TotalsDoNotMatch,
48
49    /// When the process is a TTY `--cdr` is required.
50    CdrRequired,
51}
52impl std::error::Error for Error {}
53
54impl fmt::Display for Error {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        match self {
57            Self::PathNotFile { path } => {
58                write!(f, "The path given is not a file: `{}`", path.display())
59            }
60            Self::File { path, error } => {
61                write!(f, "File error `{}`: {}", path.display(), error)
62            }
63            Self::StdIn { error } => {
64                write!(f, "Stdin error {error}")
65            }
66            Self::Internal(e) => write!(f, "{e}"),
67            Self::TotalsDoNotMatch => {
68                f.write_str("Calculation does not match all totals in the CDR")
69            }
70            Self::CdrRequired => f.write_str("`--cdr` is required when the process is a TTY"),
71        }
72    }
73}
74
75impl From<ocpi_tariffs::Error> for Error {
76    fn from(err: ocpi_tariffs::Error) -> Self {
77        Self::Internal(err)
78    }
79}
80
81impl Error {
82    pub fn file(path: PathBuf, error: io::Error) -> Self {
83        Self::File { path, error }
84    }
85
86    pub fn stdin(error: io::Error) -> Self {
87        Self::StdIn { error }
88    }
89}
90
91#[doc(hidden)]
92pub fn setup_logging() -> Result<(), &'static str> {
93    let stderr = io::stderr();
94    let builder = tracing_subscriber::fmt()
95        .without_time()
96        .with_writer(io::stderr)
97        .with_ansi(stderr.is_terminal());
98
99    let level = match env::var("RUST_LOG") {
100        Ok(s) => s.parse().unwrap_or(tracing::Level::INFO),
101        Err(err) => match err {
102            env::VarError::NotPresent => tracing::Level::INFO,
103            env::VarError::NotUnicode(_) => {
104                return Err("RUST_LOG is not unicode");
105            }
106        },
107    };
108
109    builder.with_max_level(level).init();
110
111    Ok(())
112}