1use crate::{SchemaError, Type, TypeTable};
2use serde::{Deserialize, Serialize};
3
4#[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 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 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}