1use std::fmt::{self, Write as _};
2
3use console::{measure_text_width, style};
4use ocpi_tariffs::{
5 json,
6 price::{self, TariffReport},
7 timezone, warning, 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_error(error: &warning::Error<timezone::Warning>) {
37 eprintln!(
38 "{}: Unable to find timezone due to error at path `{}`: {}",
39 style("ERR").red(),
40 error.element().path(),
41 error.warning()
42 );
43}
44
45pub fn timezone_warnings(warnings: &warning::Set<timezone::Warning>) {
47 if warnings.is_empty() {
48 return;
49 }
50
51 eprintln!(
52 "{}: {} warnings from the timezone search",
53 style("WARN").yellow(),
54 warnings.len_warnings(),
55 );
56
57 warning_set(warnings);
58}
59
60pub fn warning_set<W: Warning>(warnings: &warning::Set<W>) {
62 if warnings.is_empty() {
63 return;
64 }
65
66 eprintln!(
67 "{}: {} warnings from the timezone search",
68 style("WARN").yellow(),
69 warnings.len_warnings(),
70 );
71
72 for group in warnings {
73 let (element, warnings) = group.to_parts();
74 for warning in warnings {
75 eprintln!(
76 " - path: {}: {}",
77 style(&element.path()).green(),
78 style(warning).yellow()
79 );
80 }
81 }
82
83 let line = style(LINE).yellow();
84 eprintln!("{line}");
85}
86
87pub fn unexpected_fields(object: ObjectKind, unexpected_fields: &json::UnexpectedFields<'_>) {
89 if unexpected_fields.is_empty() {
90 return;
91 }
92
93 eprintln!(
94 "{}: {} Unknown fields found in the {}",
95 style("WARN").yellow(),
96 unexpected_fields.len(),
97 style(object).green()
98 );
99
100 for field_path in unexpected_fields {
101 eprintln!(" - {}", style(field_path).yellow());
102 }
103
104 let line = style(LINE).yellow();
105 eprintln!("{line}");
106}
107
108pub fn cdr_warnings(warnings: &warning::Set<price::Warning>) {
110 if warnings.is_empty() {
111 return;
112 }
113
114 eprintln!(
115 "{}: {} warnings for the CDR",
116 style("WARN").yellow(),
117 warnings.len_warnings(),
118 );
119
120 warning_set(warnings);
121}
122
123pub fn tariff_reports(reports: &[TariffReport]) {
125 if reports.iter().all(|report| report.warnings.is_empty()) {
126 return;
127 }
128
129 let line = style(LINE).yellow();
130
131 eprintln!("{}: warnings found in tariffs", style("WARN").yellow(),);
132
133 for report in reports {
134 let TariffReport { origin, warnings } = report;
135
136 if warnings.is_empty() {
137 continue;
138 }
139
140 eprintln!(
141 "{}: {} warnings from tariff with id: {}",
142 style("WARN").yellow(),
143 warnings.len(),
144 style(&origin.id).yellow(),
145 );
146
147 for (elem_path, warnings) in warnings {
148 eprintln!(" {}", style(elem_path).green());
149
150 for warning in warnings {
151 eprintln!(" - {}", style(warning).yellow());
152 }
153 }
154
155 eprintln!("{line}");
156 }
157
158 eprintln!("{line}");
159}
160
161pub struct Table {
163 widths: Vec<usize>,
165 buf: String,
167}
168
169pub struct Col<'a> {
171 pub label: &'a dyn fmt::Display,
172 pub width: usize,
173}
174
175impl Col<'_> {
176 pub fn empty(width: usize) -> Self {
177 Self { label: &"", width }
178 }
179}
180
181impl Table {
182 pub fn header(header: &[Col<'_>]) -> Self {
184 let widths = header
185 .iter()
186 .map(|Col { label: _, width }| *width)
187 .collect::<Vec<_>>();
188 let mut buf = String::with_capacity(TABLE_BUF_LEN);
189 let labels = header
190 .iter()
191 .map(|Col { label, width: _ }| *label)
192 .collect::<Vec<_>>();
193
194 print_table_line(&mut buf, &widths);
195 print_table_row(&mut buf, &widths, &labels);
196 print_table_line(&mut buf, &widths);
197
198 Self { widths, buf }
199 }
200
201 pub fn print_line(&mut self) {
203 print_table_line(&mut self.buf, &self.widths);
204 }
205
206 pub fn print_row(&mut self, values: &[&dyn fmt::Display]) {
208 print_table_row(&mut self.buf, &self.widths, values);
209 }
210
211 pub fn print_valid_row(
216 &mut self,
217 is_valid: bool,
218 label: &'static str,
219 values: &[&dyn fmt::Display],
220 ) {
221 let label = if is_valid {
222 style(label).green()
223 } else {
224 style(label).red()
225 };
226 print_table_row_with_label(&mut self.buf, &self.widths, &label, values);
227 }
228
229 pub fn finish(self) -> String {
231 let Self { widths, mut buf } = self;
232 print_table_line(&mut buf, &widths);
233 buf
234 }
235}
236
237#[macro_export]
244macro_rules! write_or {
245 ($dst:expr, $($arg:tt)*) => {{
246 let _ignore_result = $dst.write_fmt(std::format_args!($($arg)*));
247 }};
248}
249
250fn print_table_line(buf: &mut String, widths: &[usize]) {
252 write_or!(buf, "+");
253
254 for width in widths {
255 write_or!(
256 buf,
257 "{0:->1$}+",
258 "",
259 width.checked_add(2).unwrap_or_default()
260 );
261 }
262
263 write_or!(buf, "\n");
264}
265
266fn print_table_row(buf: &mut String, widths: &[usize], values: &[&dyn fmt::Display]) {
268 assert_eq!(
269 widths.len(),
270 values.len(),
271 "The widths and values amounts should be the same"
272 );
273 print_table_row_(buf, widths, values, None);
274}
275
276fn print_table_row_with_label(
280 buf: &mut String,
281 widths: &[usize],
282 label: &dyn fmt::Display,
283 values: &[&dyn fmt::Display],
284) {
285 print_table_row_(buf, widths, values, Some(label));
286}
287
288fn print_table_row_(
290 buf: &mut String,
291 widths: &[usize],
292 values: &[&dyn fmt::Display],
293 label: Option<&dyn fmt::Display>,
294) {
295 write_or!(buf, "|");
296
297 if let Some(label) = label {
298 let mut widths = widths.iter();
299 let Some(width) = widths.next() else {
300 return;
301 };
302 print_col(buf, label, *width);
303
304 for (value, width) in values.iter().zip(widths) {
305 print_col(buf, *value, *width);
306 }
307 } else {
308 for (value, width) in values.iter().zip(widths) {
309 print_col(buf, *value, *width);
310 }
311 }
312
313 write_or!(buf, "\n");
314}
315
316fn print_col(buf: &mut String, value: &dyn fmt::Display, width: usize) {
318 write_or!(buf, " ");
319
320 let len_before = buf.len();
323 write_or!(buf, "{value}");
324 let len_after = buf.len();
325
326 let Some(s) = &buf.get(len_before..len_after) else {
329 error!("Non UTF8 values were written as a column value");
330 return;
331 };
332
333 let len = measure_text_width(s);
334 let padding = width.saturating_sub(len);
336
337 for _ in 0..padding {
339 write_or!(buf, " ");
340 }
341
342 write_or!(buf, " |");
343}