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