1use std::fmt::{self, Write as _};
2
3use console::{measure_text_width, style};
4use ocpi_tariffs::{
5 cdr, json,
6 price::{self, TariffReport},
7 timezone, warning,
8};
9use tracing::error;
10
11use crate::ObjectKind;
12
13const LINE: &str = "----------------------------------------------------------------";
15const TABLE_BUF_LEN: usize = 4096;
17
18pub struct Optional<T>(pub Option<T>)
20where
21 T: fmt::Display;
22
23impl<T> fmt::Display for Optional<T>
24where
25 T: fmt::Display,
26{
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match &self.0 {
29 Some(v) => fmt::Display::fmt(v, f),
30 None => f.write_str("-"),
31 }
32 }
33}
34
35pub fn timezone_warnings(
37 cdr: &cdr::Versioned<'_>,
38 warnings: &warning::Report<timezone::WarningKind>,
39) {
40 if warnings.is_empty() {
41 return;
42 }
43
44 eprintln!(
45 "{}: {} warnings from the timezone search",
46 style("WARN").yellow(),
47 warnings.len(),
48 );
49
50 for warning::ElementReport { element, warnings } in warnings.iter(cdr.as_element()) {
51 for warning in warnings {
52 eprintln!(
53 " - path: {}: {}",
54 style(element.path()).green(),
55 style(warning).yellow()
56 );
57 }
58 }
59
60 let line = style(LINE).yellow();
61 eprintln!("{line}");
62}
63
64pub fn unexpected_fields(object: ObjectKind, unexpected_fields: &json::UnexpectedFields<'_>) {
66 if unexpected_fields.is_empty() {
67 return;
68 }
69
70 eprintln!(
71 "{}: {} Unknown fields found in the {}",
72 style("WARN").yellow(),
73 unexpected_fields.len(),
74 style(object).green()
75 );
76
77 for field_path in unexpected_fields {
78 eprintln!(" - {}", style(field_path).yellow());
79 }
80
81 let line = style(LINE).yellow();
82 eprintln!("{line}");
83}
84
85pub fn cdr_warnings(warnings: Vec<price::WarningReport>) {
87 if warnings.is_empty() {
88 return;
89 }
90
91 eprintln!(
92 "{}: {} Warnings generated while pricing the CDR",
93 style("WARN").yellow(),
94 warnings.len(),
95 );
96
97 for price::WarningReport { kind } in warnings {
98 eprintln!(" - {}", style(kind).green());
99 }
100
101 let line = style(LINE).yellow();
102 eprintln!("{line}");
103}
104
105pub fn tariff_reports(reports: Vec<TariffReport>) {
107 if reports
108 .iter()
109 .all(|report| report.unexpected_fields.is_empty())
110 {
111 return;
112 }
113
114 let line = style(LINE).yellow();
115
116 eprintln!(
117 "{}: Unexpected fields found in the tariffs",
118 style("WARN").yellow(),
119 );
120
121 for report in reports {
122 let TariffReport {
123 origin: reference,
124 unexpected_fields,
125 } = report;
126
127 if unexpected_fields.is_empty() {
128 continue;
129 }
130
131 eprintln!(
132 "{}: {} Unexpected fields found in the tariff with id: {}",
133 style("WARN").yellow(),
134 unexpected_fields.len(),
135 style(reference.id).yellow(),
136 );
137
138 for field_name in unexpected_fields {
139 eprintln!(" - {}", style(field_name).yellow());
140 }
141
142 eprintln!("{line}");
143 }
144
145 eprintln!("{line}");
146}
147
148pub struct Table {
150 widths: Vec<usize>,
152 buf: String,
154}
155
156pub struct Col<'a> {
158 pub label: &'a dyn fmt::Display,
159 pub width: usize,
160}
161
162impl Col<'_> {
163 pub fn empty(width: usize) -> Self {
164 Self { label: &"", width }
165 }
166}
167
168impl Table {
169 pub fn header(header: &[Col<'_>]) -> Self {
171 let widths = header
172 .iter()
173 .map(|Col { label: _, width }| *width)
174 .collect::<Vec<_>>();
175 let mut buf = String::with_capacity(TABLE_BUF_LEN);
176 let labels = header
177 .iter()
178 .map(|Col { label, width: _ }| *label)
179 .collect::<Vec<_>>();
180
181 print_table_line(&mut buf, &widths);
182 print_table_row(&mut buf, &widths, &labels);
183 print_table_line(&mut buf, &widths);
184
185 Self { widths, buf }
186 }
187
188 pub fn print_line(&mut self) {
190 print_table_line(&mut self.buf, &self.widths);
191 }
192
193 pub fn print_row(&mut self, values: &[&dyn fmt::Display]) {
195 print_table_row(&mut self.buf, &self.widths, values);
196 }
197
198 pub fn print_valid_row(
203 &mut self,
204 is_valid: bool,
205 label: &'static str,
206 values: &[&dyn fmt::Display],
207 ) {
208 let label = if is_valid {
209 style(label).green()
210 } else {
211 style(label).red()
212 };
213 print_table_row_with_label(&mut self.buf, &self.widths, &label, values);
214 }
215
216 pub fn finish(self) -> String {
218 let Self { widths, mut buf } = self;
219 print_table_line(&mut buf, &widths);
220 buf
221 }
222}
223
224#[macro_export]
231macro_rules! write_or {
232 ($dst:expr, $($arg:tt)*) => {{
233 let _ignore_result = $dst.write_fmt(std::format_args!($($arg)*));
234 }};
235}
236
237fn print_table_line(buf: &mut String, widths: &[usize]) {
239 write_or!(buf, "+");
240
241 for width in widths {
242 write_or!(buf, "{0:->1$}+", "", width + 2);
243 }
244
245 write_or!(buf, "\n");
246}
247
248fn print_table_row(buf: &mut String, widths: &[usize], values: &[&dyn fmt::Display]) {
250 assert_eq!(
251 widths.len(),
252 values.len(),
253 "The widths and values amounts should be the same"
254 );
255 print_table_row_(buf, widths, values, None);
256}
257
258fn print_table_row_with_label(
262 buf: &mut String,
263 widths: &[usize],
264 label: &dyn fmt::Display,
265 values: &[&dyn fmt::Display],
266) {
267 print_table_row_(buf, widths, values, Some(label));
268}
269
270fn print_table_row_(
272 buf: &mut String,
273 widths: &[usize],
274 values: &[&dyn fmt::Display],
275 label: Option<&dyn fmt::Display>,
276) {
277 write_or!(buf, "|");
278
279 if let Some(label) = label {
280 let mut widths = widths.iter();
281 let Some(width) = widths.next() else {
282 return;
283 };
284 print_col(buf, label, *width);
285
286 for (value, width) in values.iter().zip(widths) {
287 print_col(buf, *value, *width);
288 }
289 } else {
290 for (value, width) in values.iter().zip(widths) {
291 print_col(buf, *value, *width);
292 }
293 }
294
295 write_or!(buf, "\n");
296}
297
298fn print_col(buf: &mut String, value: &dyn fmt::Display, width: usize) {
300 write_or!(buf, " ");
301
302 let len_before = buf.len();
305 write_or!(buf, "{value}");
306 let len_after = buf.len();
307
308 let Some(s) = &buf.get(len_before..len_after) else {
311 error!("Non UTF8 values were written as a column value");
312 return;
313 };
314
315 let len = measure_text_width(s);
316 let padding = width.saturating_sub(len);
318
319 for _ in 0..padding {
321 write_or!(buf, " ");
322 }
323
324 write_or!(buf, " |");
325}