Skip to main content

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