Skip to main content

ocpi_tariffs/
string.rs

1//! Case Insensitive String. Only printable ASCII allowed.
2
3#[cfg(test)]
4mod test_from_schema;
5
6#[cfg(test)]
7mod test_reasonable_str;
8
9use std::{fmt, ops::Deref};
10
11use crate::{
12    schema::{self, HasElement as _},
13    warning::{self, IntoCaveat as _},
14    FromSchema, Verdict,
15};
16
17/// The warnings that can happen when parsing a case-insensitive string.
18#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
19pub enum Warning {
20    /// There should be no escape codes in a `CiString`.
21    ContainsEscapeCodes,
22
23    /// There should only be printable ASCII bytes in a `CiString`.
24    ContainsNonPrintableASCII,
25
26    /// The length of the string exceeds the specs constraint.
27    InvalidLengthMax { length: usize },
28
29    /// The length of the string is not equal to the specs constraint.
30    InvalidLengthExact { length: usize },
31
32    /// The casing of the string is not common practice.
33    ///
34    /// Note: This is not enforced by the string types in this module, but can be used
35    /// by linting code to signal that the casing of a given string is unorthodox.
36    PreferUppercase,
37}
38
39impl fmt::Display for Warning {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        match self {
42            Self::ContainsEscapeCodes => f.write_str("The string contains escape codes."),
43            Self::ContainsNonPrintableASCII => {
44                f.write_str("The string contains non-printable bytes.")
45            }
46            Self::InvalidLengthMax { length } => {
47                write!(
48                    f,
49                    "The string is longer than the max length `{length}` defined in the spec.",
50                )
51            }
52            Self::InvalidLengthExact { length } => {
53                write!(f, "The string should be length `{length}`.")
54            }
55            Self::PreferUppercase => {
56                write!(f, "Upper case is preferred")
57            }
58        }
59    }
60}
61
62impl crate::Warning for Warning {
63    fn id(&self) -> warning::Id {
64        match self {
65            Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
66            Self::ContainsNonPrintableASCII => {
67                warning::Id::from_static("contains_non_printable_ascii")
68            }
69            Self::InvalidLengthMax { .. } => warning::Id::from_static("invalid_length_max"),
70            Self::InvalidLengthExact { .. } => warning::Id::from_static("invalid_length_exact"),
71            Self::PreferUppercase => warning::Id::from_static("prefer_upper_case"),
72        }
73    }
74}
75
76/// String that can have `[0..=MAX_LEN]` bytes.
77///
78/// Only printable ASCII allowed. Non-printable characters like: Carriage returns, Tabs, Line breaks, etc. are not allowed.
79/// Case insensitivity is not enforced.
80///
81/// See: <https://github.com/ocpi/ocpi/blob/release-2.2.1-bugfixes/types.asciidoc#11-cistring-type>.
82/// See: <https://github.com/ocpi/ocpi/blob/release-2.1.1-bugfixes/types.md#11-cistring-type>.
83#[derive(Copy, Clone, Debug)]
84pub(crate) struct CiMaxLen<'buf, const MAX_LEN: usize>(&'buf str);
85
86impl<const MAX_LEN: usize> Deref for CiMaxLen<'_, MAX_LEN> {
87    type Target = str;
88
89    fn deref(&self) -> &Self::Target {
90        self.0
91    }
92}
93
94impl<const MAX_LEN: usize> fmt::Display for CiMaxLen<'_, MAX_LEN> {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        write!(f, "{}", self.0)
97    }
98}
99
100impl<'buf, const MAX_LEN: usize> FromSchema<'buf, schema::Str<'buf>> for CiMaxLen<'buf, MAX_LEN> {
101    type Warning = Warning;
102
103    fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
104        let (s, mut warnings) = Base::from_schema(source)?.into_parts();
105
106        if s.len() > MAX_LEN {
107            warnings.insert(
108                source.element(),
109                Warning::InvalidLengthMax { length: MAX_LEN },
110            );
111        }
112
113        Ok(Self(s.0).into_caveat(warnings))
114    }
115}
116
117/// String that can have `LEN` bytes exactly.
118///
119/// Only printable ASCII allowed. Non-printable characters like: Carriage returns, Tabs, Line breaks, etc. are not allowed.
120/// Case insensitivity is not enforced.
121///
122/// See: <https://github.com/ocpi/ocpi/blob/release-2.2.1-bugfixes/types.asciidoc#11-cistring-type>.
123/// See: <https://github.com/ocpi/ocpi/blob/release-2.1.1-bugfixes/types.md#11-cistring-type>.
124#[derive(Copy, Clone, Debug)]
125pub(crate) struct CiExactLen<'buf, const LEN: usize>(&'buf str);
126
127impl<const LEN: usize> Deref for CiExactLen<'_, LEN> {
128    type Target = str;
129
130    fn deref(&self) -> &Self::Target {
131        self.0
132    }
133}
134
135impl<const LEN: usize> fmt::Display for CiExactLen<'_, LEN> {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        write!(f, "{}", self.0)
138    }
139}
140
141impl<'buf, const LEN: usize> FromSchema<'buf, schema::Str<'buf>> for CiExactLen<'buf, LEN> {
142    type Warning = Warning;
143
144    fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
145        let (s, mut warnings) = Base::from_schema(source)?.into_parts();
146
147        if s.len() != LEN {
148            warnings.insert(
149                source.element(),
150                Warning::InvalidLengthExact { length: LEN },
151            );
152        }
153
154        Ok(Self(s.0).into_caveat(warnings))
155    }
156}
157
158/// Case Insensitive String. Only printable ASCII allowed. (Non-printable characters like: Carriage returns, Tabs, Line breaks, etc. are not allowed).
159///
160/// See: <https://github.com/ocpi/ocpi/blob/release-2.2.1-bugfixes/types.asciidoc#11-cistring-type>.
161/// See: <https://github.com/ocpi/ocpi/blob/release-2.1.1-bugfixes/types.md#11-cistring-type>.
162#[derive(Copy, Clone, Debug)]
163struct Base<'buf>(&'buf str);
164
165impl Deref for Base<'_> {
166    type Target = str;
167
168    fn deref(&self) -> &Self::Target {
169        self.0
170    }
171}
172
173impl fmt::Display for Base<'_> {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        write!(f, "{}", self.0)
176    }
177}
178
179impl<'buf> FromSchema<'buf, schema::Str<'buf>> for Base<'buf> {
180    type Warning = Warning;
181
182    fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
183        let mut warnings = warning::Set::new();
184        let elem = source.element();
185        let raw = source.value();
186
187        // The schema confirmed the value is a string, so there is no kind check. A
188        // `CiString` should contain neither escapes nor non-printable ASCII; both are
189        // detected in a single pass without decoding into a fresh allocation.
190        let issues = raw.lexical_issues();
191        if issues.escapes {
192            warnings.insert(elem, Warning::ContainsEscapeCodes);
193        }
194        if issues.non_printable_ascii {
195            warnings.insert(elem, Warning::ContainsNonPrintableASCII);
196        }
197
198        Ok(Self(raw.as_unescaped_str()).into_caveat(warnings))
199    }
200}
201
202/// The size of the input `str` exceeds the maximum deemed reasonable.
203pub(crate) struct SizeExceedsMax(());
204
205impl std::error::Error for SizeExceedsMax {}
206
207impl fmt::Debug for SizeExceedsMax {
208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209        f.debug_tuple("SizeExceedsMax").finish()
210    }
211}
212
213impl fmt::Display for SizeExceedsMax {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        write!(
216            f,
217            "The size of the input string exceeds the maximum length of {} megabytes",
218            ReasonableLen::FACTOR
219        )
220    }
221}
222
223/// A `str` that is checked for having a reasonable size.
224#[derive(Copy, Clone)]
225pub(crate) struct ReasonableLen<'buf>(&'buf str);
226
227impl<'buf> ReasonableLen<'buf> {
228    /// One million bytes of information.
229    const MEGA: usize = 1_000_000;
230
231    /// Limit the string to this many megabyte.
232    pub(crate) const FACTOR: usize = 5;
233
234    /// The maximum allowed size for a `str` given to a parse function.
235    ///
236    /// If the input `str` exceeds this size, a [`SizeExceedsMax`] is returned.
237    ///
238    /// NOTE: Currently the largest tariff at `NLENE` is ~440 kilobyte and the largest CDR is ~1.1 megabytes.
239    ///
240    /// NOTE: The motivation for a limit is to avoid parsing unseasonably large JSON objects
241    /// whether supplied through incompetence or maliciousness. Large JSON objects can be constructed
242    /// to have many warnings. This could bog down the function processing the JSON object.
243    pub(crate) const MAX_STR_INPUT_LEN: usize = Self::FACTOR * Self::MEGA;
244
245    /// Create new `ReasonableLen` object.
246    pub(crate) fn new(s: &'buf str) -> Result<ReasonableLen<'buf>, SizeExceedsMax> {
247        if s.len() >= Self::MAX_STR_INPUT_LEN {
248            return Err(SizeExceedsMax(()));
249        }
250
251        Ok(Self(s))
252    }
253
254    /// Unpack the contained `str`.
255    pub(crate) fn into_inner(self) -> &'buf str {
256        self.0
257    }
258}