Skip to main content

cratefield_tables/
manifest.rs

1//! Deserialization of the manifest's `[tables]` section.
2//!
3//! The wire shape is flat on purpose: a field is one TOML table with a
4//! `kind` and the attributes that kind accepts, so a venture reads its own
5//! schema without learning a nesting convention. An attribute that does
6//! not belong to the declared kind is an error rather than an ignored
7//! key, because a silently dropped `max_len` is exactly the kind of drift
8//! this crate exists to prevent. `[tables]` is a map keyed by table name,
9//! so the table order of the generated DDL comes from the name and not
10//! from where the author happened to put the section.
11//!
12//! The same shape is JSON, which is what `corpus/rows.json` uses: one
13//! deserializer serves the manifest and the conformance corpus, so a case
14//! in the corpus cannot describe a table the manifest could not declare.
15
16use serde::Deserialize;
17use serde::de::Error as _;
18use serde_json::Value;
19use std::collections::BTreeMap;
20
21use crate::schema::{FieldDef, FieldKind, ForeignKey, Schema, TableDef, TextFormat};
22
23/// `kind = "..."`, the one key every field declaration carries.
24#[derive(Debug, Clone, Copy, Deserialize)]
25#[serde(rename_all = "snake_case")]
26enum KindName {
27    Text,
28    Integer,
29    Real,
30    Boolean,
31    Timestamp,
32    Uuid,
33    Json,
34    Enum,
35}
36
37impl KindName {
38    fn as_str(self) -> &'static str {
39        match self {
40            KindName::Text => "text",
41            KindName::Integer => "integer",
42            KindName::Real => "real",
43            KindName::Boolean => "boolean",
44            KindName::Timestamp => "timestamp",
45            KindName::Uuid => "uuid",
46            KindName::Json => "json",
47            KindName::Enum => "enum",
48        }
49    }
50}
51
52impl<'de> Deserialize<'de> for TextFormat {
53    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
54        let name = String::deserialize(deserializer)?;
55        match name.as_str() {
56            "email" => Ok(TextFormat::Email),
57            "url" => Ok(TextFormat::Url),
58            other => Err(D::Error::custom(format!(
59                "unknown format `{other}`; the formats are email and url"
60            ))),
61        }
62    }
63}
64
65/// `primary_key = "id"` and `primary_key = ["collection", "slug"]` both
66/// parse; a single column is the common case and should not need brackets.
67#[derive(Debug, Deserialize)]
68#[serde(untagged)]
69enum PrimaryKeyWire {
70    One(String),
71    Many(Vec<String>),
72}
73
74impl PrimaryKeyWire {
75    fn into_vec(self) -> Vec<String> {
76        match self {
77            PrimaryKeyWire::One(one) => vec![one],
78            PrimaryKeyWire::Many(many) => many,
79        }
80    }
81}
82
83fn default_primary_key() -> PrimaryKeyWire {
84    PrimaryKeyWire::One("id".to_owned())
85}
86
87#[derive(Debug, Deserialize)]
88#[serde(deny_unknown_fields)]
89struct FieldWire {
90    name: String,
91    kind: KindName,
92    #[serde(default)]
93    required: bool,
94    #[serde(default)]
95    unique: bool,
96    #[serde(default)]
97    indexed: bool,
98    #[serde(default)]
99    default: Option<Value>,
100    #[serde(default)]
101    min_len: Option<u32>,
102    #[serde(default)]
103    max_len: Option<u32>,
104    #[serde(default)]
105    format: Option<TextFormat>,
106    #[serde(default)]
107    min: Option<Value>,
108    #[serde(default)]
109    max: Option<Value>,
110    #[serde(default)]
111    values: Option<Vec<String>>,
112}
113
114impl FieldWire {
115    /// Rejects an attribute the declared kind does not accept, so a
116    /// `max_len` on an integer is an error and not a dropped key.
117    fn reject_unless<E: serde::de::Error>(&self, accepted: &[&str]) -> Result<(), E> {
118        let present: [(&str, bool); 6] = [
119            ("min_len", self.min_len.is_some()),
120            ("max_len", self.max_len.is_some()),
121            ("format", self.format.is_some()),
122            ("min", self.min.is_some()),
123            ("max", self.max.is_some()),
124            ("values", self.values.is_some()),
125        ];
126        for (key, is_set) in present {
127            if is_set && !accepted.contains(&key) {
128                return Err(E::custom(format!(
129                    "field `{}`: `{key}` does not apply to a {} field",
130                    self.name,
131                    self.kind.as_str()
132                )));
133            }
134        }
135        Ok(())
136    }
137
138    fn bound_i64<E: serde::de::Error>(
139        &self,
140        key: &str,
141        value: Option<&Value>,
142    ) -> Result<Option<i64>, E> {
143        match value {
144            None => Ok(None),
145            Some(value) => value.as_i64().map(Some).ok_or_else(|| {
146                E::custom(format!(
147                    "field `{}`: `{key}` must be a whole number, not {value}",
148                    self.name
149                ))
150            }),
151        }
152    }
153
154    fn bound_f64<E: serde::de::Error>(
155        &self,
156        key: &str,
157        value: Option<&Value>,
158    ) -> Result<Option<f64>, E> {
159        match value {
160            None => Ok(None),
161            Some(value) => value
162                .as_f64()
163                .filter(|number| number.is_finite())
164                .map(Some)
165                .ok_or_else(|| {
166                    E::custom(format!(
167                        "field `{}`: `{key}` must be a finite number, not {value}",
168                        self.name
169                    ))
170                }),
171        }
172    }
173
174    fn into_field<E: serde::de::Error>(self) -> Result<FieldDef, E> {
175        let kind = match self.kind {
176            KindName::Text => {
177                self.reject_unless(&["min_len", "max_len", "format"])?;
178                FieldKind::Text {
179                    min_len: self.min_len,
180                    max_len: self.max_len,
181                    format: self.format,
182                }
183            }
184            KindName::Integer => {
185                self.reject_unless(&["min", "max"])?;
186                FieldKind::Integer {
187                    min: self.bound_i64("min", self.min.as_ref())?,
188                    max: self.bound_i64("max", self.max.as_ref())?,
189                }
190            }
191            KindName::Real => {
192                self.reject_unless(&["min", "max"])?;
193                FieldKind::Real {
194                    min: self.bound_f64("min", self.min.as_ref())?,
195                    max: self.bound_f64("max", self.max.as_ref())?,
196                }
197            }
198            KindName::Boolean => {
199                self.reject_unless(&[])?;
200                FieldKind::Boolean
201            }
202            KindName::Timestamp => {
203                self.reject_unless(&[])?;
204                FieldKind::Timestamp
205            }
206            KindName::Uuid => {
207                self.reject_unless(&[])?;
208                FieldKind::Uuid
209            }
210            KindName::Json => {
211                self.reject_unless(&[])?;
212                FieldKind::Json
213            }
214            KindName::Enum => {
215                self.reject_unless(&["values"])?;
216                let values = self.values.clone().ok_or_else(|| {
217                    E::custom(format!(
218                        "field `{}`: an enum field needs a `values` list",
219                        self.name
220                    ))
221                })?;
222                FieldKind::Enum { values }
223            }
224        };
225
226        Ok(FieldDef {
227            name: self.name,
228            kind,
229            required: self.required,
230            unique: self.unique,
231            indexed: self.indexed,
232            default: self.default,
233        })
234    }
235}
236
237impl<'de> Deserialize<'de> for FieldDef {
238    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
239        FieldWire::deserialize(deserializer)?.into_field()
240    }
241}
242
243#[derive(Debug, Deserialize)]
244#[serde(deny_unknown_fields)]
245struct ForeignKeyWire {
246    field: String,
247    references: String,
248}
249
250impl<'de> Deserialize<'de> for ForeignKey {
251    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
252        let wire = ForeignKeyWire::deserialize(deserializer)?;
253        Ok(ForeignKey {
254            field: wire.field,
255            references: wire.references,
256        })
257    }
258}
259
260#[derive(Debug, Deserialize)]
261#[serde(deny_unknown_fields)]
262struct TableWire {
263    /// Required when a table is deserialized on its own (the corpus does
264    /// that); optional under `[tables.<name>]`, where the key is the name
265    /// and an inner `name` must agree with it.
266    #[serde(default)]
267    name: Option<String>,
268    #[serde(default = "default_primary_key")]
269    primary_key: PrimaryKeyWire,
270    fields: Vec<FieldDef>,
271    #[serde(default)]
272    foreign_keys: Vec<ForeignKey>,
273}
274
275impl TableWire {
276    fn into_table<E: serde::de::Error>(self, name: String) -> Result<TableDef, E> {
277        if let Some(inner) = &self.name
278            && inner != &name
279        {
280            return Err(E::custom(format!(
281                "table `{name}`: the inner name `{inner}` does not match the section it is \
282                 declared under"
283            )));
284        }
285        Ok(TableDef {
286            name,
287            fields: self.fields,
288            primary_key: self.primary_key.into_vec(),
289            foreign_keys: self.foreign_keys,
290        })
291    }
292}
293
294impl<'de> Deserialize<'de> for TableDef {
295    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
296        let wire = TableWire::deserialize(deserializer)?;
297        let name = wire
298            .name
299            .clone()
300            .ok_or_else(|| D::Error::custom("a table declared on its own needs a `name`"))?;
301        wire.into_table(name)
302    }
303}
304
305impl<'de> Deserialize<'de> for Schema {
306    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
307        let wires = BTreeMap::<String, TableWire>::deserialize(deserializer)?;
308        let mut tables = Vec::with_capacity(wires.len());
309        for (name, wire) in wires {
310            tables.push(wire.into_table(name)?);
311        }
312        // A BTreeMap already yields its keys in order, so the table order
313        // of the generated DDL is the name order and never the manifest's.
314        Ok(Schema { tables })
315    }
316}
317
318// ---------------------------------------------------------------------------
319// Serialization: the same wire shape, the other way round
320// ---------------------------------------------------------------------------
321//
322// A declaration format that can only be read is half a format. The
323// manifest a control plane writes back out has to round-trip, and the two
324// directions drifting apart is the failure this crate exists to prevent
325// — so these mirror the deserializers above key for key, and
326// `every_shape_the_corpus_declares_round_trips` is what holds them
327// together rather than a promise that they match.
328//
329// Written by hand rather than derived for the same reason the readers
330// are: the wire shape is flat, a field's attributes belong to its kind,
331// and `serde`'s derive has no way to say that.
332
333use serde::Serialize;
334use serde::ser::SerializeMap as _;
335
336impl Serialize for TextFormat {
337    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
338        serializer.serialize_str(match self {
339            TextFormat::Email => "email",
340            TextFormat::Url => "url",
341        })
342    }
343}
344
345impl Serialize for FieldDef {
346    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
347        let mut map = serializer.serialize_map(None)?;
348        map.serialize_entry("name", &self.name)?;
349        map.serialize_entry("kind", self.kind.as_str())?;
350        // Only the attributes the declared kind accepts, because the
351        // reader refuses any other — emitting one would produce a
352        // document this crate could not read back.
353        match &self.kind {
354            FieldKind::Text {
355                min_len,
356                max_len,
357                format,
358            } => {
359                if let Some(min) = min_len {
360                    map.serialize_entry("min_len", min)?;
361                }
362                if let Some(max) = max_len {
363                    map.serialize_entry("max_len", max)?;
364                }
365                if let Some(format) = format {
366                    map.serialize_entry("format", format)?;
367                }
368            }
369            FieldKind::Integer { min, max } => {
370                if let Some(min) = min {
371                    map.serialize_entry("min", min)?;
372                }
373                if let Some(max) = max {
374                    map.serialize_entry("max", max)?;
375                }
376            }
377            FieldKind::Real { min, max } => {
378                if let Some(min) = min {
379                    map.serialize_entry("min", min)?;
380                }
381                if let Some(max) = max {
382                    map.serialize_entry("max", max)?;
383                }
384            }
385            FieldKind::Enum { values } => map.serialize_entry("values", values)?,
386            FieldKind::Boolean | FieldKind::Timestamp | FieldKind::Uuid | FieldKind::Json => {}
387        }
388        // The three flags default to `false` on the way in, so writing
389        // them only when set keeps a round trip byte-identical for the
390        // common field rather than growing three keys per column.
391        if self.required {
392            map.serialize_entry("required", &true)?;
393        }
394        if self.unique {
395            map.serialize_entry("unique", &true)?;
396        }
397        if self.indexed {
398            map.serialize_entry("indexed", &true)?;
399        }
400        if let Some(default) = &self.default {
401            map.serialize_entry("default", default)?;
402        }
403        map.end()
404    }
405}
406
407impl Serialize for ForeignKey {
408    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
409        let mut map = serializer.serialize_map(Some(2))?;
410        map.serialize_entry("field", &self.field)?;
411        map.serialize_entry("references", &self.references)?;
412        map.end()
413    }
414}
415
416impl Serialize for TableDef {
417    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
418        let mut map = serializer.serialize_map(None)?;
419        // A table serialized on its own needs its name; under a `[tables]`
420        // map the key carries it and the reader accepts an inner one that
421        // agrees. Writing it always is the form that reads back both ways.
422        map.serialize_entry("name", &self.name)?;
423        // The reader accepts a string or a list and normalises to a list;
424        // a single-column key goes back out as a string, which is the
425        // form every declaration in the tree actually uses.
426        match self.primary_key.as_slice() {
427            [one] => map.serialize_entry("primary_key", one)?,
428            many => map.serialize_entry("primary_key", many)?,
429        }
430        map.serialize_entry("fields", &self.fields)?;
431        if !self.foreign_keys.is_empty() {
432            map.serialize_entry("foreign_keys", &self.foreign_keys)?;
433        }
434        map.end()
435    }
436}
437
438impl Serialize for Schema {
439    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
440        // Keyed by table name, the shape the reader takes. `Schema::new`
441        // sorts by name, so this is already in order.
442        let mut map = serializer.serialize_map(Some(self.tables.len()))?;
443        for table in &self.tables {
444            map.serialize_entry(&table.name, table)?;
445        }
446        map.end()
447    }
448}