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