Skip to main content

sofie_core/
value.rs

1use std::any::type_name;
2use std::fmt::{self, Display};
3
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5
6use crate::{Cast, ColumnReader, ColumnSpec, Error, Result, SqlType, SqlValue};
7
8pub trait Value: Sized {
9    type Inner: Cast;
10    const SENSITIVE: bool = false;
11
12    fn inner(&self) -> &Self::Inner;
13
14    fn try_from_inner(inner: Self::Inner) -> Result<Self, impl Display>;
15
16    /// Overridden when an input rule tightens, so rows written under the looser one still read.
17    fn decode_stored(inner: Self::Inner) -> Result<Self> {
18        stored(Self::try_from_inner(inner))
19    }
20}
21
22impl<V: Value> Cast for V {
23    const COLUMNS: &'static [ColumnSpec] = V::Inner::COLUMNS;
24    const NULLABLE: bool = V::Inner::NULLABLE;
25    const SENSITIVE: bool = V::SENSITIVE || V::Inner::SENSITIVE;
26
27    fn encode(&self, out: &mut Vec<SqlValue>) {
28        self.inner().encode(out);
29    }
30
31    fn decode(row: &mut dyn ColumnReader) -> Result<Self> {
32        V::decode_stored(V::Inner::decode(row)?)
33    }
34}
35
36/// A stored value its own check refuses is the database's fault, not the caller's.
37pub fn stored<V>(built: Result<V, impl Display>) -> Result<V> {
38    built.map_err(|error| {
39        Error::internal_message(format!(
40            "a stored {} is not valid: {error}",
41            type_name::<V>()
42        ))
43    })
44}
45
46/// Evaluated in a constant, so a field spanning more than one column fails the build.
47#[must_use]
48pub const fn single_column(columns: &[ColumnSpec]) -> SqlType {
49    assert!(
50        columns.len() == 1,
51        "every field of a value with several fields must span one column"
52    );
53    columns[0].ty
54}
55
56pub fn write_masked(formatter: &mut fmt::Formatter<'_>, type_name: &str) -> fmt::Result {
57    write!(formatter, "{type_name}(***)")
58}
59
60pub fn serialize<V: Value, S: Serializer>(value: &V, serializer: S) -> Result<S::Ok, S::Error>
61where
62    V::Inner: Serialize,
63{
64    value.inner().serialize(serializer)
65}
66
67pub fn deserialize<'de, V: Value, D: Deserializer<'de>>(deserializer: D) -> Result<V, D::Error>
68where
69    V::Inner: Deserialize<'de>,
70{
71    let inner = V::Inner::deserialize(deserializer)?;
72    V::try_from_inner(inner).map_err(|error| serde::de::Error::custom(format_args!("{error:#}")))
73}
74
75#[doc(hidden)]
76pub use serde;
77
78/// `from_stored` reads only stored rows, never input: it may take the column's text as it is,
79/// and may accept what an earlier, looser rule wrote.
80#[doc(hidden)]
81#[macro_export]
82macro_rules! text_value {
83    ($value:ty, $field:ident) => {
84        $crate::text_value!($value, $field, |text: String| <$value>::parse(&text));
85    };
86    ($value:ty, $field:ident, $from_stored:expr) => {
87        impl $crate::value::Value for $value {
88            type Inner = String;
89
90            fn inner(&self) -> &String {
91                &self.$field
92            }
93
94            fn try_from_inner(text: String) -> $crate::Result<Self, impl ::std::fmt::Display> {
95                <$value>::parse(&text)
96            }
97
98            fn decode_stored(text: String) -> $crate::Result<Self> {
99                $crate::value::stored($from_stored(text))
100            }
101        }
102
103        impl $crate::value::serde::Serialize for $value {
104            fn serialize<S: $crate::value::serde::Serializer>(
105                &self,
106                serializer: S,
107            ) -> Result<S::Ok, S::Error> {
108                $crate::value::serialize(self, serializer)
109            }
110        }
111
112        impl<'de> $crate::value::serde::Deserialize<'de> for $value {
113            fn deserialize<D: $crate::value::serde::Deserializer<'de>>(
114                deserializer: D,
115            ) -> Result<Self, D::Error> {
116                $crate::value::deserialize(deserializer)
117            }
118        }
119    };
120}
121
122#[cfg(test)]
123mod tests {
124    use serde_json::json;
125
126    use super::*;
127
128    #[derive(Debug, PartialEq)]
129    struct Even(i64);
130
131    impl Value for Even {
132        type Inner = i64;
133
134        fn inner(&self) -> &i64 {
135            &self.0
136        }
137
138        fn try_from_inner(inner: i64) -> Result<Self, impl Display> {
139            if inner % 2 == 0 {
140                return Ok(Self(inner));
141            }
142            Err(Error::invalid("even", "must be even"))
143        }
144    }
145
146    struct Pin(String);
147
148    impl Value for Pin {
149        type Inner = String;
150        const SENSITIVE: bool = true;
151
152        fn inner(&self) -> &String {
153            &self.0
154        }
155
156        fn try_from_inner(inner: String) -> Result<Self, impl Display> {
157            Ok::<_, Error>(Self(inner))
158        }
159    }
160
161    #[test]
162    fn a_sensitive_value_is_a_sensitive_column_even_when_optional() {
163        const { assert!(<Pin as Cast>::SENSITIVE) };
164        const { assert!(<Option<Pin> as Cast>::SENSITIVE) };
165        const { assert!(!<Even as Cast>::SENSITIVE) };
166    }
167
168    #[test]
169    fn is_stored_as_its_inner_value_and_decoded_through_its_check() {
170        let mut values = Vec::new();
171        Even(4).encode(&mut values);
172        values.push(SqlValue::Int(3));
173        let mut row = crate::RowReader::new(&values);
174
175        assert_eq!(Even::decode(&mut row).unwrap(), Even(4));
176        assert!(matches!(
177            Even::decode(&mut row),
178            Err(Error::Internal { context, .. }) if context.contains("Even")
179        ));
180    }
181
182    #[derive(Debug, PartialEq)]
183    struct Small(i64);
184
185    impl Value for Small {
186        type Inner = i64;
187
188        fn inner(&self) -> &i64 {
189            &self.0
190        }
191
192        fn try_from_inner(inner: i64) -> Result<Self, impl Display> {
193            if inner < 10 {
194                return Ok(Self(inner));
195            }
196            Err(Error::invalid("small", "must be below 10"))
197        }
198
199        fn decode_stored(inner: i64) -> Result<Self> {
200            Ok(Self(inner))
201        }
202    }
203
204    #[test]
205    fn a_row_written_under_a_looser_rule_reads_while_input_stays_strict() {
206        let values = [SqlValue::Int(12)];
207
208        assert_eq!(
209            Small::decode(&mut crate::RowReader::new(&values)).unwrap(),
210            Small(12)
211        );
212        assert!(deserialize::<Small, _>(json!(12)).is_err());
213    }
214
215    #[test]
216    fn deserializing_reports_only_the_refusal_message() {
217        let accepted: Even = deserialize(json!(2)).unwrap();
218        let refused = deserialize::<Even, _>(json!(3)).unwrap_err();
219
220        assert_eq!(accepted, Even(2));
221        assert_eq!(refused.to_string(), "must be even");
222    }
223
224    #[test]
225    fn masks_a_sensitive_value() {
226        struct Pin;
227        impl fmt::Debug for Pin {
228            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
229                write_masked(formatter, "Pin")
230            }
231        }
232
233        assert_eq!(format!("{Pin:?}"), "Pin(***)");
234    }
235
236    #[test]
237    fn a_field_of_a_value_with_several_fields_spans_one_column() {
238        assert_eq!(single_column(i64::COLUMNS), SqlType::Int);
239    }
240}