Skip to main content

mkt_types/
extensions.rs

1use std::borrow::Borrow;
2use std::collections::{btree_map, BTreeMap};
3use std::fmt;
4use std::str::FromStr;
5
6use rust_decimal::Decimal;
7use serde_json::{Number, Value};
8use time::OffsetDateTime;
9
10#[non_exhaustive]
11#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct NamespaceKey(String);
13
14impl NamespaceKey {
15    pub fn new(value: impl Into<String>) -> Result<Self, NamespaceKeyError> {
16        let value = value.into();
17        Self::validate(value.as_str())?;
18
19        Ok(Self(value))
20    }
21
22    pub fn validate(value: &str) -> Result<(), NamespaceKeyError> {
23        let Some((namespace, name)) = value.split_once('.') else {
24            return Err(NamespaceKeyError::MissingNamespace(value.to_owned()));
25        };
26
27        if namespace.is_empty() || name.is_empty() {
28            return Err(NamespaceKeyError::EmptySegment(value.to_owned()));
29        }
30
31        if namespace.contains('.') || name.contains('.') {
32            return Err(NamespaceKeyError::TooManySegments(value.to_owned()));
33        }
34
35        if !(namespace.bytes().all(Self::is_namespace_segment_byte)
36            && name.bytes().all(Self::is_namespace_segment_byte))
37        {
38            return Err(NamespaceKeyError::InvalidCharacters(value.to_owned()));
39        }
40
41        Ok(())
42    }
43
44    fn is_namespace_segment_byte(byte: u8) -> bool {
45        byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_' || byte == b'-'
46    }
47}
48
49#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50#[non_exhaustive]
51#[derive(Debug, Clone, Default, PartialEq, Eq)]
52pub struct Extensions(BTreeMap<NamespaceKey, Value>);
53
54impl Extensions {
55    pub fn new() -> Self {
56        Self::default()
57    }
58
59    pub fn get(&self, key: impl AsRef<str>) -> Option<&Value> {
60        self.0.get(key.as_ref())
61    }
62
63    pub fn iter(&self) -> btree_map::Iter<'_, NamespaceKey, Value> {
64        self.0.iter()
65    }
66
67    pub fn is_empty(&self) -> bool {
68        self.0.is_empty()
69    }
70
71    pub fn as_map(&self) -> &BTreeMap<NamespaceKey, Value> {
72        &self.0
73    }
74
75    pub fn into_map(self) -> BTreeMap<NamespaceKey, Value> {
76        self.0
77    }
78
79    pub fn insert(&mut self, key: impl AsRef<str>, value: Value) -> Result<(), NamespaceKeyError> {
80        let namespace_key = NamespaceKey::new(key.as_ref())?;
81        self.0.insert(namespace_key, value);
82        Ok(())
83    }
84
85    pub fn insert_optional_string(
86        &mut self,
87        key: impl AsRef<str>,
88        value: Option<String>,
89    ) -> Result<(), NamespaceKeyError> {
90        if let Some(value) = value {
91            self.insert(key, Value::String(value))?;
92        }
93        Ok(())
94    }
95
96    pub fn insert_optional_bool(
97        &mut self,
98        key: impl AsRef<str>,
99        value: Option<bool>,
100    ) -> Result<(), NamespaceKeyError> {
101        if let Some(value) = value {
102            self.insert(key, Value::Bool(value))?;
103        }
104        Ok(())
105    }
106
107    pub fn insert_i64(
108        &mut self,
109        key: impl AsRef<str>,
110        value: i64,
111    ) -> Result<(), NamespaceKeyError> {
112        self.insert(key, Value::Number(Number::from(value)))
113    }
114
115    pub fn string(&self, key: impl AsRef<str>) -> Result<Option<String>, ExtensionValueError> {
116        let key = key.as_ref();
117        let Some(value) = self.0.get(key) else {
118            return Ok(None);
119        };
120        match value {
121            Value::String(value) => Ok(Some(value.clone())),
122            other => Err(ExtensionValueError::invalid_type(
123                key,
124                "string",
125                other.to_string(),
126            )),
127        }
128    }
129
130    pub fn i64(&self, key: impl AsRef<str>) -> Result<Option<i64>, ExtensionValueError> {
131        let key = key.as_ref();
132        let Some(value) = self.0.get(key) else {
133            return Ok(None);
134        };
135        match value {
136            Value::Number(number) => number
137                .as_i64()
138                .ok_or_else(|| {
139                    ExtensionValueError::invalid_type(
140                        key,
141                        "integer value that fits into i64",
142                        number.to_string(),
143                    )
144                })
145                .map(Some),
146            Value::String(raw) => {
147                raw.parse::<i64>()
148                    .map(Some)
149                    .map_err(|err| ExtensionValueError::ParseInteger {
150                        key: key.to_owned(),
151                        value: raw.clone(),
152                        message: err.to_string(),
153                    })
154            }
155            other => Err(ExtensionValueError::invalid_type(
156                key,
157                "integer",
158                other.to_string(),
159            )),
160        }
161    }
162
163    pub fn i32(&self, key: impl AsRef<str>) -> Result<Option<i32>, ExtensionValueError> {
164        let key = key.as_ref();
165        self.i64(key)?
166            .map(|value| {
167                i32::try_from(value).map_err(|_| ExtensionValueError::IntegerOutOfRange {
168                    key: key.to_owned(),
169                    value,
170                    target: "i32",
171                })
172            })
173            .transpose()
174    }
175
176    pub fn decimal(&self, key: impl AsRef<str>) -> Result<Option<Decimal>, ExtensionValueError> {
177        let key = key.as_ref();
178        let Some(value) = self.0.get(key) else {
179            return Ok(None);
180        };
181
182        let raw = match value {
183            Value::String(value) => value.clone(),
184            Value::Number(number) => number.to_string(),
185            other => {
186                return Err(ExtensionValueError::invalid_type(
187                    key,
188                    "decimal string or number",
189                    other.to_string(),
190                ))
191            }
192        };
193
194        Decimal::from_str(&raw)
195            .map(Some)
196            .map_err(|err| ExtensionValueError::ParseDecimal {
197                key: key.to_owned(),
198                value: raw,
199                message: err.to_string(),
200            })
201    }
202
203    pub fn parse_optional_decimal<E>(
204        value: Option<String>,
205        on_error: impl FnOnce(rust_decimal::Error) -> E,
206    ) -> Result<Option<Decimal>, E> {
207        value
208            .map(|raw| Decimal::from_str(raw.as_ref()).map_err(on_error))
209            .transpose()
210    }
211
212    pub fn parse_timestamp<E>(
213        value: i64,
214        on_error: impl FnOnce(time::error::ComponentRange) -> E,
215    ) -> Result<OffsetDateTime, E> {
216        OffsetDateTime::from_unix_timestamp_nanos(i128::from(value) * 1_000_000).map_err(on_error)
217    }
218
219    pub fn parse_optional_timestamp<E>(
220        value: Option<i64>,
221        on_error: impl FnOnce(time::error::ComponentRange) -> E,
222    ) -> Result<Option<OffsetDateTime>, E> {
223        value
224            .filter(|timestamp| *timestamp >= 0)
225            .map(|timestamp| Self::parse_timestamp(timestamp, on_error))
226            .transpose()
227    }
228}
229
230impl From<BTreeMap<NamespaceKey, Value>> for Extensions {
231    fn from(value: BTreeMap<NamespaceKey, Value>) -> Self {
232        Self(value)
233    }
234}
235
236impl From<Extensions> for BTreeMap<NamespaceKey, Value> {
237    fn from(value: Extensions) -> Self {
238        value.0
239    }
240}
241
242#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
243#[non_exhaustive]
244#[derive(Debug, Clone, PartialEq, Eq)]
245pub enum ExtensionValueError {
246    InvalidType {
247        key: String,
248        expected: &'static str,
249        actual: String,
250    },
251    ParseInteger {
252        key: String,
253        value: String,
254        message: String,
255    },
256    IntegerOutOfRange {
257        key: String,
258        value: i64,
259        target: &'static str,
260    },
261    ParseDecimal {
262        key: String,
263        value: String,
264        message: String,
265    },
266}
267
268impl ExtensionValueError {
269    fn invalid_type(key: &str, expected: &'static str, actual: String) -> Self {
270        Self::InvalidType {
271            key: key.to_owned(),
272            expected,
273            actual,
274        }
275    }
276}
277
278impl TryFrom<&str> for NamespaceKey {
279    type Error = NamespaceKeyError;
280
281    fn try_from(value: &str) -> Result<Self, Self::Error> {
282        Self::new(value)
283    }
284}
285
286impl fmt::Display for NamespaceKey {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        f.write_str(&self.0)
289    }
290}
291
292impl Borrow<str> for NamespaceKey {
293    fn borrow(&self) -> &str {
294        &self.0
295    }
296}
297
298#[cfg(feature = "serde")]
299impl serde::Serialize for NamespaceKey {
300    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
301    where
302        S: serde::Serializer,
303    {
304        serializer.serialize_str(&self.0)
305    }
306}
307
308#[cfg(feature = "serde")]
309impl<'de> serde::Deserialize<'de> for NamespaceKey {
310    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
311    where
312        D: serde::Deserializer<'de>,
313    {
314        let value = <String as serde::Deserialize>::deserialize(deserializer)?;
315        Self::new(value).map_err(serde::de::Error::custom)
316    }
317}
318
319#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
320#[non_exhaustive]
321#[derive(Debug, Clone, PartialEq, Eq)]
322pub enum NamespaceKeyError {
323    MissingNamespace(String),
324    EmptySegment(String),
325    TooManySegments(String),
326    InvalidCharacters(String),
327}
328
329impl fmt::Display for ExtensionValueError {
330    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331        match self {
332            Self::InvalidType {
333                key,
334                expected,
335                actual,
336            } => write!(f, "extension `{key}` expected {expected}, got {actual}"),
337            Self::ParseInteger {
338                key,
339                value,
340                message,
341            } => write!(
342                f,
343                "extension `{key}` could not parse integer `{value}`: {message}"
344            ),
345            Self::IntegerOutOfRange { key, value, target } => {
346                write!(
347                    f,
348                    "extension `{key}` integer `{value}` does not fit into {target}"
349                )
350            }
351            Self::ParseDecimal {
352                key,
353                value,
354                message,
355            } => write!(
356                f,
357                "extension `{key}` could not parse decimal `{value}`: {message}"
358            ),
359        }
360    }
361}
362
363impl std::error::Error for ExtensionValueError {}
364
365impl fmt::Display for NamespaceKeyError {
366    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367        match self {
368            Self::MissingNamespace(value) => {
369                write!(f, "namespace key must be namespace.name: {value}")
370            }
371            Self::EmptySegment(value) => write!(f, "namespace key has an empty segment: {value}"),
372            Self::TooManySegments(value) => {
373                write!(f, "namespace key must contain exactly one dot: {value}")
374            }
375            Self::InvalidCharacters(value) => {
376                write!(f, "namespace key contains invalid characters: {value}")
377            }
378        }
379    }
380}
381
382impl std::error::Error for NamespaceKeyError {}