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 given field is required.
60    FieldRequired {
61        field_name: Cow<'static, str>,
62    },
63
64    /// The `min_price` is greater than `max_price`.
65    MinPriceIsGreaterThanMax,
66
67    Money(money::Warning),
68
69    /// The field is null. It can simply be removed.
70    NeedlessNullField,
71
72    Number(number::Warning),
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::FieldRequired { field_name } => {
101                warning::Id::from_string(format!("field_required({field_name})"))
102            }
103            Self::MinPriceIsGreaterThanMax => {
104                warning::Id::from_static("min_price_is_greater_than_max")
105            }
106            Self::Money(kind) => kind.id(),
107            Self::NeedlessNullField => warning::Id::from_static("needless_null_field"),
108            Self::Number(kind) => kind.id(),
109            Self::StartDateTimeIsAfterEndDateTime => {
110                warning::Id::from_static("start_date_time_is_after_end_date_time")
111            }
112            Self::String(kind) => kind.id(),
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::FieldRequired { field_name } => {
128                write!(f, "Field is required: `{field_name}`")
129            }
130            Self::MinPriceIsGreaterThanMax => {
131                f.write_str("The `min_price` is greater than `max_price`.")
132            }
133            Self::Money(kind) => fmt::Display::fmt(kind, f),
134            Self::NeedlessNullField => write!(f, "Null field: the field can simply be removed."),
135            Self::Number(kind) => fmt::Display::fmt(kind, f),
136            Self::StartDateTimeIsAfterEndDateTime => {
137                f.write_str("The `start_date_time` is after the `end_date_time`.")
138            }
139            Self::String(kind) => fmt::Display::fmt(kind, f),
140        }
141    }
142}