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 warning::Group { element, warnings } in warnings {
73 for warning in warnings {
74 eprintln!(
75 " - path: {}: {}",
76 style(&element.path).green(),
77 style(warning).yellow()
78 );
79 }
80 }
81
82 let line = style(LINE).yellow();
83 eprintln!("{line}");
84}
85
86pub fn unexpected_fields(object: ObjectKind, unexpected_fields: &json::UnexpectedFields<'_>) {
88 if unexpected_fields.is_empty() {
89 return;
90 }
91
92 eprintln!(
93 "{}: {} Unknown fields found in the {}",
94 style("WARN").yellow(),
95 unexpected_fields.len(),
96 style(object).green()
97 );
98
99 for field_path in unexpected_fields {
100 eprintln!(" - {}", style(field_path).yellow());
101 }
102
103 let line = style(LINE).yellow();
104 eprintln!("{line}");
105}
106
107pub fn cdr_warnings(warnings: &warning::Set<price::Warning>) {
109 if warnings.is_empty() {
110 return;
111 }
112
113 eprintln!(
114 "{}: {} warnings for the CDR",
115 style("WARN").yellow(),
116 warnings.len_warnings(),
117 );
118
119 warning_set(warnings);
120}
121
122pub fn tariff_reports(reports: Vec<TariffReport>) {
124 if reports.iter().all(|report| report.warnings.is_empty()) {
125 return;
126 }
127
128 let line = style(LINE).yellow();
129
130 eprintln!("{}: warnings found in tariffs", style("WARN").yellow(),);
131
132 for report in reports {
133 let TariffReport { origin, warnings } = report;
134
135 if warnings.is_empty() {
136 continue;
137 }
138
139 eprintln!(
140 "{}: {} warnings from tariff with id: {}",
141 style("WARN").yellow(),
142 warnings.len(),
143 style(origin.id).yellow(),
144 );
145
146 for (elem_path, warnings) in warnings {
147 eprintln!(" {}", style(elem_path).green());
148
149 for warning in warnings {
150 eprintln!(" - {}", style(warning).yellow());
151 }
152 }
153
154 eprintln!("{line}");
155 }
156
157 eprintln!("{line}");
158}
159
160pub struct Table {
162 widths: Vec<usize>,
164 buf: String,
166}
167
168pub struct Col<'a> {
170 pub label: &'a dyn fmt::Display,
171 pub width: usize,
172}
173
174impl Col<'_> {
175 pub fn empty(width: usize) -> Self {
176 Self { label: &"", width }
177 }
178}
179
180impl Table {
181 pub fn header(header: &[Col<'_>]) -> Self {
183 let widths = header
184 .iter()
185 .map(|Col { label: _, width }| *width)
186 .collect::<Vec<_>>();
187 let mut buf = String::with_capacity(TABLE_BUF_LEN);
188 let labels = header
189 .iter()
190 .map(|Col { label, width: _ }| *label)
191 .collect::<Vec<_>>();
192
193 print_table_line(&mut buf, &widths);
194 print_table_row(&mut buf, &widths, &labels);
195 print_table_line(&mut buf, &widths);
196
197 Self { widths, buf }
198 }
199
200 pub fn print_line(&mut self) {
202 print_table_line(&mut self.buf, &self.widths);
203 }
204
205 pub fn print_row(&mut self, values: &[&dyn fmt::Display]) {
207 print_table_row(&mut self.buf, &self.widths, values);
208 }
209
210 pub fn print_valid_row(
215 &mut self,
216 is_valid: bool,
217 label: &'static str,
218 values: &[&dyn fmt::Display],
219 ) {
220 let label = if is_valid {
221 style(label).green()
222 } else {
223 style(label).red()
224 };
225 print_table_row_with_label(&mut self.buf, &self.widths, &label, values);
226 }
227
228 pub fn finish(self) -> String {
230 let Self { widths, mut buf } = self;
231 print_table_line(&mut buf, &widths);
232 buf
233 }
234}
235
236#[macro_export]
243macro_rules! write_or {
244 ($dst:expr, $($arg:tt)*) => {{
245 let _ignore_result = $dst.write_fmt(std::format_args!($($arg)*));
246 }};
247}
248
249fn print_table_line(buf: &mut String, widths: &[usize]) {
251 write_or!(buf, "+");
252
253 for width in widths {
254 write_or!(buf, "{0:->1$}+", "", width + 2);
255 }
256
257 write_or!(buf, "\n");
258}
259
260fn print_table_row(buf: &mut String, widths: &[usize], values: &[&dyn fmt::Display]) {
262 assert_eq!(
263 widths.len(),
264 values.len(),
265 "The widths and values amounts should be the same"
266 );
267 print_table_row_(buf, widths, values, None);
268}
269
270fn print_table_row_with_label(
274 buf: &mut String,
275 widths: &[usize],
276 label: &dyn fmt::Display,
277 values: &[&dyn fmt::Display],
278) {
279 print_table_row_(buf, widths, values, Some(label));
280}
281
282fn print_table_row_(
284 buf: &mut String,
285 widths: &[usize],
286 values: &[&dyn fmt::Display],
287 label: Option<&dyn fmt::Display>,
288) {
289 write_or!(buf, "|");
290
291 if let Some(label) = label {
292 let mut widths = widths.iter();
293 let Some(width) = widths.next() else {
294 return;
295 };
296 print_col(buf, label, *width);
297
298 for (value, width) in values.iter().zip(widths) {
299 print_col(buf, *value, *width);
300 }
301 } else {
302 for (value, width) in values.iter().zip(widths) {
303 print_col(buf, *value, *width);
304 }
305 }
306
307 write_or!(buf, "\n");
308}
309
310fn print_col(buf: &mut String, value: &dyn fmt::Display, width: usize) {
312 write_or!(buf, " ");
313
314 let len_before = buf.len();
317 write_or!(buf, "{value}");
318 let len_after = buf.len();
319
320 let Some(s) = &buf.get(len_before..len_after) else {
323 error!("Non UTF8 values were written as a column value");
324 return;
325 };
326
327 let len = measure_text_width(s);
328 let padding = width.saturating_sub(len);
330
331 for _ in 0..padding {
333 write_or!(buf, " ");
334 }
335
336 write_or!(buf, " |");
337}