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 guess_version;
7mod lint;
8mod opts;
9mod print;
10mod validate;
11
12use std::{
13    env, fmt,
14    io::{self, IsTerminal as _},
15    path::PathBuf,
16};
17
18use clap::Parser;
19use ocpi_tariffs::{price, DeserializeError};
20
21#[doc(hidden)]
22#[derive(Parser)]
23pub struct Opts {
24    #[clap(subcommand)]
25    command: opts::Command,
26}
27
28impl Opts {
29    pub fn run(self) -> Result<(), Error> {
30        self.command.run()
31    }
32}
33
34#[derive(Copy, Clone, Debug, clap::ValueEnum)]
35pub enum ObjectKind {
36    Cdr,
37    Tariff,
38}
39
40#[doc(hidden)]
41#[derive(Debug)]
42pub enum Error {
43    /// When the process is a TTY `--cdr` is required.
44    CdrRequired,
45
46    /// A deserialize `Error` occurred.
47    Deserialize(DeserializeError),
48
49    /// Unable to open a file.
50    File { path: PathBuf, error: io::Error },
51
52    /// An internal error that is a bug.
53    Internal(Box<dyn std::error::Error>),
54
55    /// A timezone was provided by the user and it failed to parse
56    InvalidTimezone(chrono_tz::ParseError),
57
58    /// An Error happened when calling the `tariff::lint` fn.
59    Lint(ocpi_tariffs::lint::Error),
60
61    /// A timezone could not be found or inferred.
62    NoTimezone,
63
64    /// The `CDR` or tariff path supplied is not a file.
65    PathNotFile { path: PathBuf },
66
67    /// The `CDR` or tariff path supplied doesn't have a parent dir.
68    PathNoParentDir { path: PathBuf },
69
70    /// An Error happened when calling the `cdr::price` fn.
71    Price(price::Error),
72
73    /// An Error happened when performing I/O.
74    StdIn(io::Error),
75
76    /// When the process is a TTY `--tariff` is required.
77    TariffRequired,
78
79    /// The calculated totals deviate from the totals in the CDR.
80    TotalsDoNotMatch,
81}
82
83impl std::error::Error for Error {}
84
85impl fmt::Display for Error {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        match self {
88            Self::CdrRequired => f.write_str("`--cdr` is required when the process is a TTY"),
89            Self::Deserialize(err) => write!(f, "{err}"),
90            Self::File { path, error } => {
91                write!(f, "File error `{}`: {}", path.display(), error)
92            }
93            Self::Internal(err) => {
94                write!(f, "{err}")
95            }
96            Self::InvalidTimezone(err) => write!(f, "the timezone given is invalid; {err}"),
97            Self::Lint(err) => write!(f, "Lint error {err}"),
98            Self::NoTimezone => write!(f, "A timezone could not be found or inferred."),
99            Self::PathNotFile { path } => {
100                write!(f, "The path given is not a file: `{}`", path.display())
101            }
102            Self::PathNoParentDir { path } => {
103                write!(
104                    f,
105                    "The path given doesn't have a parent dir: `{}`",
106                    path.display()
107                )
108            }
109            Self::Price(err) => write!(f, "{err}"),
110            Self::StdIn(err) => {
111                write!(f, "Stdin error {err}")
112            }
113            Self::TariffRequired => f.write_str("`--tariff` is required when the process is a TTY"),
114            Self::TotalsDoNotMatch => {
115                f.write_str("Calculation does not match all totals in the CDR")
116            }
117        }
118    }
119}
120
121impl From<price::Error> for Error {
122    fn from(err: price::Error) -> Self {
123        Self::Price(err)
124    }
125}
126
127impl From<ocpi_tariffs::lint::Error> for Error {
128    fn from(err: ocpi_tariffs::lint::Error) -> Self {
129        Self::Lint(err)
130    }
131}
132
133impl From<DeserializeError> for Error {
134    fn from(err: DeserializeError) -> Self {
135        Self::Deserialize(err)
136    }
137}
138
139impl Error {
140    pub fn file(path: PathBuf, error: io::Error) -> Self {
141        Self::File { path, error }
142    }
143
144    pub fn stdin(err: io::Error) -> Self {
145        Self::StdIn(err)
146    }
147}
148
149#[doc(hidden)]
150pub fn setup_logging() -> Result<(), &'static str> {
151    let stderr = io::stderr();
152    let builder = tracing_subscriber::fmt()
153        .without_time()
154        .with_writer(io::stderr)
155        .with_ansi(stderr.is_terminal());
156
157    let level = match env::var("RUST_LOG") {
158        Ok(s) => s.parse().unwrap_or(tracing::Level::INFO),
159        Err(err) => match err {
160            env::VarError::NotPresent => tracing::Level::INFO,
161            env::VarError::NotUnicode(_) => {
162                return Err("RUST_LOG is not unicode");
163            }
164        },
165    };
166
167    builder.with_max_level(level).init();
168
169    Ok(())
170}