1use std::fmt::{self, Write as _};
2
3use console::{measure_text_width, style};
4use ocpi_tariffs::{cdr, json, 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 unknown_cdr_fields(unknown_fields: UnexpectedFields) {
66 if unknown_fields.is_empty() {
67 return;
68 }
69
70 eprintln!(
71 "{}: {} Unknown fields found in the CDR",
72 style("WARN").yellow(),
73 unknown_fields.len(),
74 );
75
76 for field_name in unknown_fields {
77 eprintln!(" - {}", style(field_name).yellow());
78 }
79
80 let line = style(LINE).yellow();
81 eprintln!("{line}");
82}
83
84pub fn unknown_tariff_fields(unknown_fields: Vec<(String, UnexpectedFields)>) {
86 if unknown_fields
87 .iter()
88 .all(|(_, unknown_fields)| unknown_fields.is_empty())
89 {
90 return;
91 }
92
93 let line = style(LINE).yellow();
94
95 eprintln!(
96 "{}: Unknown fields found in the tariffs",
97 style("WARN").yellow(),
98 );
99
100 for (tariff_id, unknown_fields) in unknown_fields {
101 if unknown_fields.is_empty() {
102 continue;
103 }
104
105 eprintln!(
106 "{}: {} Unknown fields found in the tariff with id: {}",
107 style("WARN").yellow(),
108 unknown_fields.len(),
109 style(tariff_id).yellow(),
110 );
111
112 for field_name in unknown_fields {
113 eprintln!(" - {}", style(field_name).yellow());
114 }
115
116 eprintln!("{line}");
117 }
118
119 eprintln!("{line}");
120}
121
122pub struct Table {
124 widths: Vec<usize>,
126 buf: String,
128}
129
130pub struct Col<'a> {
132 pub label: &'a dyn fmt::Display,
133 pub width: usize,
134}
135
136impl Col<'_> {
137 pub fn empty(width: usize) -> Self {
138 Self { label: &"", width }
139 }
140}
141
142impl Table {
143 pub fn header(header: &[Col<'_>]) -> Self {
145 let widths = header
146 .iter()
147 .map(|Col { label: _, width }| *width)
148 .collect::<Vec<_>>();
149 let mut buf = String::with_capacity(TABLE_BUF_LEN);
150 let labels = header
151 .iter()
152 .map(|Col { label, width: _ }| *label)
153 .collect::<Vec<_>>();
154
155 print_table_line(&mut buf, &widths);
156 print_table_row(&mut buf, &widths, &labels);
157 print_table_line(&mut buf, &widths);
158
159 Self { widths, buf }
160 }
161
162 pub fn print_line(&mut self) {
164 print_table_line(&mut self.buf, &self.widths);
165 }
166
167 pub fn print_row(&mut self, values: &[&dyn fmt::Display]) {
169 print_table_row(&mut self.buf, &self.widths, values);
170 }
171
172 pub fn print_valid_row(
177 &mut self,
178 is_valid: bool,
179 label: &'static str,
180 values: &[&dyn fmt::Display],
181 ) {
182 let label = if is_valid {
183 style(label).green()
184 } else {
185 style(label).red()
186 };
187 print_table_row_with_label(&mut self.buf, &self.widths, &label, values);
188 }
189
190 pub fn finish(self) -> String {
192 let Self { widths, mut buf } = self;
193 print_table_line(&mut buf, &widths);
194 buf
195 }
196}
197
198#[macro_export]
205macro_rules! write_or {
206 ($dst:expr, $($arg:tt)*) => {{
207 let _ignore_result = $dst.write_fmt(std::format_args!($($arg)*));
208 }};
209}
210
211fn print_table_line(buf: &mut String, widths: &[usize]) {
213 write_or!(buf, "+");
214
215 for width in widths {
216 write_or!(buf, "{0:->1$}+", "", width + 2);
217 }
218
219 write_or!(buf, "\n");
220}
221
222fn print_table_row(buf: &mut String, widths: &[usize], values: &[&dyn fmt::Display]) {
224 assert_eq!(
225 widths.len(),
226 values.len(),
227 "The widths and values amounts should be the same"
228 );
229 print_table_row_(buf, widths, values, None);
230}
231
232fn print_table_row_with_label(
236 buf: &mut String,
237 widths: &[usize],
238 label: &dyn fmt::Display,
239 values: &[&dyn fmt::Display],
240) {
241 print_table_row_(buf, widths, values, Some(label));
242}
243
244fn print_table_row_(
246 buf: &mut String,
247 widths: &[usize],
248 values: &[&dyn fmt::Display],
249 label: Option<&dyn fmt::Display>,
250) {
251 write_or!(buf, "|");
252
253 if let Some(label) = label {
254 let mut widths = widths.iter();
255 let Some(width) = widths.next() else {
256 return;
257 };
258 print_col(buf, label, *width);
259
260 for (value, width) in values.iter().zip(widths) {
261 print_col(buf, *value, *width);
262 }
263 } else {
264 for (value, width) in values.iter().zip(widths) {
265 print_col(buf, *value, *width);
266 }
267 }
268
269 write_or!(buf, "\n");
270}
271
272fn print_col(buf: &mut String, value: &dyn fmt::Display, width: usize) {
274 write_or!(buf, " ");
275
276 let len_before = buf.len();
279 write_or!(buf, "{value}");
280 let len_after = buf.len();
281
282 let Some(s) = &buf.get(len_before..len_after) else {
285 error!("Non UTF8 values were written as a column value");
286 return;
287 };
288
289 let len = measure_text_width(s);
290 let padding = width.saturating_sub(len);
292
293 for _ in 0..padding {
295 write_or!(buf, " ");
296 }
297
298 write_or!(buf, " |");
299}