Skip to main content

ocpi_tariffs/
enumeration.rs

1use crate::{json, warning};
2
3/// A type used to deserialize a JSON string value into a structured Rust enum.
4///
5/// The deserialized value may not map to a `Known` variant in the enum and therefore be `Unknown`.
6/// The caller can then decide what to do with the `Unknown` variant.
7#[derive(Clone, Debug)]
8pub(crate) enum Enum<T> {
9    Known(T),
10    Unknown(String),
11}
12
13/// Create an `Enum<T>` from a `&str`.
14///
15/// This is used in conjunction with `FromJson`
16pub(crate) trait IntoEnum: Sized {
17    fn enum_from_str(s: &str) -> Enum<Self>;
18}
19
20/// A wrapper around `WarningKind` that associates the `enum` name with the warning.
21#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
22pub struct Warning {
23    /// The type name of the enum that raised this warning.
24    type_name: &'static str,
25
26    /// The kind of warning raised.
27    kind: WarningKind,
28}
29
30#[cfg(test)]
31impl Warning {
32    pub(crate) fn type_name(&self) -> &'static str {
33        self.type_name
34    }
35
36    pub(crate) fn kind(&self) -> &WarningKind {
37        &self.kind
38    }
39}
40
41impl std::fmt::Display for Warning {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        match &self.kind {
44            WarningKind::ContainsEscapeCodes => write!(
45                f,
46                "The {} value contains escape codes but it does not need them.",
47                self.type_name
48            ),
49            WarningKind::Decode(warning) => std::fmt::Display::fmt(&warning, f),
50            WarningKind::PreferUpperCase => {
51                write!(f, "The `{}` should be uppercase.", self.type_name)
52            }
53            WarningKind::InvalidVariant => {
54                write!(f, "The value is not a valid `{}`.", self.type_name)
55            }
56            WarningKind::InvalidType => {
57                write!(f, "A {} value should be a string.", self.type_name)
58            }
59        }
60    }
61}
62
63impl crate::Warning for Warning {
64    fn id(&self) -> warning::Id {
65        match &self.kind {
66            WarningKind::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
67            WarningKind::Decode(warning) => warning.id(),
68            WarningKind::PreferUpperCase => warning::Id::from_static("prefer_upper_case"),
69            WarningKind::InvalidVariant => warning::Id::from_static("invalid_variant"),
70
71            WarningKind::InvalidType => warning::Id::from_static("invalid_type"),
72        }
73    }
74}
75
76#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
77pub enum WarningKind {
78    /// The enum variant doesn't need to have escape-codes.
79    ContainsEscapeCodes,
80
81    /// The field at the path could not be decoded.
82    Decode(json::decode::Warning),
83
84    /// Each enum should be uppercase.
85    PreferUpperCase,
86
87    /// The value is not a valid.
88    InvalidVariant,
89
90    /// The JSON value given is not a string.
91    InvalidType,
92}
93
94impl WarningKind {
95    /// Convert a `WarningKind` into a `Warning` by supplying a type name.
96    pub(crate) fn into_warning(self, type_name: &'static str) -> Warning {
97        Warning {
98            type_name,
99            kind: self,
100        }
101    }
102}
103
104/// This macro defines the `FromJson` impl for the given enum.
105///
106/// Note: The `IntoEnum` should already be defined for the enum.
107#[macro_export]
108macro_rules! define_enum_from_json {
109    ($kind:ident, display_name: $name:literal, warning_id: $warning:literal) => {
110        impl $crate::json::FromJson<'_> for $kind {
111            type Warning = $crate::enumeration::Warning;
112
113            fn from_json(elem: &$crate::json::Element<'_>) -> $crate::Verdict<Self, Self::Warning> {
114                use $crate::warning::IntoCaveat as _;
115
116                let value = Enum::<$kind>::from_json(elem)?;
117                let (value, warnings) = value.into_parts();
118
119                let value = match value {
120                    $crate::Enum::Known(v) => v,
121                    $crate::Enum::Unknown(_) => {
122                        return warnings.bail(
123                            $crate::enumeration::WarningKind::InvalidVariant
124                                .into_warning(stringify!($kind)),
125                            elem,
126                        );
127                    }
128                };
129
130                Ok(value.into_caveat(warnings))
131            }
132        }
133
134        impl $crate::json::FromJson<'_> for $crate::Enum<$kind> {
135            type Warning = $crate::enumeration::Warning;
136
137            fn from_json(elem: &$crate::json::Element<'_>) -> $crate::Verdict<Self, Self::Warning> {
138                use $crate::warning::IntoCaveat as _;
139
140                let mut warnings = $crate::warning::Set::new();
141                let value = elem.as_value();
142
143                let Some(s) = value.to_raw_str() else {
144                    return warnings.bail(
145                        $crate::enumeration::WarningKind::InvalidType
146                            .into_warning(stringify!($kind)),
147                        elem,
148                    );
149                };
150
151                // We can't use `fn gather_warnings_into` here as we want to convert the warnings
152                // using the type name from the macro instead of the `impl From<WarningA> for WarningB`.
153                // This requires a manual conversion from `decode::Warning` to `Warning`.
154                let (s, inner_warnings) = s.has_escapes(elem).into_parts();
155                let inner_warnings =
156                    inner_warnings
157                        .into_inner()
158                        .into_iter()
159                        .map(|(elem_id, group)| {
160                            (
161                                elem_id,
162                                group.map(|w| {
163                                    $crate::enumeration::WarningKind::Decode(w)
164                                        .into_warning(stringify!($kind))
165                                }),
166                            )
167                        });
168
169                warnings.extend(inner_warnings);
170
171                let s = match s {
172                    $crate::json::decode::PendingStr::NoEscapes(s) => s,
173                    $crate::json::decode::PendingStr::HasEscapes(_) => {
174                        return warnings.bail(
175                            $crate::enumeration::WarningKind::ContainsEscapeCodes
176                                .into_warning(stringify!($kind)),
177                            elem,
178                        );
179                    }
180                };
181
182                if !s.chars().all(|c| c.is_uppercase() || c == '_') {
183                    warnings.insert(
184                        $crate::enumeration::WarningKind::PreferUpperCase
185                            .into_warning(stringify!($kind)),
186                        elem,
187                    );
188                }
189
190                let value = $kind::enum_from_str(s);
191
192                Ok(value.into_caveat(warnings))
193            }
194        }
195    };
196}