Skip to main content

saya_types/contract/
preference_serde.rs

1//! Hand-rolled `Deserialize` for `PreferenceValue`.
2//!
3//! The derived `Deserialize` would populate the string-carrying variants'
4//! fields directly and bypass the validated constructors — exactly the
5//! "validated constructor beside a publicly-constructible variant" the security
6//! standard warns about. This impl routes deserialization through the
7//! constructors, so a `{"kind":"timezone","value":"SELECT ..."}` row is refused
8//! by the *type*, not only by the store's admission gate. That is the spec's
9//! "make it unrepresentable, not filtered" rule, enforced on every construction
10//! path, not just the obvious one.
11
12use serde::Deserialize;
13
14use crate::contract::preference::{DateGrain, OutputStyle, PreferenceValue};
15
16impl<'de> Deserialize<'de> for PreferenceValue {
17    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
18    where
19        D: serde::Deserializer<'de>,
20    {
21        #[derive(Deserialize)]
22        #[serde(field_identifier, rename_all = "snake_case")]
23        enum Field {
24            Kind,
25            Value,
26            Grain,
27            Style,
28            Name,
29        }
30
31        struct PreferenceValueVisitor;
32
33        impl<'de> serde::de::Visitor<'de> for PreferenceValueVisitor {
34            type Value = PreferenceValue;
35
36            fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37                f.write_str("a preference value tagged with `kind`")
38            }
39
40            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
41            where
42                A: serde::de::MapAccess<'de>,
43            {
44                let mut kind: Option<String> = None;
45                let mut value: Option<String> = None;
46                let mut grain: Option<DateGrain> = None;
47                let mut style: Option<OutputStyle> = None;
48                let mut name: Option<String> = None;
49                while let Some(key) = map.next_key::<Field>()? {
50                    match key {
51                        Field::Kind => {
52                            if kind.is_some() {
53                                return Err(serde::de::Error::duplicate_field("kind"));
54                            }
55                            kind = Some(map.next_value()?);
56                        }
57                        Field::Value => {
58                            if value.is_some() {
59                                return Err(serde::de::Error::duplicate_field("value"));
60                            }
61                            value = Some(map.next_value()?);
62                        }
63                        Field::Grain => {
64                            if grain.is_some() {
65                                return Err(serde::de::Error::duplicate_field("grain"));
66                            }
67                            grain = Some(map.next_value()?);
68                        }
69                        Field::Style => {
70                            if style.is_some() {
71                                return Err(serde::de::Error::duplicate_field("style"));
72                            }
73                            style = Some(map.next_value()?);
74                        }
75                        Field::Name => {
76                            if name.is_some() {
77                                return Err(serde::de::Error::duplicate_field("name"));
78                            }
79                            name = Some(map.next_value()?);
80                        }
81                    }
82                }
83                let kind = kind.ok_or_else(|| serde::de::Error::missing_field("kind"))?;
84                // Construction goes through the validated constructors, so a
85                // malformed timezone or profile name is refused here, at the
86                // type, before any caller ever holds a `PreferenceValue`.
87                match kind.as_str() {
88                    "timezone" => {
89                        let v = value.ok_or_else(|| serde::de::Error::missing_field("value"))?;
90                        PreferenceValue::timezone(v).map_err(serde::de::Error::custom)
91                    }
92                    "date_grain" => {
93                        let g = grain.ok_or_else(|| serde::de::Error::missing_field("grain"))?;
94                        Ok(PreferenceValue::date_grain(g))
95                    }
96                    "output_style" => {
97                        let s = style.ok_or_else(|| serde::de::Error::missing_field("style"))?;
98                        Ok(PreferenceValue::output_style(s))
99                    }
100                    "default_profile" => {
101                        let n = name.ok_or_else(|| serde::de::Error::missing_field("name"))?;
102                        PreferenceValue::default_profile(n).map_err(serde::de::Error::custom)
103                    }
104                    other => Err(serde::de::Error::unknown_variant(
105                        other,
106                        &["timezone", "date_grain", "output_style", "default_profile"],
107                    )),
108                }
109            }
110        }
111
112        deserializer.deserialize_map(PreferenceValueVisitor)
113    }
114}