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
36pub(crate) use datetime::DateTime;
37use datetime::{Date, Time};
38use duration::{HoursDecimal, SecondsRound};
39use energy::{Ampere, Kw, Kwh};
40pub(crate) use number::Number;
41use serde::{Deserialize, Deserializer};
42use types::DayOfWeek;
43use warning::IntoCaveat;
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(Clone, Copy, Hash, PartialEq, Eq)]
94pub struct OutOfRange(());
95
96impl std::error::Error for OutOfRange {}
97
98impl OutOfRange {
99 const fn new() -> OutOfRange {
100 OutOfRange(())
101 }
102}
103
104impl fmt::Display for OutOfRange {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 write!(f, "out of range")
107 }
108}
109
110impl fmt::Debug for OutOfRange {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 write!(f, "out of range")
113 }
114}
115
116pub struct ParseError {
118 object: ObjectType,
120
121 kind: ParseErrorKind,
123}
124
125pub enum ParseErrorKind {
127 Erased(Box<dyn std::error::Error + Send + Sync + 'static>),
129
130 Json(json::Error),
132
133 ShouldBeAnObject,
135}
136
137impl std::error::Error for ParseError {
138 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
139 match &self.kind {
140 ParseErrorKind::Erased(err) => Some(&**err),
141 ParseErrorKind::Json(err) => Some(err),
142 ParseErrorKind::ShouldBeAnObject => None,
143 }
144 }
145}
146
147impl fmt::Debug for ParseError {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 fmt::Display::fmt(self, f)
150 }
151}
152
153impl fmt::Display for ParseError {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 write!(f, "while deserializing {:?}: ", self.object)?;
156
157 match &self.kind {
158 ParseErrorKind::Erased(err) => write!(f, "{err}"),
159 ParseErrorKind::Json(err) => write!(f, "{err}"),
160 ParseErrorKind::ShouldBeAnObject => f.write_str("The root element should be an object"),
161 }
162 }
163}
164
165impl ParseError {
166 fn from_cdr_err(err: json::Error) -> Self {
168 Self {
169 object: ObjectType::Tariff,
170 kind: ParseErrorKind::Json(err),
171 }
172 }
173
174 fn from_tariff_err(err: json::Error) -> Self {
176 Self {
177 object: ObjectType::Tariff,
178 kind: ParseErrorKind::Json(err),
179 }
180 }
181
182 fn from_cdr_serde_err(err: serde_json::Error) -> Self {
184 Self {
185 object: ObjectType::Cdr,
186 kind: ParseErrorKind::Erased(err.into()),
187 }
188 }
189
190 fn from_tariff_serde_err(err: serde_json::Error) -> Self {
192 Self {
193 object: ObjectType::Tariff,
194 kind: ParseErrorKind::Erased(err.into()),
195 }
196 }
197
198 fn cdr_should_be_object() -> ParseError {
199 Self {
200 object: ObjectType::Cdr,
201 kind: ParseErrorKind::ShouldBeAnObject,
202 }
203 }
204
205 fn tariff_should_be_object() -> ParseError {
206 Self {
207 object: ObjectType::Tariff,
208 kind: ParseErrorKind::ShouldBeAnObject,
209 }
210 }
211
212 pub fn into_parts(self) -> (ObjectType, ParseErrorKind) {
214 (self.object, self.kind)
215 }
216}
217
218#[derive(Copy, Clone, Debug, Eq, PartialEq)]
220pub enum ObjectType {
221 Cdr,
225
226 Tariff,
230}
231
232fn null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
233where
234 T: Default + Deserialize<'de>,
235 D: Deserializer<'de>,
236{
237 let opt = Option::deserialize(deserializer)?;
238 Ok(opt.unwrap_or_default())
239}
240
241#[cfg(test)]
242mod test {
243 use std::{env, fmt, io::IsTerminal as _, path::Path, sync::Once};
244
245 use rust_decimal::Decimal;
246 use serde::{
247 de::{value::StrDeserializer, IntoDeserializer as _},
248 Deserialize,
249 };
250 use tracing::debug;
251 use tracing_subscriber::util::SubscriberInitExt as _;
252
253 use crate::{json, DateTime};
254
255 #[track_caller]
257 pub fn setup() {
258 static INITIALIZED: Once = Once::new();
259
260 INITIALIZED.call_once_force(|state| {
261 if state.is_poisoned() {
262 return;
263 }
264
265 let is_tty = std::io::stderr().is_terminal();
266
267 let level = match env::var("RUST_LOG") {
268 Ok(s) => s.parse().unwrap_or(tracing::Level::INFO),
269 Err(err) => match err {
270 env::VarError::NotPresent => tracing::Level::INFO,
271 env::VarError::NotUnicode(_) => {
272 panic!("`RUST_LOG` is not unicode");
273 }
274 },
275 };
276
277 let subscriber = tracing_subscriber::fmt()
278 .with_ansi(is_tty)
279 .with_file(true)
280 .with_level(false)
281 .with_line_number(true)
282 .with_max_level(level)
283 .with_target(false)
284 .with_test_writer()
285 .without_time()
286 .finish();
287
288 subscriber
289 .try_init()
290 .expect("Init tracing_subscriber::Subscriber");
291 });
292 }
293
294 pub trait AsDecimal {
296 fn as_dec(&self) -> &Decimal;
297 }
298
299 pub trait DecimalPartialEq<Rhs = Self> {
301 #[must_use]
302 fn eq_dec(&self, other: &Rhs) -> bool;
303 }
304
305 #[track_caller]
306 pub fn assert_no_unexpected_fields(unexpected_fields: &json::UnexpectedFields<'_>) {
307 if !unexpected_fields.is_empty() {
308 const MAX_FIELD_DISPLAY: usize = 20;
309
310 if unexpected_fields.len() > MAX_FIELD_DISPLAY {
311 let truncated_fields = unexpected_fields
312 .iter()
313 .take(MAX_FIELD_DISPLAY)
314 .map(|path| path.to_string())
315 .collect::<Vec<_>>();
316
317 panic!(
318 "Unexpected fields found({}); displaying the first ({}): \n{}\n... and {} more",
319 unexpected_fields.len(),
320 truncated_fields.len(),
321 truncated_fields.join(",\n"),
322 unexpected_fields.len() - truncated_fields.len(),
323 )
324 } else {
325 panic!(
326 "Unexpected fields found({}):\n{}",
327 unexpected_fields.len(),
328 unexpected_fields.to_strings().join(",\n")
329 )
330 };
331 }
332 }
333
334 #[track_caller]
335 pub fn assert_unexpected_fields(
336 unexpected_fields: &json::UnexpectedFields<'_>,
337 expected: &[&'static str],
338 ) {
339 if unexpected_fields.len() != expected.len() {
340 let unexpected_fields = unexpected_fields
341 .into_iter()
342 .map(|path| path.to_string())
343 .collect::<Vec<_>>();
344
345 panic!(
346 "The unexpected fields and expected fields lists have different lengths.\n\nUnexpected fields found:\n{}",
347 unexpected_fields.join(",\n")
348 );
349 }
350
351 let unmatched_paths = unexpected_fields
352 .into_iter()
353 .zip(expected.iter())
354 .filter(|(a, b)| a != *b)
355 .collect::<Vec<_>>();
356
357 if !unmatched_paths.is_empty() {
358 let unmatched_paths = unmatched_paths
359 .into_iter()
360 .map(|(a, b)| format!("{a} != {b}"))
361 .collect::<Vec<_>>();
362
363 panic!(
364 "The unexpected fields don't match the expected fields.\n\nUnexpected fields found:\n{}",
365 unmatched_paths.join(",\n")
366 );
367 }
368 }
369
370 #[derive(Debug, Default)]
374 pub enum Expectation<T> {
375 Present(ExpectValue<T>),
377
378 #[default]
380 Absent,
381 }
382
383 #[derive(Debug)]
385 pub enum ExpectValue<T> {
386 Some(T),
388
389 Null,
391 }
392
393 impl<T> ExpectValue<T>
394 where
395 T: fmt::Debug,
396 {
397 pub fn into_option(self) -> Option<T> {
399 match self {
400 Self::Some(v) => Some(v),
401 Self::Null => None,
402 }
403 }
404
405 #[track_caller]
411 pub fn expect_value(self) -> T {
412 match self {
413 ExpectValue::Some(v) => v,
414 ExpectValue::Null => panic!("the field expects a value"),
415 }
416 }
417 }
418
419 impl<'de, T> Deserialize<'de> for Expectation<T>
420 where
421 T: Deserialize<'de>,
422 {
423 #[expect(clippy::unwrap_in_result, reason = "This is test util code")]
424 #[track_caller]
425 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
426 where
427 D: serde::Deserializer<'de>,
428 {
429 let value = serde_json::Value::deserialize(deserializer)?;
430
431 if value.is_null() {
432 return Ok(Expectation::Present(ExpectValue::Null));
433 }
434
435 let v = T::deserialize(value).unwrap();
436 Ok(Expectation::Present(ExpectValue::Some(v)))
437 }
438 }
439
440 #[track_caller]
442 pub fn datetime_from_str(s: &str) -> DateTime {
443 let de: StrDeserializer<'_, serde::de::value::Error> = s.into_deserializer();
444 DateTime::deserialize(de).unwrap()
445 }
446
447 #[track_caller]
452 pub fn read_expect_json(json_file_path: &Path, feature: &str) -> Option<String> {
453 let json_dir = json_file_path
454 .parent()
455 .expect("The given file should live in a dir");
456
457 let json_file_name = json_file_path
458 .file_stem()
459 .expect("The `json_file_path` should be a file")
460 .to_str()
461 .expect("The `json_file_path` should have a valid name");
462
463 let expect_file_name = format!("output_{feature}__{json_file_name}.json");
466
467 debug!("Try to read expectation file: `{expect_file_name}`");
468
469 let s = std::fs::read_to_string(json_dir.join(&expect_file_name))
470 .ok()
471 .map(|mut s| {
472 json_strip_comments::strip(&mut s).ok();
473 s
474 });
475
476 debug!("Successfully Read expectation file: `{expect_file_name}`");
477 s
478 }
479}
480
481#[cfg(test)]
482mod test_rust_decimal_arbitrary_precision {
483 use rust_decimal_macros::dec;
484
485 use crate::Number;
486
487 #[test]
488 fn should_serialize_decimal_with_12_fraction_digits() {
489 let dec = dec!(0.123456789012);
490 let actual = serde_json::to_string(&dec).unwrap();
491 assert_eq!(actual, r#""0.123456789012""#.to_owned());
492 }
493
494 #[test]
495 fn should_serialize_decimal_with_8_fraction_digits() {
496 let dec = dec!(37.12345678);
497 let actual = serde_json::to_string(&dec).unwrap();
498 assert_eq!(actual, r#""37.12345678""#.to_owned());
499 }
500
501 #[test]
502 fn should_serialize_0_decimal_without_fraction_digits() {
503 let dec = dec!(0);
504 let actual = serde_json::to_string(&dec).unwrap();
505 assert_eq!(actual, r#""0""#.to_owned());
506 }
507
508 #[test]
509 fn should_serialize_12_num_with_4_fraction_digits() {
510 let num: Number = dec!(0.1234).try_into().unwrap();
511 let actual = serde_json::to_string(&num).unwrap();
512 assert_eq!(actual, r#""0.1234""#.to_owned());
513 }
514
515 #[test]
516 fn should_serialize_8_num_with_4_fraction_digits() {
517 let num: Number = dec!(37.1234).try_into().unwrap();
518 let actual = serde_json::to_string(&num).unwrap();
519 assert_eq!(actual, r#""37.1234""#.to_owned());
520 }
521
522 #[test]
523 fn should_serialize_0_num_without_fraction_digits() {
524 let num: Number = dec!(0).try_into().unwrap();
525 let actual = serde_json::to_string(&num).unwrap();
526 assert_eq!(actual, r#""0""#.to_owned());
527 }
528}