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 analyze;
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;
17use ocpi_tariffs::{price, timezone};
18
19#[doc(hidden)]
20#[derive(Parser)]
21pub struct Opts {
22    #[clap(subcommand)]
23    command: opts::Command,
24}
25
26impl Opts {
27    pub fn run(self) -> Result<(), Error> {
28        self.command.run()
29    }
30}
31
32#[doc(hidden)]
33#[derive(Debug)]
34pub enum Error {
35    /// The `CDR` or tariff path supplied is not a file.
36    PathNotFile {
37        path: PathBuf,
38    },
39    File {
40        path: PathBuf,
41        error: io::Error,
42    },
43    StdIn {
44        error: io::Error,
45    },
46
47    /// An Error happened when calling the `cdr::price` fn.
48    Price(price::Error),
49
50    /// An Error happened when calling the `timezone::find_or_infer` fn.
51    Timezone(timezone::Error),
52
53    /// The calculated totals deviate from the totals in the CDR.
54    TotalsDoNotMatch,
55
56    /// When the process is a TTY `--cdr` is required.
57    CdrRequired,
58}
59
60impl std::error::Error for Error {}
61
62impl fmt::Display for Error {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        match self {
65            Self::PathNotFile { path } => {
66                write!(f, "The path given is not a file: `{}`", path.display())
67            }
68            Self::File { path, error } => {
69                write!(f, "File error `{}`: {}", path.display(), error)
70            }
71            Self::StdIn { error } => {
72                write!(f, "Stdin error {error}")
73            }
74            Self::Price(err) => write!(f, "{err}"),
75            Self::Timezone(err) => write!(f, "{err}"),
76            Self::TotalsDoNotMatch => {
77                f.write_str("Calculation does not match all totals in the CDR")
78            }
79            Self::CdrRequired => f.write_str("`--cdr` is required when the process is a TTY"),
80        }
81    }
82}
83
84impl From<price::Error> for Error {
85    fn from(err: price::Error) -> Self {
86        Self::Price(err)
87    }
88}
89
90impl From<timezone::Error> for Error {
91    fn from(err: timezone::Error) -> Self {
92        Self::Timezone(err)
93    }
94}
95
96impl Error {
97    pub fn file(path: PathBuf, error: io::Error) -> Self {
98        Self::File { path, error }
99    }
100
101    pub fn stdin(error: io::Error) -> Self {
102        Self::StdIn { error }
103    }
104}
105
106#[doc(hidden)]
107pub fn setup_logging() -> Result<(), &'static str> {
108    let stderr = io::stderr();
109    let builder = tracing_subscriber::fmt()
110        .without_time()
111        .with_writer(io::stderr)
112        .with_ansi(stderr.is_terminal());
113
114    let level = match env::var("RUST_LOG") {
115        Ok(s) => s.parse().unwrap_or(tracing::Level::INFO),
116        Err(err) => match err {
117            env::VarError::NotPresent => tracing::Level::INFO,
118            env::VarError::NotUnicode(_) => {
119                return Err("RUST_LOG is not unicode");
120            }
121        },
122    };
123
124    builder.with_max_level(level).init();
125
126    Ok(())
127}