1pub mod cdr;
14pub mod country;
15pub mod currency;
16pub mod datetime;
17pub mod duration;
18mod energy;
19pub mod guess;
20pub mod json;
21pub mod lint;
22pub mod money;
23pub mod number;
24pub mod price;
25pub mod string;
26pub mod tariff;
27pub mod timezone;
28mod v211;
29mod v221;
30pub mod warning;
31pub mod weekday;
32
33use std::{collections::BTreeSet, fmt};
34
35use duration::SecondsRound;
36use serde::{Deserialize, Deserializer};
37use warning::IntoCaveat;
38use weekday::Weekday;
39
40#[doc(inline)]
41pub use datetime::{Date, DateTime, Time};
42#[doc(inline)]
43pub use duration::HoursDecimal;
44#[doc(inline)]
45pub use energy::{Ampere, Kw, Kwh};
46#[doc(inline)]
47pub use money::{Cost, Money, Price, Vat, VatApplicable};
48#[doc(inline)]
49pub use number::Number;
50#[doc(inline)]
51pub use string::CiString;
52#[doc(inline)]
53pub use warning::{Caveat, Verdict, VerdictExt, Warning};
54
55pub type UnexpectedFields = BTreeSet<String>;
57
58pub type TariffId = String;
60
61#[derive(Clone, Copy, Debug, PartialEq)]
63pub enum Version {
64 V221,
65 V211,
66}
67
68impl Versioned for Version {
69 fn version(&self) -> Version {
70 *self
71 }
72}
73
74impl fmt::Display for Version {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 match self {
77 Version::V221 => f.write_str("v221"),
78 Version::V211 => f.write_str("v211"),
79 }
80 }
81}
82
83pub trait Versioned: fmt::Debug {
85 fn version(&self) -> Version;
87}
88
89pub trait Unversioned: fmt::Debug {
91 type Versioned: Versioned;
93
94 fn force_into_versioned(self, version: Version) -> Self::Versioned;
101}
102
103#[derive(Clone, Copy, Hash, PartialEq, Eq)]
105pub struct OutOfRange(());
106
107impl std::error::Error for OutOfRange {}
108
109impl OutOfRange {
110 const fn new() -> OutOfRange {
111 OutOfRange(())
112 }
113}
114
115impl fmt::Display for OutOfRange {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 write!(f, "out of range")
118 }
119}
120
121impl fmt::Debug for OutOfRange {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 write!(f, "out of range")
124 }
125}
126
127pub struct ParseError {
129 object: ObjectType,
131
132 kind: ParseErrorKind,
134}
135
136pub enum ParseErrorKind {
138 Erased(Box<dyn std::error::Error + Send + Sync + 'static>),
140
141 Json(json::Error),
143
144 ShouldBeAnObject,
146}
147
148impl std::error::Error for ParseError {
149 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
150 match &self.kind {
151 ParseErrorKind::Erased(err) => Some(&**err),
152 ParseErrorKind::Json(err) => Some(err),
153 ParseErrorKind::ShouldBeAnObject => None,
154 }
155 }
156}
157
158impl fmt::Debug for ParseError {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 fmt::Display::fmt(self, f)
161 }
162}
163
164impl fmt::Display for ParseError {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 write!(f, "while deserializing {:?}: ", self.object)?;
167
168 match &self.kind {
169 ParseErrorKind::Erased(err) => write!(f, "{err}"),
170 ParseErrorKind::Json(err) => write!(f, "{err}"),
171 ParseErrorKind::ShouldBeAnObject => f.write_str("The root element should be an object"),
172 }
173 }
174}
175
176impl ParseError {
177 fn from_cdr_err(err: json::Error) -> Self {
179 Self {
180 object: ObjectType::Tariff,
181 kind: ParseErrorKind::Json(err),
182 }
183 }
184
185 fn from_tariff_err(err: json::Error) -> Self {
187 Self {
188 object: ObjectType::Tariff,
189 kind: ParseErrorKind::Json(err),
190 }
191 }
192
193 fn from_cdr_serde_err(err: serde_json::Error) -> Self {
195 Self {
196 object: ObjectType::Cdr,
197 kind: ParseErrorKind::Erased(err.into()),
198 }
199 }
200
201 fn from_tariff_serde_err(err: serde_json::Error) -> Self {
203 Self {
204 object: ObjectType::Tariff,
205 kind: ParseErrorKind::Erased(err.into()),
206 }
207 }
208
209 fn cdr_should_be_object() -> ParseError {
210 Self {
211 object: ObjectType::Cdr,
212 kind: ParseErrorKind::ShouldBeAnObject,
213 }
214 }
215
216 fn tariff_should_be_object() -> ParseError {
217 Self {
218 object: ObjectType::Tariff,
219 kind: ParseErrorKind::ShouldBeAnObject,
220 }
221 }
222
223 pub fn into_parts(self) -> (ObjectType, ParseErrorKind) {
225 (self.object, self.kind)
226 }
227}
228
229#[derive(Copy, Clone, Debug, Eq, PartialEq)]
231pub enum ObjectType {
232 Cdr,
236
237 Tariff,
241}
242
243fn null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
244where
245 T: Default + Deserialize<'de>,
246 D: Deserializer<'de>,
247{
248 let opt = Option::deserialize(deserializer)?;
249 Ok(opt.unwrap_or_default())
250}
251
252#[cfg(test)]
253mod test {
254 #![allow(
255 clippy::unwrap_in_result,
256 reason = "unwraps are allowed anywhere in tests"
257 )]
258
259 use std::{env, fmt, io::IsTerminal as _, path::Path, sync::Once};
260
261 use rust_decimal::Decimal;
262 use serde::{
263 de::{value::StrDeserializer, IntoDeserializer as _},
264 Deserialize,
265 };
266 use tracing::debug;
267 use tracing_subscriber::util::SubscriberInitExt as _;
268
269 use crate::{json, DateTime};
270
271 #[track_caller]
273 pub fn setup() {
274 static INITIALIZED: Once = Once::new();
275
276 INITIALIZED.call_once_force(|state| {
277 if state.is_poisoned() {
278 return;
279 }
280
281 let is_tty = std::io::stderr().is_terminal();
282
283 let level = match env::var("RUST_LOG") {
284 Ok(s) => s.parse().unwrap_or(tracing::Level::INFO),
285 Err(err) => match err {
286 env::VarError::NotPresent => tracing::Level::INFO,
287 env::VarError::NotUnicode(_) => {
288 panic!("`RUST_LOG` is not unicode");
289 }
290 },
291 };
292
293 let subscriber = tracing_subscriber::fmt()
294 .with_ansi(is_tty)
295 .with_file(true)
296 .with_level(false)
297 .with_line_number(true)
298 .with_max_level(level)
299 .with_target(false)
300 .with_test_writer()
301 .without_time()
302 .finish();
303
304 subscriber
305 .try_init()
306 .expect("Init tracing_subscriber::Subscriber");
307 });
308 }
309
310 pub trait AsDecimal {
312 fn as_dec(&self) -> &Decimal;
313 }
314
315 pub trait DecimalPartialEq<Rhs = Self> {
317 #[must_use]
318 fn eq_dec(&self, other: &Rhs) -> bool;
319 }
320
321 #[track_caller]
322 pub fn assert_no_unexpected_fields(unexpected_fields: &json::UnexpectedFields<'_>) {
323 if !unexpected_fields.is_empty() {
324 const MAX_FIELD_DISPLAY: usize = 20;
325
326 if unexpected_fields.len() > MAX_FIELD_DISPLAY {
327 let truncated_fields = unexpected_fields
328 .iter()
329 .take(MAX_FIELD_DISPLAY)
330 .map(|path| path.to_string())
331 .collect::<Vec<_>>();
332
333 panic!(
334 "Unexpected fields found({}); displaying the first ({}): \n{}\n... and {} more",
335 unexpected_fields.len(),
336 truncated_fields.len(),
337 truncated_fields.join(",\n"),
338 unexpected_fields.len() - truncated_fields.len(),
339 )
340 } else {
341 panic!(
342 "Unexpected fields found({}):\n{}",
343 unexpected_fields.len(),
344 unexpected_fields.to_strings().join(",\n")
345 )
346 };
347 }
348 }
349
350 #[track_caller]
351 pub fn assert_unexpected_fields(
352 unexpected_fields: &json::UnexpectedFields<'_>,
353 expected: &[&'static str],
354 ) {
355 if unexpected_fields.len() != expected.len() {
356 let unexpected_fields = unexpected_fields
357 .into_iter()
358 .map(|path| path.to_string())
359 .collect::<Vec<_>>();
360
361 panic!(
362 "The unexpected fields and expected fields lists have different lengths.\n\nUnexpected fields found:\n{}",
363 unexpected_fields.join(",\n")
364 );
365 }
366
367 let unmatched_paths = unexpected_fields
368 .into_iter()
369 .zip(expected.iter())
370 .filter(|(a, b)| a != *b)
371 .collect::<Vec<_>>();
372
373 if !unmatched_paths.is_empty() {
374 let unmatched_paths = unmatched_paths
375 .into_iter()
376 .map(|(a, b)| format!("{a} != {b}"))
377 .collect::<Vec<_>>();
378
379 panic!(
380 "The unexpected fields don't match the expected fields.\n\nUnexpected fields found:\n{}",
381 unmatched_paths.join(",\n")
382 );
383 }
384 }
385
386 #[derive(Debug, Default)]
390 pub enum Expectation<T> {
391 Present(ExpectValue<T>),
393
394 #[default]
396 Absent,
397 }
398
399 #[derive(Debug)]
401 pub enum ExpectValue<T> {
402 Some(T),
404
405 Null,
407 }
408
409 impl<T> ExpectValue<T>
410 where
411 T: fmt::Debug,
412 {
413 pub fn into_option(self) -> Option<T> {
415 match self {
416 Self::Some(v) => Some(v),
417 Self::Null => None,
418 }
419 }
420
421 #[track_caller]
427 pub fn expect_value(self) -> T {
428 match self {
429 ExpectValue::Some(v) => v,
430 ExpectValue::Null => panic!("the field expects a value"),
431 }
432 }
433 }
434
435 impl<'de, T> Deserialize<'de> for Expectation<T>
436 where
437 T: Deserialize<'de>,
438 {
439 #[expect(clippy::unwrap_in_result, reason = "This is test util code")]
440 #[track_caller]
441 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
442 where
443 D: serde::Deserializer<'de>,
444 {
445 let value = serde_json::Value::deserialize(deserializer)?;
446
447 if value.is_null() {
448 return Ok(Expectation::Present(ExpectValue::Null));
449 }
450
451 let v = T::deserialize(value).unwrap();
452 Ok(Expectation::Present(ExpectValue::Some(v)))
453 }
454 }
455
456 #[track_caller]
458 pub fn datetime_from_str(s: &str) -> DateTime {
459 let de: StrDeserializer<'_, serde::de::value::Error> = s.into_deserializer();
460 DateTime::deserialize(de).unwrap()
461 }
462
463 #[track_caller]
468 pub fn read_expect_json(json_file_path: &Path, feature: &str) -> Option<String> {
469 let json_dir = json_file_path
470 .parent()
471 .expect("The given file should live in a dir");
472
473 let json_file_name = json_file_path
474 .file_stem()
475 .expect("The `json_file_path` should be a file")
476 .to_str()
477 .expect("The `json_file_path` should have a valid name");
478
479 let expect_file_name = format!("output_{feature}__{json_file_name}.json");
482
483 debug!("Try to read expectation file: `{expect_file_name}`");
484
485 let s = std::fs::read_to_string(json_dir.join(&expect_file_name))
486 .ok()
487 .map(|mut s| {
488 json_strip_comments::strip(&mut s).ok();
489 s
490 });
491
492 debug!("Successfully Read expectation file: `{expect_file_name}`");
493 s
494 }
495}
496
497#[cfg(test)]
498mod test_rust_decimal_arbitrary_precision {
499 use rust_decimal_macros::dec;
500
501 use crate::Number;
502
503 #[test]
504 fn should_serialize_decimal_with_12_fraction_digits() {
505 let dec = dec!(0.123456789012);
506 let actual = serde_json::to_string(&dec).unwrap();
507 assert_eq!(actual, r#""0.123456789012""#.to_owned());
508 }
509
510 #[test]
511 fn should_serialize_decimal_with_8_fraction_digits() {
512 let dec = dec!(37.12345678);
513 let actual = serde_json::to_string(&dec).unwrap();
514 assert_eq!(actual, r#""37.12345678""#.to_owned());
515 }
516
517 #[test]
518 fn should_serialize_0_decimal_without_fraction_digits() {
519 let dec = dec!(0);
520 let actual = serde_json::to_string(&dec).unwrap();
521 assert_eq!(actual, r#""0""#.to_owned());
522 }
523
524 #[test]
525 fn should_serialize_12_num_with_4_fraction_digits() {
526 let num: Number = dec!(0.1234).try_into().unwrap();
527 let actual = serde_json::to_string(&num).unwrap();
528 assert_eq!(actual, r#""0.1234""#.to_owned());
529 }
530
531 #[test]
532 fn should_serialize_8_num_with_4_fraction_digits() {
533 let num: Number = dec!(37.1234).try_into().unwrap();
534 let actual = serde_json::to_string(&num).unwrap();
535 assert_eq!(actual, r#""37.1234""#.to_owned());
536 }
537
538 #[test]
539 fn should_serialize_0_num_without_fraction_digits() {
540 let num: Number = dec!(0).try_into().unwrap();
541 let actual = serde_json::to_string(&num).unwrap();
542 assert_eq!(actual, r#""0""#.to_owned());
543 }
544}