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