Skip to main content

ocpi_tariffs/lint/
tariff.rs

1#[cfg(test)]
2mod test;
3
4pub(crate) mod v211;
5pub(crate) mod v221;
6pub mod v2x;
7
8use std::{borrow::Cow, fmt};
9
10use crate::{
11    country, currency, datetime, from_warning_all, json, money, number, string, tariff, warning,
12    Versioned as _,
13};
14
15/// Lint the given tariff and return a report of any [`Warning`]s found.
16///
17/// * See: [OCPI spec 2.2.1: Tariff](<https://github.com/ocpi/ocpi/blob/release-2.2.1-bugfixes/mod_tariffs.asciidoc#131-tariff-object>)
18/// * See: [OCPI spec 2.1.1: Tariff](<https://github.com/ocpi/ocpi/blob/release-2.1.1-bugfixes/mod_tariffs.md#31-tariff-object>)
19pub(crate) fn lint(tariff: &tariff::Versioned<'_>) -> Report {
20    let mut warnings = warning::Set::new();
21
22    {
23        // Walk all elements taking note of which elements have a null value.
24        // There is no reason to use `null` in the OCPI spec.
25        // Instead, you should just leave out the field in question.
26        let walker = json::walk::DepthFirst::new(tariff.as_element());
27
28        for elem in walker.filter(|&elem| elem.value().is_null()) {
29            warnings.insert(Warning::NeedlessNullField, elem);
30        }
31    }
32
33    match tariff.version() {
34        crate::Version::V221 => v221::lint(tariff.as_element(), warnings),
35        crate::Version::V211 => v211::lint(tariff.as_element(), warnings),
36    }
37}
38
39/// A tariff linting report.
40#[derive(Debug)]
41pub struct Report {
42    /// Any `Warning`s found while linting the tariff.
43    pub warnings: warning::Set<Warning>,
44}
45
46#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
47pub enum Warning {
48    Country(country::Warning),
49
50    /// Both the CDR and tariff have a `country_code` that should be an alpha-2.
51    CpoCountryCodeShouldBeAlpha2,
52
53    Currency(currency::Warning),
54
55    DateTime(datetime::Warning),
56
57    Elements(v2x::elements::Warning),
58
59    /// The `min_price` is greater than `max_price`.
60    MinPriceIsGreaterThanMax,
61
62    Money(money::Warning),
63
64    /// The field is null. It can simply be removed.
65    NeedlessNullField,
66
67    Number(number::Warning),
68
69    /// The given field is required.
70    FieldRequired {
71        field_name: Cow<'static, str>,
72    },
73
74    String(string::Warning),
75
76    /// The `start_date_time` is after the `end_date_time`.
77    StartDateTimeIsAfterEndDateTime,
78}
79
80from_warning_all!(
81    country::Warning => Warning::Country,
82    currency::Warning => Warning::Currency,
83    datetime::Warning => Warning::DateTime,
84    number::Warning => Warning::Number,
85    money::Warning => Warning::Money,
86    string::Warning => Warning::String,
87    v2x::elements::Warning => Warning::Elements
88);
89
90impl crate::Warning for Warning {
91    fn id(&self) -> warning::Id {
92        match self {
93            Self::CpoCountryCodeShouldBeAlpha2 => {
94                warning::Id::from_static("cpo_country_code_should_be_alpha2")
95            }
96            Self::Country(kind) => kind.id(),
97            Self::Currency(kind) => kind.id(),
98            Self::DateTime(kind) => kind.id(),
99            Self::Elements(kind) => kind.id(),
100            Self::MinPriceIsGreaterThanMax => {
101                warning::Id::from_static("min_price_is_greater_than_max")
102            }
103            Self::Number(kind) => kind.id(),
104            Self::Money(kind) => kind.id(),
105            Self::NeedlessNullField => warning::Id::from_static("needless_null_field"),
106            Self::StartDateTimeIsAfterEndDateTime => {
107                warning::Id::from_static("start_date_time_is_after_end_date_time")
108            }
109            Self::String(kind) => kind.id(),
110            Self::FieldRequired { field_name } => {
111                warning::Id::from_string(format!("field_required({field_name})"))
112            }
113        }
114    }
115}
116
117impl fmt::Display for Warning {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        match self {
120            Self::CpoCountryCodeShouldBeAlpha2 => {
121                f.write_str("The value should be an alpha-2 ISO 3166-1 country code")
122            }
123            Self::Country(kind) => fmt::Display::fmt(kind, f),
124            Self::Currency(kind) => fmt::Display::fmt(kind, f),
125            Self::DateTime(kind) => fmt::Display::fmt(kind, f),
126            Self::Elements(kind) => fmt::Display::fmt(kind, f),
127            Self::MinPriceIsGreaterThanMax => {
128                f.write_str("The `min_price` is greater than `max_price`.")
129            }
130            Self::Number(kind) => fmt::Display::fmt(kind, f),
131            Self::Money(kind) => fmt::Display::fmt(kind, f),
132            Self::NeedlessNullField => write!(f, "Null field: the field can simply be removed."),
133            Self::StartDateTimeIsAfterEndDateTime => {
134                f.write_str("The `start_date_time` is after the `end_date_time`.")
135            }
136            Self::String(kind) => fmt::Display::fmt(kind, f),
137            Self::FieldRequired { field_name } => {
138                write!(f, "Field is required: `{field_name}`")
139            }
140        }
141    }
142}