1use std::fmt::{self, Write as _};
4
5use console::{measure_text_width, style};
6use ocpi_tariffs::{
7 json,
8 price::{self, TariffReport},
9 timezone, warning, Warning,
10};
11use tracing::error;
12
13use crate::ObjectKind;
14
15const LINE: &str = "----------------------------------------------------------------";
17const TABLE_BUF_LEN: usize = 4096;
19
20pub struct Optional<T>(pub Option<T>)
22where
23 T: fmt::Display;
24
25impl<T> fmt::Display for Optional<T>
26where
27 T: fmt::Display,
28{
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match &self.0 {
31 Some(v) => fmt::Display::fmt(v, f),
32 None => f.write_str("-"),
33 }
34 }
35}
36
37pub fn timezone_error(error: &warning::Error<timezone::Warning>) {
39 eprintln!(
40 "{}: Unable to find timezone due to error at path `{}`: {}",
41 style("ERR").red(),
42 error.element().path(),
43 error.warning()
44 );
45}
46
47pub fn timezone_warnings(warnings: &warning::Set<timezone::Warning>) {
49 if warnings.is_empty() {
50 return;
51 }
52
53 eprintln!(
54 "{}: {} warnings from the timezone search",
55 style("WARN").yellow(),
56 warnings.len_warnings(),
57 );
58
59 warning_set(warnings);
60}
61
62pub fn warning_set<W: Warning>(warnings: &warning::Set<W>) {
64 if warnings.is_empty() {
65 return;
66 }
67
68 eprintln!(
69 "{}: {} warnings from the timezone search",
70 style("WARN").yellow(),
71 warnings.len_warnings(),
72 );
73
74 for group in warnings {
75 let (element, warnings) = group.to_parts();
76 for warning in warnings {
77 eprintln!(
78 " - path: {}: {}",
79 style(&element.path()).green(),
80 style(warning).yellow()
81 );
82 }
83 }
84
85 let line = style(LINE).yellow();
86 eprintln!("{line}");
87}
88
89pub fn unexpected_fields(object: ObjectKind, unexpected_fields: &json::UnexpectedFields<'_>) {
91 if unexpected_fields.is_empty() {
92 return;
93 }
94
95 eprintln!(
96 "{}: {} Unknown fields found in the {}",
97 style("WARN").yellow(),
98 unexpected_fields.len(),
99 style(object).green()
100 );
101
102 for field_path in unexpected_fields {
103 eprintln!(" - {}", style(field_path).yellow());
104 }
105
106 let line = style(LINE).yellow();
107 eprintln!("{line}");
108}
109
110pub fn cdr_warnings(warnings: &warning::Set<price::Warning>) {
112 if warnings.is_empty() {
113 return;
114 }
115
116 eprintln!(
117 "{}: {} warnings for the CDR",
118 style("WARN").yellow(),
119 warnings.len_warnings(),
120 );
121
122 warning_set(warnings);
123}
124
125pub fn tariff_reports(reports: &[TariffReport]) {
127 if reports.iter().all(|report| report.warnings.is_empty()) {
128 return;
129 }
130
131 let line = style(LINE).yellow();
132
133 eprintln!("{}: warnings found in tariffs", style("WARN").yellow());
134
135 for report in reports {
136 let TariffReport { origin, warnings } = report;
137
138 if warnings.is_empty() {
139 continue;
140 }
141
142 eprintln!(
143 "{}: {} warnings from tariff with id: {}",
144 style("WARN").yellow(),
145 warnings.len(),
146 style(&origin.id).yellow(),
147 );
148
149 for (elem_path, warnings) in warnings {
150 eprintln!(" {}", style(elem_path).green());
151
152 for warning in warnings {
153 eprintln!(" - {}", style(warning).yellow());
154 }
155 }
156
157 eprintln!("{line}");
158 }
159
160 eprintln!("{line}");
161}
162
163pub struct Table {
165 widths: Vec<usize>,
167
168 buf: String,
170}
171
172pub struct Col<'caller> {
174 pub label: &'caller dyn fmt::Display,
176
177 pub width: usize,
179}
180
181impl Col<'_> {
182 pub fn empty(width: usize) -> Self {
184 Self { label: &"", width }
185 }
186}
187
188impl Table {
189 pub fn header(header: &[Col<'_>]) -> Self {
191 let widths = header
192 .iter()
193 .map(|Col { label: _, width }| *width)
194 .collect::<Vec<_>>();
195 let mut buf = String::with_capacity(TABLE_BUF_LEN);
196 let labels = header
197 .iter()
198 .map(|Col { label, width: _ }| *label)
199 .collect::<Vec<_>>();
200
201 print_table_line(&mut buf, &widths);
202 print_table_row(&mut buf, &widths, &labels);
203 print_table_line(&mut buf, &widths);
204
205 Self { widths, buf }
206 }
207
208 pub fn print_line(&mut self) {
210 print_table_line(&mut self.buf, &self.widths);
211 }
212
213 pub fn print_row(&mut self, values: &[&dyn fmt::Display]) {
215 print_table_row(&mut self.buf, &self.widths, values);
216 }
217
218 pub fn print_valid_row(
223 &mut self,
224 is_valid: bool,
225 label: &'static str,
226 values: &[&dyn fmt::Display],
227 ) {
228 let label = if is_valid {
229 style(label).green()
230 } else {
231 style(label).red()
232 };
233 print_table_row_with_label(&mut self.buf, &self.widths, &label, values);
234 }
235
236 pub fn finish(self) -> String {
238 let Self { widths, mut buf } = self;
239 print_table_line(&mut buf, &widths);
240 buf
241 }
242}
243
244#[macro_export]
251macro_rules! write_or {
252 ($dst:expr, $($arg:tt)*) => {{
253 let _ignore_result = $dst.write_fmt(std::format_args!($($arg)*));
254 }};
255}
256
257fn print_table_line(buf: &mut String, widths: &[usize]) {
259 write_or!(buf, "+");
260
261 for width in widths {
262 write_or!(
263 buf,
264 "{0:->1$}+",
265 "",
266 width.checked_add(2).unwrap_or_default()
267 );
268 }
269
270 write_or!(buf, "\n");
271}
272
273fn print_table_row(buf: &mut String, widths: &[usize], values: &[&dyn fmt::Display]) {
275 assert_eq!(
276 widths.len(),
277 values.len(),
278 "The widths and values amounts should be the same"
279 );
280 print_table_row_(buf, widths, values, None);
281}
282
283fn print_table_row_with_label(
287 buf: &mut String,
288 widths: &[usize],
289 label: &dyn fmt::Display,
290 values: &[&dyn fmt::Display],
291) {
292 print_table_row_(buf, widths, values, Some(label));
293}
294
295fn print_table_row_(
297 buf: &mut String,
298 widths: &[usize],
299 values: &[&dyn fmt::Display],
300 label: Option<&dyn fmt::Display>,
301) {
302 write_or!(buf, "|");
303
304 if let Some(label) = label {
305 let mut widths = widths.iter();
306 let Some(width) = widths.next() else {
307 return;
308 };
309 print_col(buf, label, *width);
310
311 for (value, width) in values.iter().zip(widths) {
312 print_col(buf, *value, *width);
313 }
314 } else {
315 for (value, width) in values.iter().zip(widths) {
316 print_col(buf, *value, *width);
317 }
318 }
319
320 write_or!(buf, "\n");
321}
322
323fn print_col(buf: &mut String, value: &dyn fmt::Display, width: usize) {
325 write_or!(buf, " ");
326
327 let len_before = buf.len();
330 write_or!(buf, "{value}");
331 let len_after = buf.len();
332
333 let Some(s) = &buf.get(len_before..len_after) else {
336 error!("Non UTF8 values were written as a column value");
337 return;
338 };
339
340 let len = measure_text_width(s);
341 let padding = width.saturating_sub(len);
343
344 for _ in 0..padding {
346 write_or!(buf, " ");
347 }
348
349 write_or!(buf, " |");
350}