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