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(cdr: &cdr::Versioned<'_>, warnings: &warning::Set<timezone::WarningKind>) {
37 if warnings.is_empty() {
38 return;
39 }
40
41 eprintln!(
42 "{}: {} warnings from the timezone search",
43 style("WARN").yellow(),
44 warnings.len(),
45 );
46
47 for warning::Group { element, warnings } in warnings.group_by_elem(cdr.as_element()) {
48 for warning in warnings {
49 eprintln!(
50 " - path: {}: {}",
51 style(element.path()).green(),
52 style(warning).yellow()
53 );
54 }
55 }
56
57 let line = style(LINE).yellow();
58 eprintln!("{line}");
59}
60
61pub fn unexpected_fields(object: ObjectKind, unexpected_fields: &json::UnexpectedFields<'_>) {
63 if unexpected_fields.is_empty() {
64 return;
65 }
66
67 eprintln!(
68 "{}: {} Unknown fields found in the {}",
69 style("WARN").yellow(),
70 unexpected_fields.len(),
71 style(object).green()
72 );
73
74 for field_path in unexpected_fields {
75 eprintln!(" - {}", style(field_path).yellow());
76 }
77
78 let line = style(LINE).yellow();
79 eprintln!("{line}");
80}
81
82pub fn cdr_warnings(warnings: price::WarningMap) {
84 if warnings.is_empty() {
85 return;
86 }
87
88 eprintln!(
89 "{}: {} warnings for the CDR",
90 style("WARN").yellow(),
91 warnings.len(),
92 );
93
94 for (elem_path, warnings) in warnings {
95 eprintln!(" {}", style(elem_path).green());
96
97 for warning in warnings {
98 eprintln!(" - {}", style(warning).yellow());
99 }
100 }
101
102 let line = style(LINE).yellow();
103 eprintln!("{line}");
104}
105
106pub fn tariff_reports(reports: Vec<TariffReport>) {
108 if reports.iter().all(|report| report.warnings.is_empty()) {
109 return;
110 }
111
112 let line = style(LINE).yellow();
113
114 eprintln!("{}: warnings found in tariffs", style("WARN").yellow(),);
115
116 for report in reports {
117 let TariffReport { origin, warnings } = report;
118
119 if warnings.is_empty() {
120 continue;
121 }
122
123 eprintln!(
124 "{}: {} warnings from tariff with id: {}",
125 style("WARN").yellow(),
126 warnings.len(),
127 style(origin.id).yellow(),
128 );
129
130 for (elem_path, warnings) in warnings {
131 eprintln!(" {}", style(elem_path).green());
132
133 for warning in warnings {
134 eprintln!(" - {}", style(warning).yellow());
135 }
136 }
137
138 eprintln!("{line}");
139 }
140
141 eprintln!("{line}");
142}
143
144pub struct Table {
146 widths: Vec<usize>,
148 buf: String,
150}
151
152pub struct Col<'a> {
154 pub label: &'a dyn fmt::Display,
155 pub width: usize,
156}
157
158impl Col<'_> {
159 pub fn empty(width: usize) -> Self {
160 Self { label: &"", width }
161 }
162}
163
164impl Table {
165 pub fn header(header: &[Col<'_>]) -> Self {
167 let widths = header
168 .iter()
169 .map(|Col { label: _, width }| *width)
170 .collect::<Vec<_>>();
171 let mut buf = String::with_capacity(TABLE_BUF_LEN);
172 let labels = header
173 .iter()
174 .map(|Col { label, width: _ }| *label)
175 .collect::<Vec<_>>();
176
177 print_table_line(&mut buf, &widths);
178 print_table_row(&mut buf, &widths, &labels);
179 print_table_line(&mut buf, &widths);
180
181 Self { widths, buf }
182 }
183
184 pub fn print_line(&mut self) {
186 print_table_line(&mut self.buf, &self.widths);
187 }
188
189 pub fn print_row(&mut self, values: &[&dyn fmt::Display]) {
191 print_table_row(&mut self.buf, &self.widths, values);
192 }
193
194 pub fn print_valid_row(
199 &mut self,
200 is_valid: bool,
201 label: &'static str,
202 values: &[&dyn fmt::Display],
203 ) {
204 let label = if is_valid {
205 style(label).green()
206 } else {
207 style(label).red()
208 };
209 print_table_row_with_label(&mut self.buf, &self.widths, &label, values);
210 }
211
212 pub fn finish(self) -> String {
214 let Self { widths, mut buf } = self;
215 print_table_line(&mut buf, &widths);
216 buf
217 }
218}
219
220#[macro_export]
227macro_rules! write_or {
228 ($dst:expr, $($arg:tt)*) => {{
229 let _ignore_result = $dst.write_fmt(std::format_args!($($arg)*));
230 }};
231}
232
233fn print_table_line(buf: &mut String, widths: &[usize]) {
235 write_or!(buf, "+");
236
237 for width in widths {
238 write_or!(buf, "{0:->1$}+", "", width + 2);
239 }
240
241 write_or!(buf, "\n");
242}
243
244fn print_table_row(buf: &mut String, widths: &[usize], values: &[&dyn fmt::Display]) {
246 assert_eq!(
247 widths.len(),
248 values.len(),
249 "The widths and values amounts should be the same"
250 );
251 print_table_row_(buf, widths, values, None);
252}
253
254fn print_table_row_with_label(
258 buf: &mut String,
259 widths: &[usize],
260 label: &dyn fmt::Display,
261 values: &[&dyn fmt::Display],
262) {
263 print_table_row_(buf, widths, values, Some(label));
264}
265
266fn print_table_row_(
268 buf: &mut String,
269 widths: &[usize],
270 values: &[&dyn fmt::Display],
271 label: Option<&dyn fmt::Display>,
272) {
273 write_or!(buf, "|");
274
275 if let Some(label) = label {
276 let mut widths = widths.iter();
277 let Some(width) = widths.next() else {
278 return;
279 };
280 print_col(buf, label, *width);
281
282 for (value, width) in values.iter().zip(widths) {
283 print_col(buf, *value, *width);
284 }
285 } else {
286 for (value, width) in values.iter().zip(widths) {
287 print_col(buf, *value, *width);
288 }
289 }
290
291 write_or!(buf, "\n");
292}
293
294fn print_col(buf: &mut String, value: &dyn fmt::Display, width: usize) {
296 write_or!(buf, " ");
297
298 let len_before = buf.len();
301 write_or!(buf, "{value}");
302 let len_after = buf.len();
303
304 let Some(s) = &buf.get(len_before..len_after) else {
307 error!("Non UTF8 values were written as a column value");
308 return;
309 };
310
311 let len = measure_text_width(s);
312 let padding = width.saturating_sub(len);
314
315 for _ in 0..padding {
317 write_or!(buf, " ");
318 }
319
320 write_or!(buf, " |");
321}