Skip to main content

bern_protocol/
value.rs

1use crate::{SchemaError, Type, TypeTable};
2use serde::{Deserialize, Serialize};
3
4/// A schema-checked value. Records and enum payloads keep field order from
5/// their definition, so encoders never need field names on hot paths.
6#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum Value {
9    Unit,
10    Bool(bool),
11    Int(i64),
12    Float(f64),
13    Text(String),
14    Enum { variant: u32, fields: Vec<Value> },
15    Optional(Option<Box<Value>>),
16    Record(Vec<Value>),
17    List(Vec<Value>),
18}
19
20impl Value {
21    pub fn unit_variant(index: u32) -> Self {
22        Value::Enum {
23            variant: index,
24            fields: Vec::new(),
25        }
26    }
27
28    pub fn none() -> Self {
29        Value::Optional(None)
30    }
31
32    pub fn some(value: Value) -> Self {
33        Value::Optional(Some(Box::new(value)))
34    }
35
36    /// Verify that this value matches `ty`. Floats must be finite so every
37    /// encoding can represent them.
38    pub fn check(&self, ty: &Type, types: &TypeTable) -> Result<(), SchemaError> {
39        match (self, ty) {
40            (Value::Unit, Type::Unit)
41            | (Value::Bool(_), Type::Bool)
42            | (Value::Int(_), Type::Int)
43            | (Value::Text(_), Type::Text) => Ok(()),
44            (Value::Float(value), Type::Float) => {
45                if value.is_finite() {
46                    Ok(())
47                } else {
48                    Err(SchemaError::new("float values must be finite"))
49                }
50            }
51            (Value::Enum { variant, fields }, Type::Enum { name }) => {
52                let def = types
53                    .enum_def(name)
54                    .ok_or_else(|| SchemaError::new(format!("unknown enum `{name}`")))?;
55                let variant_def = def.variants.get(*variant as usize).ok_or_else(|| {
56                    SchemaError::new(format!("`{name}` has no variant #{variant}"))
57                })?;
58                if fields.len() != variant_def.fields.len() {
59                    return Err(SchemaError::new(format!(
60                        "`{name}::{}` expects {} fields",
61                        variant_def.name,
62                        variant_def.fields.len()
63                    )));
64                }
65                for (value, field) in fields.iter().zip(&variant_def.fields) {
66                    value.check(&field.ty, types).map_err(|error| {
67                        SchemaError::new(format!(
68                            "{name}::{}.{}: {}",
69                            variant_def.name, field.name, error.message
70                        ))
71                    })?;
72                }
73                Ok(())
74            }
75            (Value::Optional(None), Type::Optional { .. }) => Ok(()),
76            (Value::Optional(Some(value)), Type::Optional { inner }) => value.check(inner, types),
77            (Value::Record(values), Type::Record { name }) => {
78                let def = types
79                    .record_def(name)
80                    .ok_or_else(|| SchemaError::new(format!("unknown record `{name}`")))?;
81                if values.len() != def.fields.len() {
82                    return Err(SchemaError::new(format!(
83                        "record `{name}` expects {} fields",
84                        def.fields.len()
85                    )));
86                }
87                for (value, field) in values.iter().zip(&def.fields) {
88                    value.check(&field.ty, types).map_err(|error| {
89                        SchemaError::new(format!("{name}.{}: {}", field.name, error.message))
90                    })?;
91                }
92                Ok(())
93            }
94            (Value::List(values), Type::List { inner }) => {
95                for (index, value) in values.iter().enumerate() {
96                    value.check(inner, types).map_err(|error| {
97                        SchemaError::new(format!("[{index}]: {}", error.message))
98                    })?;
99                }
100                Ok(())
101            }
102            (value, ty) => Err(SchemaError::new(format!(
103                "expected {}, found {}",
104                ty.display(),
105                value.kind_name()
106            ))),
107        }
108    }
109
110    pub fn kind_name(&self) -> &'static str {
111        match self {
112            Value::Unit => "unit",
113            Value::Bool(_) => "bool",
114            Value::Int(_) => "int",
115            Value::Float(_) => "float",
116            Value::Text(_) => "text",
117            Value::Enum { .. } => "enum",
118            Value::Optional(_) => "optional",
119            Value::Record(_) => "record",
120            Value::List(_) => "list",
121        }
122    }
123
124    /// The type's zero value, used when a declaration omits an initial value.
125    pub fn default_for(ty: &Type, types: &TypeTable) -> Result<Value, SchemaError> {
126        Ok(match ty {
127            Type::Unit => Value::Unit,
128            Type::Bool => Value::Bool(false),
129            Type::Int => Value::Int(0),
130            Type::Float => Value::Float(0.0),
131            Type::Text => Value::Text(String::new()),
132            Type::Optional { .. } => Value::Optional(None),
133            Type::List { .. } => Value::List(Vec::new()),
134            Type::Enum { name } => {
135                let def = types
136                    .enum_def(name)
137                    .ok_or_else(|| SchemaError::new(format!("unknown enum `{name}`")))?;
138                let first = &def.variants[0];
139                let fields = first
140                    .fields
141                    .iter()
142                    .map(|field| Value::default_for(&field.ty, types))
143                    .collect::<Result<_, _>>()?;
144                Value::Enum { variant: 0, fields }
145            }
146            Type::Record { name } => {
147                let def = types
148                    .record_def(name)
149                    .ok_or_else(|| SchemaError::new(format!("unknown record `{name}`")))?;
150                Value::Record(
151                    def.fields
152                        .iter()
153                        .map(|field| Value::default_for(&field.ty, types))
154                        .collect::<Result<_, _>>()?,
155                )
156            }
157        })
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164    use crate::{EnumDef, FieldDef, RecordDef, VariantDef};
165
166    fn table() -> TypeTable {
167        TypeTable {
168            enums: vec![EnumDef {
169                name: "Kind".into(),
170                variants: vec![
171                    VariantDef {
172                        name: "A".into(),
173                        fields: vec![],
174                        label: None,
175                    },
176                    VariantDef {
177                        name: "B".into(),
178                        fields: vec![FieldDef {
179                            name: "id".into(),
180                            ty: Type::Int,
181                            doc: String::new(),
182                            content: false,
183                        }],
184                        label: None,
185                    },
186                ],
187            }],
188            records: vec![RecordDef {
189                name: "Item".into(),
190                fields: vec![
191                    FieldDef {
192                        name: "id".into(),
193                        ty: Type::Int,
194                        doc: String::new(),
195                        content: false,
196                    },
197                    FieldDef {
198                        name: "kind".into(),
199                        ty: Type::Enum {
200                            name: "Kind".into(),
201                        },
202                        doc: String::new(),
203                        content: false,
204                    },
205                ],
206            }],
207        }
208    }
209
210    #[test]
211    fn values_are_checked_against_nested_schema() {
212        let types = table();
213        types.validate().unwrap();
214        let item = Type::Record {
215            name: "Item".into(),
216        };
217        let good = Value::Record(vec![
218            Value::Int(1),
219            Value::Enum {
220                variant: 1,
221                fields: vec![Value::Int(3)],
222            },
223        ]);
224        good.check(&item, &types).unwrap();
225        let bad = Value::Record(vec![Value::Int(1), Value::unit_variant(1)]);
226        assert!(bad.check(&item, &types).is_err());
227        assert!(Value::Float(f64::NAN).check(&Type::Float, &types).is_err());
228        assert_eq!(
229            Value::default_for(&item, &types).unwrap(),
230            Value::Record(vec![Value::Int(0), Value::unit_variant(0)])
231        );
232    }
233
234    #[test]
235    fn recursive_records_are_rejected() {
236        let types = TypeTable {
237            enums: vec![],
238            records: vec![RecordDef {
239                name: "Node".into(),
240                fields: vec![FieldDef {
241                    name: "child".into(),
242                    ty: Type::optional(Type::Record {
243                        name: "Node".into(),
244                    }),
245                    doc: String::new(),
246                    content: false,
247                }],
248            }],
249        };
250        assert!(types.validate().is_err());
251    }
252}