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