Skip to main content

serde_er7/
separators.rs

1//! [`Separators`]: the delimiter set, serialized as an object of
2//! single-character strings.
3
4use std::fmt;
5use std::ops::{Deref, DerefMut};
6
7use serde::de::{self, MapAccess, Visitor};
8use serde::ser::SerializeStruct;
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10
11/// A Serde-enabled [`er7::Separators`].
12///
13/// This is the plainest wrapper in the crate: six named, scalar fields, no
14/// recursion — the same shape as the `Point { x, y }` example in
15/// [serde's own manual-implementation
16/// guide](https://docs.rs/serde/latest/serde/), just with six fields
17/// instead of two. Each `char` serializes through
18/// [`Serializer::serialize_char`], which every common format maps to a
19/// one-character string.
20///
21/// Example:
22///
23/// ```
24/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
25/// use serde_er7::Separators;
26///
27/// let wrapped = Separators(er7::Separators::default());
28/// let json = serde_json::to_string(&wrapped)?;
29/// assert_eq!(
30///     json,
31///     r#"{"field":"|","component":"^","repetition":"~","escape":"\\","subcomponent":"&","truncation":null}"#
32/// );
33///
34/// let back: Separators = serde_json::from_str(&json)?;
35/// assert_eq!(back.0, er7::Separators::default());
36/// # Ok(())
37/// # }
38/// ```
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub struct Separators(pub er7::Separators);
41
42impl From<er7::Separators> for Separators {
43    fn from(inner: er7::Separators) -> Separators {
44        Separators(inner)
45    }
46}
47
48impl From<Separators> for er7::Separators {
49    fn from(outer: Separators) -> er7::Separators {
50        outer.0
51    }
52}
53
54impl Deref for Separators {
55    type Target = er7::Separators;
56
57    fn deref(&self) -> &er7::Separators {
58        &self.0
59    }
60}
61
62impl DerefMut for Separators {
63    fn deref_mut(&mut self) -> &mut er7::Separators {
64        &mut self.0
65    }
66}
67
68impl Serialize for Separators {
69    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
70    where
71        S: Serializer,
72    {
73        let mut state = serializer.serialize_struct("Separators", 6)?;
74        state.serialize_field("field", &self.0.field)?;
75        state.serialize_field("component", &self.0.component)?;
76        state.serialize_field("repetition", &self.0.repetition)?;
77        state.serialize_field("escape", &self.0.escape)?;
78        state.serialize_field("subcomponent", &self.0.subcomponent)?;
79        state.serialize_field("truncation", &self.0.truncation)?;
80        state.end()
81    }
82}
83
84const FIELDS: &[&str] = &[
85    "field",
86    "component",
87    "repetition",
88    "escape",
89    "subcomponent",
90    "truncation",
91];
92
93struct SeparatorsVisitor;
94
95impl<'de> Visitor<'de> for SeparatorsVisitor {
96    type Value = Separators;
97
98    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
99        formatter.write_str(
100            "a Separators object with \"field\", \"component\", \"repetition\", \"escape\", \
101             \"subcomponent\", and \"truncation\"",
102        )
103    }
104
105    fn visit_map<V>(self, mut map: V) -> Result<Separators, V::Error>
106    where
107        V: MapAccess<'de>,
108    {
109        let mut field = None;
110        let mut component = None;
111        let mut repetition = None;
112        let mut escape = None;
113        let mut subcomponent = None;
114        let mut truncation: Option<Option<char>> = None;
115
116        while let Some(key) = map.next_key::<String>()? {
117            match key.as_str() {
118                "field" => set_once(&mut field, &mut map, "field")?,
119                "component" => set_once(&mut component, &mut map, "component")?,
120                "repetition" => set_once(&mut repetition, &mut map, "repetition")?,
121                "escape" => set_once(&mut escape, &mut map, "escape")?,
122                "subcomponent" => set_once(&mut subcomponent, &mut map, "subcomponent")?,
123                "truncation" => {
124                    if truncation.is_some() {
125                        return Err(de::Error::duplicate_field("truncation"));
126                    }
127                    truncation = Some(map.next_value()?);
128                }
129                _ => {
130                    let _ = map.next_value::<de::IgnoredAny>()?;
131                }
132            }
133        }
134
135        let field = field.ok_or_else(|| de::Error::missing_field("field"))?;
136        let component = component.ok_or_else(|| de::Error::missing_field("component"))?;
137        let repetition = repetition.ok_or_else(|| de::Error::missing_field("repetition"))?;
138        let escape = escape.ok_or_else(|| de::Error::missing_field("escape"))?;
139        let subcomponent = subcomponent.ok_or_else(|| de::Error::missing_field("subcomponent"))?;
140        // `truncation` is genuinely optional in ER7 itself (spec: v2.7+
141        // only), so a message that omits the key gets `None` rather than an
142        // error — unlike the five delimiters above, which every message has.
143        let truncation = truncation.unwrap_or(None);
144
145        Ok(Separators(er7::Separators {
146            field,
147            component,
148            repetition,
149            escape,
150            subcomponent,
151            truncation,
152        }))
153    }
154}
155
156/// Read one `char` field from the map, rejecting a second occurrence of the
157/// same key.
158fn set_once<'de, V>(
159    slot: &mut Option<char>,
160    map: &mut V,
161    name: &'static str,
162) -> Result<(), V::Error>
163where
164    V: MapAccess<'de>,
165{
166    if slot.is_some() {
167        return Err(de::Error::duplicate_field(name));
168    }
169    *slot = Some(map.next_value()?);
170    Ok(())
171}
172
173impl<'de> Deserialize<'de> for Separators {
174    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
175    where
176        D: Deserializer<'de>,
177    {
178        deserializer.deserialize_struct("Separators", FIELDS, SeparatorsVisitor)
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn round_trips_the_default_delimiters() {
188        let separators = Separators::default();
189        let json = serde_json::to_string(&separators).unwrap();
190        let back: Separators = serde_json::from_str(&json).unwrap();
191        assert_eq!(back, separators);
192    }
193
194    #[test]
195    fn round_trips_custom_delimiters_and_truncation() {
196        let separators = Separators(er7::Separators {
197            field: '#',
198            component: '*',
199            repetition: '!',
200            escape: '?',
201            subcomponent: '@',
202            truncation: Some('%'),
203        });
204        let json = serde_json::to_string(&separators).unwrap();
205        let back: Separators = serde_json::from_str(&json).unwrap();
206        assert_eq!(back, separators);
207    }
208
209    #[test]
210    fn treats_a_missing_truncation_key_as_none() {
211        let json =
212            r#"{"field":"|","component":"^","repetition":"~","escape":"\\","subcomponent":"&"}"#;
213        let back: Separators = serde_json::from_str(json).unwrap();
214        assert_eq!(back.truncation, None);
215    }
216
217    #[test]
218    fn rejects_a_missing_required_field() {
219        let err = serde_json::from_str::<Separators>(r#"{"field":"|"}"#).unwrap_err();
220        assert!(err.to_string().contains("component"));
221    }
222}