ocpi_tariffs/
string.rs

1//! Case Insensitive String. Only printable ASCII allowed.
2
3use std::{borrow::Cow, fmt};
4
5use crate::{
6    json,
7    warning::{self, IntoCaveat},
8    Caveat, Verdict,
9};
10
11/// The warnings that can happen when parsing a case-insensitive string.
12#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
13pub enum WarningKind {
14    /// There should be no escape codes in a `CiString`.
15    ContainsEscapeCodes,
16
17    /// There should only be printable ASCII bytes in a `CiString`.
18    ContainsNonPrintableASCII,
19
20    /// The JSON value given is not a string.
21    InvalidType,
22
23    /// The length of the string exceeds the specs constraint.
24    ExceedsMaxLength,
25}
26
27impl fmt::Display for WarningKind {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            WarningKind::ContainsEscapeCodes => f.write_str("The string contains escape codes."),
31            WarningKind::ContainsNonPrintableASCII => {
32                f.write_str("The string contains non-printable bytes.")
33            }
34            WarningKind::ExceedsMaxLength => {
35                f.write_str("The string is longer than the max length defined in the spec.")
36            }
37            WarningKind::InvalidType => f.write_str("The value should be a string."),
38        }
39    }
40}
41
42impl warning::Kind for WarningKind {
43    fn id(&self) -> Cow<'static, str> {
44        match self {
45            WarningKind::ContainsEscapeCodes => "contains_escape_codes".into(),
46            WarningKind::ContainsNonPrintableASCII => "contains_non_printable_ascii".into(),
47            WarningKind::ExceedsMaxLength => "exceeds_max_len".into(),
48            WarningKind::InvalidType => "invalid_type".into(),
49        }
50    }
51}
52
53/// Case Insensitive String. Only printable ASCII allowed. (Non-printable characters like: Carriage returns, Tabs, Line breaks, etc are not allowed)
54///
55/// See: <https://github.com/ocpi/ocpi/blob/release-2.2.1-bugfixes/types.asciidoc#11-cistring-type>
56/// See: <https://github.com/ocpi/ocpi/blob/release-2.1.1-bugfixes/types.md#11-cistring-type>
57#[derive(Copy, Clone, Debug)]
58#[expect(dead_code, reason = "CiString will be used in mod generate")]
59pub(crate) struct CiString<'buf, const BYTE_LEN: usize>(&'buf str);
60
61impl<const BYTE_LEN: usize> fmt::Display for CiString<'_, BYTE_LEN> {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(f, "{}", self.0)
64    }
65}
66
67impl<const BYTE_LEN: usize> IntoCaveat for CiString<'_, BYTE_LEN> {
68    fn into_caveat<K: warning::Kind>(self, warnings: warning::Set<K>) -> Caveat<Self, K> {
69        Caveat::new(self, warnings)
70    }
71}
72
73impl<'buf, 'elem: 'buf, const BYTE_LEN: usize> json::FromJson<'elem, 'buf>
74    for CiString<'buf, BYTE_LEN>
75{
76    type WarningKind = WarningKind;
77
78    fn from_json(elem: &'elem json::Element<'buf>) -> Verdict<Self, Self::WarningKind> {
79        let mut warnings = warning::Set::new();
80        let mut check_len_and_printable = |s: &str| {
81            if s.len() > BYTE_LEN {
82                warnings.with_elem(WarningKind::ExceedsMaxLength, elem);
83            }
84
85            if s.chars()
86                .any(|c| c.is_ascii_whitespace() || c.is_ascii_control())
87            {
88                warnings.with_elem(WarningKind::ContainsNonPrintableASCII, elem);
89            }
90        };
91        let Some(id) = elem.as_raw_str() else {
92            warnings.with_elem(WarningKind::InvalidType, elem);
93            return Err(warnings);
94        };
95
96        // We don't care about the details of any warnings the escapes in the Id may have.
97        // The Id should simply not have any escapes.
98        let id = id.has_escapes(elem).ignore_warnings();
99        let id = match id {
100            json::decode::PendingStr::NoEscapes(s) => {
101                check_len_and_printable(s);
102                s
103            }
104            json::decode::PendingStr::HasEscapes(escape_str) => {
105                // We decode the escapes to check if any of the escapes result in non-printable ASCII.
106                let decoded = escape_str.decode_escapes(elem).ignore_warnings();
107                check_len_and_printable(&decoded);
108                escape_str.into_raw()
109            }
110        };
111
112        Ok(CiString(id).into_caveat(warnings))
113    }
114}