1use crate::{LocalizedText, SchemaError};
2use serde::{Deserialize, Serialize};
3
4#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
8#[serde(tag = "kind", rename_all = "snake_case")]
9pub enum Type {
10 Unit,
11 Bool,
12 Int,
13 Float,
14 Text,
15 Enum { name: String },
16 Record { name: String },
17 Optional { inner: Box<Type> },
18 List { inner: Box<Type> },
19}
20
21impl Type {
22 pub fn optional(inner: Type) -> Self {
23 Type::Optional {
24 inner: Box::new(inner),
25 }
26 }
27
28 pub fn list(inner: Type) -> Self {
29 Type::List {
30 inner: Box::new(inner),
31 }
32 }
33
34 pub fn display(&self) -> String {
36 match self {
37 Type::Unit => "()".into(),
38 Type::Bool => "bool".into(),
39 Type::Int => "int".into(),
40 Type::Float => "float".into(),
41 Type::Text => "text".into(),
42 Type::Enum { name } | Type::Record { name } => name.clone(),
43 Type::Optional { inner } => format!("{}?", inner.display()),
44 Type::List { inner } => format!("[{}]", inner.display()),
45 }
46 }
47}
48
49#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
50pub struct FieldDef {
51 pub name: String,
52 #[serde(rename = "type")]
53 pub ty: Type,
54 #[serde(default, skip_serializing_if = "String::is_empty")]
55 pub doc: String,
56 #[serde(default, skip_serializing_if = "is_false")]
59 pub content: bool,
60}
61
62fn is_false(value: &bool) -> bool {
63 !*value
64}
65
66#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
67pub struct VariantDef {
68 pub name: String,
69 #[serde(default, skip_serializing_if = "Vec::is_empty")]
71 pub fields: Vec<FieldDef>,
72 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub label: Option<LocalizedText>,
75}
76
77#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
78pub struct EnumDef {
79 pub name: String,
80 pub variants: Vec<VariantDef>,
81}
82
83impl EnumDef {
84 pub fn is_unit_only(&self) -> bool {
85 self.variants
86 .iter()
87 .all(|variant| variant.fields.is_empty())
88 }
89
90 pub fn variant_index(&self, name: &str) -> Option<usize> {
91 self.variants
92 .iter()
93 .position(|variant| variant.name == name)
94 }
95}
96
97#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
98pub struct RecordDef {
99 pub name: String,
100 pub fields: Vec<FieldDef>,
101}
102
103impl RecordDef {
104 pub fn field_index(&self, name: &str) -> Option<usize> {
105 self.fields.iter().position(|field| field.name == name)
106 }
107}
108
109#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
111pub struct TypeTable {
112 #[serde(default)]
113 pub enums: Vec<EnumDef>,
114 #[serde(default)]
115 pub records: Vec<RecordDef>,
116}
117
118impl TypeTable {
119 pub fn enum_def(&self, name: &str) -> Option<&EnumDef> {
120 self.enums.iter().find(|def| def.name == name)
121 }
122
123 pub fn record_def(&self, name: &str) -> Option<&RecordDef> {
124 self.records.iter().find(|def| def.name == name)
125 }
126
127 pub fn merge(&mut self, other: &TypeTable) -> Result<(), SchemaError> {
129 for def in &other.enums {
130 match self.enum_def(&def.name) {
131 Some(existing) if existing != def => {
132 return Err(SchemaError::new(format!(
133 "enum `{}` is defined twice with different shapes",
134 def.name
135 )));
136 }
137 Some(_) => {}
138 None => self.enums.push(def.clone()),
139 }
140 }
141 for def in &other.records {
142 match self.record_def(&def.name) {
143 Some(existing) if existing != def => {
144 return Err(SchemaError::new(format!(
145 "record `{}` is defined twice with different shapes",
146 def.name
147 )));
148 }
149 Some(_) => {}
150 None => self.records.push(def.clone()),
151 }
152 }
153 Ok(())
154 }
155
156 pub fn validate(&self) -> Result<(), SchemaError> {
159 let mut names = std::collections::BTreeSet::new();
160 for name in self
161 .enums
162 .iter()
163 .map(|def| &def.name)
164 .chain(self.records.iter().map(|def| &def.name))
165 {
166 if !names.insert(name) {
167 return Err(SchemaError::new(format!("type `{name}` is declared twice")));
168 }
169 }
170 for def in &self.enums {
171 if def.variants.is_empty() {
172 return Err(SchemaError::new(format!(
173 "enum `{}` has no variants",
174 def.name
175 )));
176 }
177 let mut seen = std::collections::BTreeSet::new();
178 for variant in &def.variants {
179 if !seen.insert(&variant.name) {
180 return Err(SchemaError::new(format!(
181 "enum `{}` repeats variant `{}`",
182 def.name, variant.name
183 )));
184 }
185 for field in &variant.fields {
186 self.check_type(&field.ty, &mut Vec::new())?;
187 }
188 for param in variant.label.iter().flat_map(|label| &label.params) {
190 let Some(Type::Enum { name }) = variant
191 .fields
192 .iter()
193 .find(|field| field.name == *param)
194 .map(|field| &field.ty)
195 else {
196 continue;
197 };
198 if self
199 .enum_def(name)
200 .is_some_and(|shown| shown.variants.iter().any(|v| v.label.is_none()))
201 {
202 return Err(SchemaError::new(format!(
203 "the label of `{}::{}` shows `{{{param}}}`, a `{name}`; give every variant of `{name}` a label",
204 def.name, variant.name
205 )));
206 }
207 }
208 }
209 }
210 for def in &self.records {
211 self.check_type(
212 &Type::Record {
213 name: def.name.clone(),
214 },
215 &mut Vec::new(),
216 )?;
217 }
218 Ok(())
219 }
220
221 pub fn check_type(&self, ty: &Type, stack: &mut Vec<String>) -> Result<(), SchemaError> {
222 match ty {
223 Type::Unit | Type::Bool | Type::Int | Type::Float | Type::Text => Ok(()),
224 Type::Enum { name } => self
225 .enum_def(name)
226 .map(|_| ())
227 .ok_or_else(|| SchemaError::new(format!("unknown enum `{name}`"))),
228 Type::Record { name } => {
229 let def = self
230 .record_def(name)
231 .ok_or_else(|| SchemaError::new(format!("unknown record `{name}`")))?;
232 if stack.contains(name) {
233 return Err(SchemaError::new(format!(
234 "record `{name}` contains itself; recursive data needs a dedicated design"
235 )));
236 }
237 stack.push(name.clone());
238 for field in &def.fields {
239 self.check_type(&field.ty, stack)?;
240 }
241 stack.pop();
242 Ok(())
243 }
244 Type::Optional { inner } => {
245 if matches!(**inner, Type::Optional { .. }) {
246 return Err(SchemaError::new("nested optionals are not supported"));
247 }
248 self.check_type(inner, stack)
249 }
250 Type::List { inner } => self.check_type(inner, stack),
251 }
252 }
253}