1pub mod cdr;
15pub mod country;
16pub mod currency;
17mod datetime;
18mod de;
19mod duration;
20mod energy;
21pub mod guess;
22pub mod json;
23pub mod lint;
24mod money;
25mod number;
26pub mod price;
27pub mod tariff;
28pub mod timezone;
29mod types;
30mod v211;
31mod v221;
32pub mod warning;
33
34use std::{collections::BTreeSet, fmt};
35
36use serde::{Deserialize, Deserializer};
37
38use datetime::{Date, DateTime, Time};
39use duration::{HoursDecimal, SecondsRound};
40use energy::{Ampere, Kw, Kwh};
41use number::Number;
42use types::DayOfWeek;
43use warning::{IntoCaveat, IntoWarning};
44
45pub use money::{Money, Price, Vat};
46#[doc(inline)]
47pub use warning::{Caveat, Verdict, VerdictExt, Warning};
48
49pub type UnexpectedFields = BTreeSet<String>;
51
52pub type TariffId = String;
54
55#[derive(Clone, Copy, Debug, PartialEq)]
57pub enum Version {
58 V221,
59 V211,
60}
61
62impl Versioned for Version {
63 fn version(&self) -> Version {
64 *self
65 }
66}
67
68impl fmt::Display for Version {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 match self {
71 Version::V221 => f.write_str("v221"),
72 Version::V211 => f.write_str("v211"),
73 }
74 }
75}
76
77pub trait Versioned: fmt::Debug {
79 fn version(&self) -> Version;
81}
82
83pub trait Unversioned: fmt::Debug {
85 type Versioned: Versioned;
87
88 fn force_into_versioned(self, version: Version) -> Self::Versioned;
90}
91
92#[derive(Debug)]
93pub struct DeserializeError {
94 object: ObjectType,
96
97 err: Box<dyn std::error::Error + 'static>,
99}
100
101impl std::error::Error for DeserializeError {
102 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
103 Some(&*self.err)
104 }
105}
106
107impl fmt::Display for DeserializeError {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 write!(f, "{}", self.err)
110 }
111}
112
113impl DeserializeError {
114 fn from_err<E>(object: ObjectType, err: E) -> Self
116 where
117 E: std::error::Error + 'static,
118 {
119 Self {
120 object,
121 err: err.into(),
122 }
123 }
124
125 fn from_cdr_err<E>(err: E) -> Self
127 where
128 E: std::error::Error + 'static,
129 {
130 Self::from_err(ObjectType::Cdr, err)
131 }
132
133 fn from_tariff_err<E>(err: E) -> Self
135 where
136 E: std::error::Error + 'static,
137 {
138 Self::from_err(ObjectType::Tariff, err)
139 }
140
141 pub fn into_parts(self) -> (ObjectType, Box<dyn std::error::Error + 'static>) {
143 (self.object, self.err)
144 }
145}
146
147#[derive(Copy, Clone, Debug, Eq, PartialEq)]
149pub enum ObjectType {
150 Cdr,
154
155 Tariff,
159}
160
161fn null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
162where
163 T: Default + Deserialize<'de>,
164 D: Deserializer<'de>,
165{
166 let opt = Option::deserialize(deserializer)?;
167 Ok(opt.unwrap_or_default())
168}
169
170#[cfg(test)]
171mod test {
172 use std::{env, fmt, io::IsTerminal as _, path::Path, sync::Once};
173
174 use serde::{
175 de::{value::StrDeserializer, IntoDeserializer as _},
176 Deserialize,
177 };
178 use tracing::debug;
179 use tracing_subscriber::util::SubscriberInitExt as _;
180
181 use crate::{json, DateTime};
182
183 #[track_caller]
185 pub fn setup() {
186 static INITIALIZED: Once = Once::new();
187
188 INITIALIZED.call_once_force(|state| {
189 if state.is_poisoned() {
190 return;
191 }
192
193 let is_tty = std::io::stderr().is_terminal();
194
195 let level = match env::var("RUST_LOG") {
196 Ok(s) => s.parse().unwrap_or(tracing::Level::INFO),
197 Err(err) => match err {
198 env::VarError::NotPresent => tracing::Level::INFO,
199 env::VarError::NotUnicode(_) => {
200 panic!("`RUST_LOG` is not unicode");
201 }
202 },
203 };
204
205 let subscriber = tracing_subscriber::fmt()
206 .with_ansi(is_tty)
207 .with_file(true)
208 .with_level(false)
209 .with_line_number(true)
210 .with_max_level(level)
211 .with_target(false)
212 .with_test_writer()
213 .without_time()
214 .finish();
215
216 subscriber
217 .try_init()
218 .expect("Init tracing_subscriber::Subscriber");
219 });
220 }
221
222 #[track_caller]
223 pub fn assert_no_unexpected_fields(unexpected_fields: &json::UnexpectedFields<'_>) {
224 if !unexpected_fields.is_empty() {
225 const MAX_FIELD_DISPLAY: usize = 20;
226
227 if unexpected_fields.len() > MAX_FIELD_DISPLAY {
228 let truncated_fields = unexpected_fields
229 .iter()
230 .take(MAX_FIELD_DISPLAY)
231 .map(|path| path.to_string())
232 .collect::<Vec<_>>();
233
234 panic!(
235 "Unexpected fields found({}); displaying the first ({}): \n{}\n... and {} more",
236 unexpected_fields.len(),
237 truncated_fields.len(),
238 truncated_fields.join(",\n"),
239 unexpected_fields.len() - truncated_fields.len(),
240 )
241 } else {
242 panic!(
243 "Unexpected fields found({}):\n{}",
244 unexpected_fields.len(),
245 unexpected_fields.to_strings().join(",\n")
246 )
247 };
248 }
249 }
250
251 #[track_caller]
252 pub fn assert_unexpected_fields(
253 unexpected_fields: &json::UnexpectedFields<'_>,
254 expected: &[&'static str],
255 ) {
256 if unexpected_fields.len() != expected.len() {
257 let unexpected_fields = unexpected_fields
258 .into_iter()
259 .map(|path| path.to_string())
260 .collect::<Vec<_>>();
261
262 panic!(
263 "The unexpected fields and expected fields lists have different lengths.\n\nUnexpected fields found:\n{}",
264 unexpected_fields.join(",\n")
265 );
266 }
267
268 let unmatched_paths = unexpected_fields
269 .into_iter()
270 .zip(expected.iter())
271 .filter(|(a, b)| a != *b)
272 .collect::<Vec<_>>();
273
274 if !unmatched_paths.is_empty() {
275 let unmatched_paths = unmatched_paths
276 .into_iter()
277 .map(|(a, b)| format!("{a} != {b}"))
278 .collect::<Vec<_>>();
279
280 panic!(
281 "The unexpected fields don't match the expected fields.\n\nUnexpected fields found:\n{}",
282 unmatched_paths.join(",\n")
283 );
284 }
285 }
286
287 #[derive(Debug, Default)]
291 pub enum Expectation<T> {
292 Present(ExpectValue<T>),
294
295 #[default]
297 Absent,
298 }
299
300 #[derive(Debug)]
302 pub enum ExpectValue<T> {
303 Some(T),
305
306 Null,
308 }
309
310 impl<T> ExpectValue<T>
311 where
312 T: fmt::Debug,
313 {
314 pub fn into_option(self) -> Option<T> {
316 match self {
317 Self::Some(v) => Some(v),
318 Self::Null => None,
319 }
320 }
321
322 #[track_caller]
328 pub fn expect_value(self) -> T {
329 match self {
330 ExpectValue::Some(v) => v,
331 ExpectValue::Null => panic!("the field expects a value"),
332 }
333 }
334 }
335
336 impl<'de, T> Deserialize<'de> for Expectation<T>
337 where
338 T: Deserialize<'de>,
339 {
340 #[expect(clippy::unwrap_in_result, reason = "This is test util code")]
341 #[track_caller]
342 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
343 where
344 D: serde::Deserializer<'de>,
345 {
346 let value = serde_json::Value::deserialize(deserializer)?;
347
348 if value.is_null() {
349 return Ok(Expectation::Present(ExpectValue::Null));
350 }
351
352 let v = T::deserialize(value).unwrap();
353 Ok(Expectation::Present(ExpectValue::Some(v)))
354 }
355 }
356
357 #[track_caller]
359 pub fn datetime_from_str(s: &str) -> DateTime {
360 let de: StrDeserializer<'_, serde::de::value::Error> = s.into_deserializer();
361 DateTime::deserialize(de).unwrap()
362 }
363
364 #[track_caller]
369 pub fn read_expect_json(json_file_path: &Path, feature: &str) -> Option<String> {
370 let json_dir = json_file_path
371 .parent()
372 .expect("The given file should live in a dir");
373
374 let json_file_name = json_file_path
375 .file_stem()
376 .expect("The `json_file_path` should be a file")
377 .to_str()
378 .expect("The `json_file_path` should have a valid name");
379
380 let expect_file_name = format!("output_{feature}__{json_file_name}.json");
383
384 debug!("Try to read expectation file: `{expect_file_name}`");
385
386 let s = std::fs::read_to_string(json_dir.join(&expect_file_name))
387 .ok()
388 .map(|mut s| {
389 json_strip_comments::strip(&mut s).ok();
390 s
391 });
392
393 debug!("Successfully Read expectation file: `{expect_file_name}`");
394 s
395 }
396}
397
398#[cfg(test)]
399mod test_rust_decimal_arbitrary_precision {
400 use rust_decimal_macros::dec;
401
402 use crate::Number;
403
404 #[test]
405 fn should_serialize_decimal_with_12_fraction_digits() {
406 let dec = dec!(0.123456789012);
407 let actual = serde_json::to_string(&dec).unwrap();
408 assert_eq!(actual, r#""0.123456789012""#.to_owned());
409 }
410
411 #[test]
412 fn should_serialize_decimal_with_8_fraction_digits() {
413 let dec = dec!(37.12345678);
414 let actual = serde_json::to_string(&dec).unwrap();
415 assert_eq!(actual, r#""37.12345678""#.to_owned());
416 }
417
418 #[test]
419 fn should_serialize_0_decimal_without_fraction_digits() {
420 let dec = dec!(0);
421 let actual = serde_json::to_string(&dec).unwrap();
422 assert_eq!(actual, r#""0""#.to_owned());
423 }
424
425 #[test]
426 fn should_serialize_12_num_with_4_fraction_digits() {
427 let num: Number = dec!(0.1234).try_into().unwrap();
428 let actual = serde_json::to_string(&num).unwrap();
429 assert_eq!(actual, r#""0.1234""#.to_owned());
430 }
431
432 #[test]
433 fn should_serialize_8_num_with_4_fraction_digits() {
434 let num: Number = dec!(37.1234).try_into().unwrap();
435 let actual = serde_json::to_string(&num).unwrap();
436 assert_eq!(actual, r#""37.1234""#.to_owned());
437 }
438
439 #[test]
440 fn should_serialize_0_num_without_fraction_digits() {
441 let num: Number = dec!(0).try_into().unwrap();
442 let actual = serde_json::to_string(&num).unwrap();
443 assert_eq!(actual, r#""0""#.to_owned());
444 }
445}