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