1pub mod cdr;
138pub mod country;
139pub mod currency;
140pub mod datetime;
141pub mod duration;
142mod energy;
143pub mod guess;
144pub mod json;
145pub mod lint;
146pub mod money;
147pub mod number;
148pub mod price;
149pub mod string;
150pub mod tariff;
151pub mod timezone;
152mod v211;
153mod v221;
154pub mod warning;
155pub mod weekday;
156
157use std::{collections::BTreeSet, fmt};
158
159use serde::{Deserialize, Deserializer};
160
161#[expect(unused_imports, reason = "Soon to be used in `mod generator`")]
162use string::CiString;
163use warning::IntoCaveat;
164use weekday::Weekday;
165
166#[doc(inline)]
167pub use energy::{Ampere, Kw, Kwh};
168#[doc(inline)]
169pub use money::{Cost, Money, Price, Vat, VatApplicable};
170#[doc(inline)]
171pub use warning::{Caveat, Verdict, VerdictExt, Warning};
172
173pub type UnexpectedFields = BTreeSet<String>;
175
176pub type TariffId = String;
178
179#[derive(Clone, Copy, Debug, PartialEq)]
181pub enum Version {
182 V221,
183 V211,
184}
185
186impl Versioned for Version {
187 fn version(&self) -> Version {
188 *self
189 }
190}
191
192impl fmt::Display for Version {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 match self {
195 Version::V221 => f.write_str("v221"),
196 Version::V211 => f.write_str("v211"),
197 }
198 }
199}
200
201pub trait Versioned: fmt::Debug {
203 fn version(&self) -> Version;
205}
206
207pub trait Unversioned: fmt::Debug {
209 type Versioned: Versioned;
211
212 fn force_into_versioned(self, version: Version) -> Self::Versioned;
219}
220
221#[derive(Clone, Copy, Hash, PartialEq, Eq)]
223pub struct OutOfRange(());
224
225impl std::error::Error for OutOfRange {}
226
227impl OutOfRange {
228 const fn new() -> OutOfRange {
229 OutOfRange(())
230 }
231}
232
233impl fmt::Display for OutOfRange {
234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235 write!(f, "out of range")
236 }
237}
238
239impl fmt::Debug for OutOfRange {
240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241 write!(f, "out of range")
242 }
243}
244
245pub struct ParseError {
247 object: ObjectType,
249
250 kind: ParseErrorKind,
252}
253
254pub enum ParseErrorKind {
256 Erased(Box<dyn std::error::Error + Send + Sync + 'static>),
258
259 Json(json::Error),
261
262 ShouldBeAnObject,
264}
265
266impl std::error::Error for ParseError {
267 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
268 match &self.kind {
269 ParseErrorKind::Erased(err) => Some(&**err),
270 ParseErrorKind::Json(err) => Some(err),
271 ParseErrorKind::ShouldBeAnObject => None,
272 }
273 }
274}
275
276impl fmt::Debug for ParseError {
277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278 fmt::Display::fmt(self, f)
279 }
280}
281
282impl fmt::Display for ParseError {
283 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284 write!(f, "while deserializing {:?}: ", self.object)?;
285
286 match &self.kind {
287 ParseErrorKind::Erased(err) => write!(f, "{err}"),
288 ParseErrorKind::Json(err) => write!(f, "{err}"),
289 ParseErrorKind::ShouldBeAnObject => f.write_str("The root element should be an object"),
290 }
291 }
292}
293
294impl ParseError {
295 fn from_cdr_err(err: json::Error) -> Self {
297 Self {
298 object: ObjectType::Tariff,
299 kind: ParseErrorKind::Json(err),
300 }
301 }
302
303 fn from_tariff_err(err: json::Error) -> Self {
305 Self {
306 object: ObjectType::Tariff,
307 kind: ParseErrorKind::Json(err),
308 }
309 }
310
311 fn from_cdr_serde_err(err: serde_json::Error) -> Self {
313 Self {
314 object: ObjectType::Cdr,
315 kind: ParseErrorKind::Erased(err.into()),
316 }
317 }
318
319 fn from_tariff_serde_err(err: serde_json::Error) -> Self {
321 Self {
322 object: ObjectType::Tariff,
323 kind: ParseErrorKind::Erased(err.into()),
324 }
325 }
326
327 fn cdr_should_be_object() -> ParseError {
328 Self {
329 object: ObjectType::Cdr,
330 kind: ParseErrorKind::ShouldBeAnObject,
331 }
332 }
333
334 fn tariff_should_be_object() -> ParseError {
335 Self {
336 object: ObjectType::Tariff,
337 kind: ParseErrorKind::ShouldBeAnObject,
338 }
339 }
340
341 pub fn into_parts(self) -> (ObjectType, ParseErrorKind) {
343 (self.object, self.kind)
344 }
345}
346
347#[derive(Copy, Clone, Debug, Eq, PartialEq)]
349pub enum ObjectType {
350 Cdr,
354
355 Tariff,
359}
360
361pub(crate) trait SaturatingAdd {
365 #[must_use]
367 fn saturating_add(self, other: Self) -> Self;
368}
369
370pub(crate) trait SaturatingSub {
374 #[must_use]
376 fn saturating_sub(self, other: Self) -> Self;
377}
378
379fn null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
380where
381 T: Default + Deserialize<'de>,
382 D: Deserializer<'de>,
383{
384 let opt = Option::deserialize(deserializer)?;
385 Ok(opt.unwrap_or_default())
386}
387
388#[cfg(test)]
389mod test {
390 #![allow(
391 clippy::unwrap_in_result,
392 reason = "unwraps are allowed anywhere in tests"
393 )]
394
395 use std::{env, fmt, io::IsTerminal as _, path::Path, sync::Once};
396
397 use chrono::{DateTime, Utc};
398 use rust_decimal::Decimal;
399 use serde::{
400 de::{value::StrDeserializer, IntoDeserializer as _},
401 Deserialize,
402 };
403 use tracing::debug;
404 use tracing_subscriber::util::SubscriberInitExt as _;
405
406 use crate::{datetime, json, number};
407
408 #[track_caller]
410 pub fn setup() {
411 static INITIALIZED: Once = Once::new();
412
413 INITIALIZED.call_once_force(|state| {
414 if state.is_poisoned() {
415 return;
416 }
417
418 let is_tty = std::io::stderr().is_terminal();
419
420 let level = match env::var("RUST_LOG") {
421 Ok(s) => s.parse().unwrap_or(tracing::Level::INFO),
422 Err(err) => match err {
423 env::VarError::NotPresent => tracing::Level::INFO,
424 env::VarError::NotUnicode(_) => {
425 panic!("`RUST_LOG` is not unicode");
426 }
427 },
428 };
429
430 let subscriber = tracing_subscriber::fmt()
431 .with_ansi(is_tty)
432 .with_file(true)
433 .with_level(false)
434 .with_line_number(true)
435 .with_max_level(level)
436 .with_target(false)
437 .with_test_writer()
438 .without_time()
439 .finish();
440
441 subscriber
442 .try_init()
443 .expect("Init tracing_subscriber::Subscriber");
444 });
445 }
446
447 pub trait ApproxEq<Rhs = Self> {
457 #[must_use]
458 fn approx_eq(&self, other: &Rhs) -> bool;
459 }
460
461 impl<T> ApproxEq for Option<T>
462 where
463 T: ApproxEq,
464 {
465 fn approx_eq(&self, other: &Self) -> bool {
466 match (self, other) {
467 (Some(a), Some(b)) => a.approx_eq(b),
468 (None, None) => true,
469 _ => false,
470 }
471 }
472 }
473
474 pub fn approx_eq_dec(a: Decimal, mut b: Decimal, tolerance: Decimal, precision: u32) -> bool {
476 let a = a.round_dp(precision);
477 b.rescale(number::SCALE);
478 let b = b.round_dp(precision);
479 (a - b).abs() <= tolerance
480 }
481
482 #[track_caller]
483 pub fn assert_no_unexpected_fields(unexpected_fields: &json::UnexpectedFields<'_>) {
484 if !unexpected_fields.is_empty() {
485 const MAX_FIELD_DISPLAY: usize = 20;
486
487 if unexpected_fields.len() > MAX_FIELD_DISPLAY {
488 let truncated_fields = unexpected_fields
489 .iter()
490 .take(MAX_FIELD_DISPLAY)
491 .map(|path| path.to_string())
492 .collect::<Vec<_>>();
493
494 panic!(
495 "Unexpected fields found({}); displaying the first ({}): \n{}\n... and {} more",
496 unexpected_fields.len(),
497 truncated_fields.len(),
498 truncated_fields.join(",\n"),
499 unexpected_fields.len() - truncated_fields.len(),
500 )
501 } else {
502 panic!(
503 "Unexpected fields found({}):\n{}",
504 unexpected_fields.len(),
505 unexpected_fields.to_strings().join(",\n")
506 )
507 };
508 }
509 }
510
511 #[track_caller]
512 pub fn assert_unexpected_fields(
513 unexpected_fields: &json::UnexpectedFields<'_>,
514 expected: &[&'static str],
515 ) {
516 if unexpected_fields.len() != expected.len() {
517 let unexpected_fields = unexpected_fields
518 .into_iter()
519 .map(|path| path.to_string())
520 .collect::<Vec<_>>();
521
522 panic!(
523 "The unexpected fields and expected fields lists have different lengths.\n\nUnexpected fields found:\n{}",
524 unexpected_fields.join(",\n")
525 );
526 }
527
528 let unmatched_paths = unexpected_fields
529 .into_iter()
530 .zip(expected.iter())
531 .filter(|(a, b)| a != *b)
532 .collect::<Vec<_>>();
533
534 if !unmatched_paths.is_empty() {
535 let unmatched_paths = unmatched_paths
536 .into_iter()
537 .map(|(a, b)| format!("{a} != {b}"))
538 .collect::<Vec<_>>();
539
540 panic!(
541 "The unexpected fields don't match the expected fields.\n\nUnexpected fields found:\n{}",
542 unmatched_paths.join(",\n")
543 );
544 }
545 }
546
547 #[derive(Debug, Default)]
551 pub enum Expectation<T> {
552 Present(ExpectValue<T>),
554
555 #[default]
557 Absent,
558 }
559
560 #[derive(Debug)]
562 pub enum ExpectValue<T> {
563 Some(T),
565
566 Null,
568 }
569
570 impl<T> ExpectValue<T>
571 where
572 T: fmt::Debug,
573 {
574 pub fn into_option(self) -> Option<T> {
576 match self {
577 Self::Some(v) => Some(v),
578 Self::Null => None,
579 }
580 }
581
582 #[track_caller]
588 pub fn expect_value(self) -> T {
589 match self {
590 ExpectValue::Some(v) => v,
591 ExpectValue::Null => panic!("the field expects a value"),
592 }
593 }
594 }
595
596 impl<'de, T> Deserialize<'de> for Expectation<T>
597 where
598 T: Deserialize<'de>,
599 {
600 #[expect(clippy::unwrap_in_result, reason = "This is test util code")]
601 #[track_caller]
602 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
603 where
604 D: serde::Deserializer<'de>,
605 {
606 let value = serde_json::Value::deserialize(deserializer)?;
607
608 if value.is_null() {
609 return Ok(Expectation::Present(ExpectValue::Null));
610 }
611
612 let v = T::deserialize(value).unwrap();
613 Ok(Expectation::Present(ExpectValue::Some(v)))
614 }
615 }
616
617 #[track_caller]
619 pub fn datetime_from_str(s: &str) -> DateTime<Utc> {
620 let de: StrDeserializer<'_, serde::de::value::Error> = s.into_deserializer();
621 datetime::deser_to_utc(de).unwrap()
622 }
623
624 #[track_caller]
629 pub fn read_expect_json(json_file_path: &Path, feature: &str) -> Option<String> {
630 let json_dir = json_file_path
631 .parent()
632 .expect("The given file should live in a dir");
633
634 let json_file_name = json_file_path
635 .file_stem()
636 .expect("The `json_file_path` should be a file")
637 .to_str()
638 .expect("The `json_file_path` should have a valid name");
639
640 let expect_file_name = format!("output_{feature}__{json_file_name}.json");
643
644 debug!("Try to read expectation file: `{expect_file_name}`");
645
646 let s = std::fs::read_to_string(json_dir.join(&expect_file_name))
647 .ok()
648 .map(|mut s| {
649 json_strip_comments::strip(&mut s).ok();
650 s
651 });
652
653 debug!("Successfully Read expectation file: `{expect_file_name}`");
654 s
655 }
656
657 #[track_caller]
658 pub fn assert_approx_eq_failed(
659 left: &dyn fmt::Debug,
660 right: &dyn fmt::Debug,
661 args: Option<fmt::Arguments<'_>>,
662 ) -> ! {
663 match args {
664 Some(args) => panic!(
665 "assertion `left == right` failed: {args}
666left: {left:?}
667right: {right:?}"
668 ),
669 None => panic!(
670 "assertion `left == right` failed
671left: {left:?}
672right: {right:?}"
673 ),
674 }
675 }
676
677 #[macro_export]
679 macro_rules! assert_approx_eq {
680 ($left:expr, $right:expr $(,)?) => ({
681 use $crate::test::ApproxEq;
682
683 match (&$left, &$right) {
684 (left_val, right_val) => {
685 if !((*left_val).approx_eq(&*right_val)) {
686 $crate::test::assert_approx_eq_failed(
690 &*left_val,
691 &*right_val,
692 std::option::Option::None
693 );
694 }
695 }
696 }
697 });
698 ($left:expr, $right:expr, $($arg:tt)+) => ({
699 use $crate::test::ApproxEq;
700
701 match (&$left, &$right) {
702 (left_val, right_val) => {
703 if !((*left_val).approx_eq(&*right_val)) {
704 $crate::test::assert_approx_eq_failed(
708 &*left_val,
709 &*right_val,
710 std::option::Option::Some(std::format_args!($($arg)+))
711 );
712 }
713 }
714 }
715 });
716 }
717}
718
719#[cfg(test)]
720mod test_rust_decimal_arbitrary_precision {
721 use rust_decimal_macros::dec;
722
723 #[test]
724 fn should_serialize_decimal_with_12_fraction_digits() {
725 let dec = dec!(0.123456789012);
726 let actual = serde_json::to_string(&dec).unwrap();
727 assert_eq!(actual, r#""0.123456789012""#.to_owned());
728 }
729
730 #[test]
731 fn should_serialize_decimal_with_8_fraction_digits() {
732 let dec = dec!(37.12345678);
733 let actual = serde_json::to_string(&dec).unwrap();
734 assert_eq!(actual, r#""37.12345678""#.to_owned());
735 }
736
737 #[test]
738 fn should_serialize_0_decimal_without_fraction_digits() {
739 let dec = dec!(0);
740 let actual = serde_json::to_string(&dec).unwrap();
741 assert_eq!(actual, r#""0""#.to_owned());
742 }
743
744 #[test]
745 fn should_serialize_12_num_with_4_fraction_digits() {
746 let num = dec!(0.1234);
747 let actual = serde_json::to_string(&num).unwrap();
748 assert_eq!(actual, r#""0.1234""#.to_owned());
749 }
750
751 #[test]
752 fn should_serialize_8_num_with_4_fraction_digits() {
753 let num = dec!(37.1234);
754 let actual = serde_json::to_string(&num).unwrap();
755 assert_eq!(actual, r#""37.1234""#.to_owned());
756 }
757
758 #[test]
759 fn should_serialize_0_num_without_fraction_digits() {
760 let num = dec!(0);
761 let actual = serde_json::to_string(&num).unwrap();
762 assert_eq!(actual, r#""0""#.to_owned());
763 }
764}