Skip to main content

rich_ext/schema/
model.rs

1//! The format-neutral schema model: named fields with a type, nullability,
2//! constraints and children (0.0.16 workstreams 1 and 4).
3//!
4//! This is the shape every schema source maps into, so tabular data, JSON
5//! Schema, Arrow and SQL DDL can be shown and compared the same way. A
6//! [`Schema`] is a list of [`Field`]s (and, for a SQL file, further named
7//! tables); a field's [`DataType`] carries its children (a struct's
8//! fields, a list's item, a map's key and value). Detail a format has and
9//! the model does not, such as Arrow's `Int32` or SQL's `VARCHAR(20)`, is
10//! kept as the field's [native type](Field::native_type), as written.
11//!
12//! Beside its type a field has [`Constraint`]s: required and nullable,
13//! enum values, bounds, length, pattern, format, a default, primary and
14//! unique keys and a [`ForeignKey`] to `table.column`. JSON Schema's own
15//! structure (pattern-named fields, tuple items, `oneOf` branches,
16//! references) is kept with the [`FieldKind`] of each child and the
17//! [reference](Field::reference) a type was found through, so
18//! [`SchemaTree`](super::SchemaTree) draws any of them from the model.
19//!
20//! ```
21//! use rich_ext::schema::{Constraint, DataType, Field, ForeignKey, Schema};
22//!
23//! let schema = Schema::new([
24//!     Field::new("id", DataType::Integer)
25//!         .nullable(false)
26//!         .required(true)
27//!         .with_constraint(Constraint::PrimaryKey(vec![])),
28//!     Field::new("email", DataType::String).with_constraint(Constraint::Unique(vec![])),
29//!     Field::new("team", DataType::Integer)
30//!         .with_constraint(Constraint::References(ForeignKey::to("teams", "id"))),
31//!     Field::new("tags", DataType::list(DataType::String)),
32//!     Field::new(
33//!         "address",
34//!         DataType::Struct(vec![
35//!             Field::new("city", DataType::String),
36//!             Field::new("zip", DataType::String).with_native_type("char(5)"),
37//!         ]),
38//!     ),
39//! ])
40//! .named("users");
41//! assert_eq!(schema.index_of("email"), Some(1));
42//! assert_eq!(schema.primary_key(), ["id"]);
43//! assert_eq!(schema.foreign_keys()[0].to_string(), "team → teams.id");
44//! assert_eq!(schema.fields()[3].data_type().to_string(), "list<string>");
45//! let address = schema.field("address").unwrap();
46//! let children: Vec<&str> = address.children().map(|f| f.name()).collect();
47//! assert_eq!(children, ["city", "zip"]);
48//! ```
49
50use std::fmt;
51
52/// A field's logical type.
53///
54/// Scalars are the types tabular formats share; [`List`](DataType::List),
55/// [`Struct`](DataType::Struct) and [`Map`](DataType::Map) nest.
56/// [`Any`](DataType::Any) is a type the source declares to accept anything
57/// (JSON Schema's `{}`); [`Unknown`](DataType::Unknown) is one the source did
58/// not say or a reader could not tell (an empty column, a type a mapping does
59/// not know).
60#[derive(Clone, Debug, PartialEq, Eq, Hash)]
61#[non_exhaustive]
62pub enum DataType {
63    /// Accepts any value.
64    Any,
65    /// Not known.
66    Unknown,
67    /// `true` or `false`.
68    Boolean,
69    /// A whole number, of any width or signedness.
70    Integer,
71    /// A binary floating-point number.
72    Float,
73    /// A fixed-point decimal, with its precision (total digits) and scale
74    /// (digits after the point) when the source gives them.
75    Decimal {
76        /// Total digits.
77        precision: Option<u32>,
78        /// Digits after the decimal point.
79        scale: Option<i32>,
80    },
81    /// Text.
82    String,
83    /// Bytes.
84    Binary,
85    /// A calendar date.
86    Date,
87    /// A date and time, with its time zone when the source gives one.
88    Timestamp {
89        /// The time zone, as the source writes it (`UTC`, `+01:00`).
90        timezone: Option<String>,
91    },
92    /// A sequence of items, each described by the field.
93    List(Box<Field>),
94    /// Named fields.
95    Struct(Vec<Field>),
96    /// Keys to values.
97    Map {
98        /// The key field.
99        key: Box<Field>,
100        /// The value field.
101        value: Box<Field>,
102    },
103}
104
105impl DataType {
106    /// A list of nullable `item`s, under the conventional name `item`.
107    pub fn list(item: DataType) -> Self {
108        DataType::List(Box::new(Field::new("item", item)))
109    }
110
111    /// A map from non-null `key`s to nullable `value`s, under the
112    /// conventional names `key` and `value`.
113    pub fn map(key: DataType, value: DataType) -> Self {
114        DataType::Map {
115            key: Box::new(Field::new("key", key).nullable(false)),
116            value: Box::new(Field::new("value", value)),
117        }
118    }
119
120    /// A timestamp without a time zone.
121    pub fn timestamp() -> Self {
122        DataType::Timestamp { timezone: None }
123    }
124
125    /// A decimal without a stated precision or scale.
126    pub fn decimal() -> Self {
127        DataType::Decimal {
128            precision: None,
129            scale: None,
130        }
131    }
132
133    /// Whether values of this type are numbers (integer, float or decimal).
134    pub fn is_numeric(&self) -> bool {
135        matches!(
136            self,
137            DataType::Integer | DataType::Float | DataType::Decimal { .. }
138        )
139    }
140
141    /// Whether this type nests other fields (list, struct or map).
142    pub fn is_nested(&self) -> bool {
143        matches!(
144            self,
145            DataType::List(_) | DataType::Struct(_) | DataType::Map { .. }
146        )
147    }
148
149    /// The fields this type nests: a struct's fields, a list's item, a map's
150    /// key and value. Empty for scalars.
151    pub fn children(&self) -> impl Iterator<Item = &Field> {
152        let (slice, pair): (&[Field], [Option<&Field>; 2]) = match self {
153            DataType::Struct(fields) => (fields, [None, None]),
154            DataType::List(item) => (&[], [Some(item), None]),
155            DataType::Map { key, value } => (&[], [Some(key), Some(value)]),
156            _ => (&[], [None, None]),
157        };
158        slice.iter().chain(pair.into_iter().flatten())
159    }
160}
161
162/// The lowercase name: `integer`, `decimal(10,2)`, `timestamp[UTC]`,
163/// `list<string>`, `map<string, integer>`, `struct`.
164impl fmt::Display for DataType {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        match self {
167            DataType::Any => f.write_str("any"),
168            DataType::Unknown => f.write_str("unknown"),
169            DataType::Boolean => f.write_str("boolean"),
170            DataType::Integer => f.write_str("integer"),
171            DataType::Float => f.write_str("float"),
172            DataType::Decimal { precision, scale } => match (precision, scale) {
173                (Some(p), Some(s)) => write!(f, "decimal({p},{s})"),
174                (Some(p), None) => write!(f, "decimal({p})"),
175                _ => f.write_str("decimal"),
176            },
177            DataType::String => f.write_str("string"),
178            DataType::Binary => f.write_str("binary"),
179            DataType::Date => f.write_str("date"),
180            DataType::Timestamp { timezone: None } => f.write_str("timestamp"),
181            DataType::Timestamp { timezone: Some(tz) } => write!(f, "timestamp[{tz}]"),
182            DataType::List(item) => write!(f, "list<{}>", item.data_type),
183            DataType::Struct(_) => f.write_str("struct"),
184            DataType::Map { key, value } => {
185                write!(f, "map<{}, {}>", key.data_type, value.data_type)
186            }
187        }
188    }
189}
190
191/// A value as the source writes it: JSON (`"a"`, `5`, `true`), or a SQL
192/// literal (`'a'`, `CURRENT_TIMESTAMP`).
193///
194/// Kept as text so every format's values compare and show the same way;
195/// [`as_f64`](Literal::as_f64) reads a number back for comparisons.
196#[derive(Clone, Debug, PartialEq, Eq, Hash)]
197pub struct Literal(String);
198
199impl Literal {
200    /// A literal written `text`.
201    pub fn new(text: impl Into<String>) -> Self {
202        Literal(text.into())
203    }
204
205    /// A JSON value, written compactly.
206    pub fn json(value: &serde_json::Value) -> Self {
207        Literal(value.to_string())
208    }
209
210    /// The text.
211    pub fn as_str(&self) -> &str {
212        &self.0
213    }
214
215    /// The value as a number, when it is written as one.
216    pub fn as_f64(&self) -> Option<f64> {
217        let text = self.0.trim();
218        // Rust reads `inf` and `NaN` as numbers; no format here writes one so.
219        let numeric = !text.is_empty()
220            && text
221                .chars()
222                .all(|c| c.is_ascii_digit() || matches!(c, '-' | '+' | '.' | 'e' | 'E'));
223        if numeric { text.parse().ok() } else { None }
224    }
225
226    /// The text, cut to 40 characters with `…`.
227    pub fn short(&self) -> String {
228        if self.0.chars().count() > 40 {
229            let cut: String = self.0.chars().take(39).collect();
230            format!("{cut}…")
231        } else {
232            self.0.clone()
233        }
234    }
235}
236
237impl fmt::Display for Literal {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        f.write_str(&self.0)
240    }
241}
242
243/// A foreign key: columns of one table that refer to columns of another.
244#[derive(Clone, Debug, PartialEq, Eq, Hash)]
245pub struct ForeignKey {
246    /// This table's columns; empty on a column's own constraint, where the
247    /// column is the one.
248    pub columns: Vec<String>,
249    /// The table referred to.
250    pub table: String,
251    /// Its columns; empty when the source names only the table (its
252    /// primary key).
253    pub references: Vec<String>,
254}
255
256impl ForeignKey {
257    /// A column's reference to `table.column`.
258    pub fn to(table: impl Into<String>, column: impl Into<String>) -> Self {
259        ForeignKey {
260            columns: Vec::new(),
261            table: table.into(),
262            references: vec![column.into()],
263        }
264    }
265}
266
267/// `users.id`, or `(a, b) → t.(x, y)` with this table's columns.
268impl fmt::Display for ForeignKey {
269    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270        let list = |names: &[String]| match names {
271            [one] => one.clone(),
272            many => format!("({})", many.join(", ")),
273        };
274        if !self.columns.is_empty() {
275            write!(f, "{} → ", list(&self.columns))?;
276        }
277        if self.references.is_empty() {
278            f.write_str(&self.table)
279        } else {
280            write!(f, "{}.{}", self.table, list(&self.references))
281        }
282    }
283}
284
285/// A rule a field's values follow, beside its type.
286///
287/// Each has a [key](Constraint::key) two versions are compared by, and
288/// shows itself as the schema tree writes it ([`Display`](fmt::Display),
289/// empty for a value that says nothing, such as `uniqueItems: false`).
290/// Bounds and flags keep the source's [`Literal`], so `minimum: 1.5` and
291/// `minimum: 2` compare as numbers and show as written.
292#[derive(Clone, Debug, PartialEq, Eq, Hash)]
293#[non_exhaustive]
294pub enum Constraint {
295    /// Exactly this value (`= 5`).
296    Const(Literal),
297    /// One of these values.
298    Enum(Vec<Literal>),
299    /// A named format (`email`, `date`).
300    Format(String),
301    /// A regular expression the text matches.
302    Pattern(String),
303    /// The fewest characters.
304    MinLength(Literal),
305    /// The most characters.
306    MaxLength(Literal),
307    /// The smallest value.
308    Minimum(Literal),
309    /// Values are above this.
310    ExclusiveMinimum(Literal),
311    /// The largest value.
312    Maximum(Literal),
313    /// Values are below this.
314    ExclusiveMaximum(Literal),
315    /// Values are a multiple of this.
316    MultipleOf(Literal),
317    /// The fewest items.
318    MinItems(Literal),
319    /// The most items.
320    MaxItems(Literal),
321    /// Items are distinct (shown when `true`).
322    UniqueItems(Literal),
323    /// The value used when none is given.
324    Default(Literal),
325    /// The field is deprecated (shown when `true`).
326    Deprecated(Literal),
327    /// The field is read-only (shown when `true`).
328    ReadOnly(Literal),
329    /// The field is write-only (shown when `true`).
330    WriteOnly(Literal),
331    /// Whether fields other than those listed are allowed: `false` refuses
332    /// them and `true` allows them; a schema (JSON Schema's
333    /// `additionalProperties: {…}`) describes them, and is also a child of
334    /// kind [`FieldKind::Additional`].
335    Additional(Literal),
336    /// The primary key: on a field, the field itself (no names); on a
337    /// table, the key's columns.
338    PrimaryKey(Vec<String>),
339    /// Unique values: on a field, its own (no names); on a table, across
340    /// the named columns.
341    Unique(Vec<String>),
342    /// Values refer to another table's.
343    References(ForeignKey),
344    /// Anything else, by the source's own keyword (`minContains=1`).
345    Other {
346        /// The source's keyword.
347        name: String,
348        /// Its value.
349        value: Literal,
350    },
351}
352
353impl Constraint {
354    /// The key two versions are compared by: the JSON Schema keyword where
355    /// there is one (`minLength`, `additionalProperties`), else a name of
356    /// its own (`primaryKey`, `unique`, `references`).
357    pub fn key(&self) -> &str {
358        match self {
359            Constraint::Const(_) => "const",
360            Constraint::Enum(_) => "enum",
361            Constraint::Format(_) => "format",
362            Constraint::Pattern(_) => "pattern",
363            Constraint::MinLength(_) => "minLength",
364            Constraint::MaxLength(_) => "maxLength",
365            Constraint::Minimum(_) => "minimum",
366            Constraint::ExclusiveMinimum(_) => "exclusiveMinimum",
367            Constraint::Maximum(_) => "maximum",
368            Constraint::ExclusiveMaximum(_) => "exclusiveMaximum",
369            Constraint::MultipleOf(_) => "multipleOf",
370            Constraint::MinItems(_) => "minItems",
371            Constraint::MaxItems(_) => "maxItems",
372            Constraint::UniqueItems(_) => "uniqueItems",
373            Constraint::Default(_) => "default",
374            Constraint::Deprecated(_) => "deprecated",
375            Constraint::ReadOnly(_) => "readOnly",
376            Constraint::WriteOnly(_) => "writeOnly",
377            Constraint::Additional(_) => "additionalProperties",
378            Constraint::PrimaryKey(_) => "primaryKey",
379            Constraint::Unique(_) => "unique",
380            Constraint::References(_) => "references",
381            Constraint::Other { name, .. } => name,
382        }
383    }
384
385    /// The literal a bound or flag holds.
386    pub fn literal(&self) -> Option<&Literal> {
387        match self {
388            Constraint::Const(v)
389            | Constraint::MinLength(v)
390            | Constraint::MaxLength(v)
391            | Constraint::Minimum(v)
392            | Constraint::ExclusiveMinimum(v)
393            | Constraint::Maximum(v)
394            | Constraint::ExclusiveMaximum(v)
395            | Constraint::MultipleOf(v)
396            | Constraint::MinItems(v)
397            | Constraint::MaxItems(v)
398            | Constraint::UniqueItems(v)
399            | Constraint::Default(v)
400            | Constraint::Deprecated(v)
401            | Constraint::ReadOnly(v)
402            | Constraint::WriteOnly(v)
403            | Constraint::Additional(v)
404            | Constraint::Other { value: v, .. } => Some(v),
405            _ => None,
406        }
407    }
408
409    /// The constraint's name as a change list writes it (`maxLength`,
410    /// `additional properties`, `primary key`).
411    pub fn name(&self) -> &str {
412        match self {
413            Constraint::Additional(_) => "additional properties",
414            Constraint::PrimaryKey(_) => "primary key",
415            other => other.key(),
416        }
417    }
418
419    /// The value as a change list writes it: `5`, `/^a/`, `email`,
420    /// `refused`, `users.id`; empty for a field's own key or `unique`.
421    pub fn value_text(&self) -> String {
422        match self {
423            Constraint::Format(format) => format.clone(),
424            Constraint::Pattern(pattern) => format!("/{pattern}/"),
425            Constraint::Enum(values) => values
426                .iter()
427                .map(Literal::short)
428                .collect::<Vec<_>>()
429                .join(", "),
430            Constraint::Additional(v) => match v.as_str() {
431                "false" => "refused".into(),
432                "true" => "allowed".into(),
433                _ => v.short(),
434            },
435            Constraint::PrimaryKey(columns) | Constraint::Unique(columns) => {
436                if columns.is_empty() {
437                    String::new()
438                } else {
439                    format!("({})", columns.join(", "))
440                }
441            }
442            Constraint::References(key) => key.to_string(),
443            other => other.literal().map(Literal::short).unwrap_or_default(),
444        }
445    }
446}
447
448/// As the schema tree writes it: `minLength=1`, `pattern=/^a/`,
449/// `one of "a", "b"`, `primary key`, `→ users.id`. Empty when there is
450/// nothing to show (`uniqueItems: false`).
451impl fmt::Display for Constraint {
452    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
453        let flag = |v: &Literal| v.as_str() == "true";
454        match self {
455            Constraint::Const(v) => write!(f, "= {}", v.short()),
456            Constraint::Enum(values) => {
457                let shown: Vec<String> = values.iter().take(8).map(Literal::short).collect();
458                write!(f, "one of {}", shown.join(", "))?;
459                if values.len() > 8 {
460                    write!(f, ", … {} more", values.len() - 8)?;
461                }
462                Ok(())
463            }
464            Constraint::Format(format) => write!(f, "format={format}"),
465            Constraint::Pattern(pattern) => write!(f, "pattern=/{pattern}/"),
466            Constraint::UniqueItems(v) if flag(v) => f.write_str("unique items"),
467            Constraint::Deprecated(v) if flag(v) => f.write_str("deprecated"),
468            Constraint::ReadOnly(v) if flag(v) => f.write_str("read-only"),
469            Constraint::WriteOnly(v) if flag(v) => f.write_str("write-only"),
470            Constraint::UniqueItems(_)
471            | Constraint::Deprecated(_)
472            | Constraint::ReadOnly(_)
473            | Constraint::WriteOnly(_) => Ok(()),
474            Constraint::Additional(v) if v.as_str() == "false" => {
475                f.write_str("no other properties")
476            }
477            Constraint::Additional(_) => Ok(()),
478            Constraint::PrimaryKey(columns) if columns.is_empty() => f.write_str("primary key"),
479            Constraint::PrimaryKey(columns) => write!(f, "primary key ({})", columns.join(", ")),
480            Constraint::Unique(columns) if columns.is_empty() => f.write_str("unique"),
481            Constraint::Unique(columns) => write!(f, "unique ({})", columns.join(", ")),
482            Constraint::References(key) => write!(f, "→ {key}"),
483            other => write!(
484                f,
485                "{}={}",
486                other.key(),
487                other.literal().map(Literal::short).unwrap_or_default()
488            ),
489        }
490    }
491}
492
493/// The composition a branch belongs to.
494#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
495pub enum Composition {
496    /// Exactly one branch holds.
497    OneOf,
498    /// At least one holds.
499    AnyOf,
500    /// All hold.
501    AllOf,
502}
503
504impl Composition {
505    /// All three, in the order a tree shows them.
506    pub const ALL: [Composition; 3] = [Composition::OneOf, Composition::AnyOf, Composition::AllOf];
507
508    /// `one of`, `any of`, `all of`.
509    pub fn title(self) -> &'static str {
510        match self {
511            Composition::OneOf => "one of",
512            Composition::AnyOf => "any of",
513            Composition::AllOf => "all of",
514        }
515    }
516}
517
518/// How a nested field relates to the field it is under.
519///
520/// Most are plain [`Field`](FieldKind::Field)s: a struct's fields, a list's
521/// item, a map's key and value. JSON Schema adds the rest: fields named by
522/// a pattern, the schema for any other field, tuple and array items,
523/// `oneOf`/`anyOf`/`allOf` branches (in a [`Group`](FieldKind::Group),
524/// unless the parent is nothing but its branches), and `not`, `if`, `then`
525/// and `else`.
526#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
527#[non_exhaustive]
528pub enum FieldKind {
529    /// A named field.
530    #[default]
531    Field,
532    /// Fields whose names match a pattern (the field's name, `/^x_/`).
533    Pattern,
534    /// Any field not otherwise listed (`[other properties]`).
535    Additional,
536    /// One position of a tuple (`[0]`).
537    TupleItem,
538    /// Every item of an array (`[items]`).
539    Items,
540    /// A group of branches (`one of`), whose children are the branches.
541    Group(Composition),
542    /// One branch (`[1] Card`).
543    Branch(Composition),
544    /// A condition, named by its keyword (`not`, `if`, `then`, `else`).
545    Condition,
546}
547
548/// Why a type named by a reference is not drawn in place.
549#[derive(Clone, Debug, PartialEq, Eq, Hash)]
550#[non_exhaustive]
551pub enum Unexpanded {
552    /// The reference names a type the field is already inside.
553    Recursive(String),
554    /// The reference could not be followed.
555    Unresolved {
556        /// The reference, as written.
557        reference: String,
558        /// Why (`another document`, `not found`).
559        reason: String,
560    },
561}
562
563impl Unexpanded {
564    /// The reference, as written.
565    pub fn reference(&self) -> &str {
566        match self {
567            Unexpanded::Recursive(reference) => reference,
568            Unexpanded::Unresolved { reference, .. } => reference,
569        }
570    }
571}
572
573/// `→ #/$defs/node (recursive)`, `→ other.json (another document)`.
574impl fmt::Display for Unexpanded {
575    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
576        match self {
577            Unexpanded::Recursive(reference) => write!(f, "→ {reference} (recursive)"),
578            Unexpanded::Unresolved { reference, reason } => write!(f, "→ {reference} ({reason})"),
579        }
580    }
581}
582
583/// One named field: a type, whether it may be null and whether it must be
584/// present, its constraints, and optionally the source's own name for the
585/// type, a description and metadata.
586#[derive(Clone, Debug, PartialEq, Eq, Hash)]
587#[must_use = "builder methods return a new value; it does nothing until it is rendered or used"]
588pub struct Field {
589    name: String,
590    data_type: DataType,
591    nullable: bool,
592    required: bool,
593    native_type: Option<String>,
594    description: Option<String>,
595    constraints: Vec<Constraint>,
596    metadata: Vec<(String, String)>,
597    kind: FieldKind,
598    reference: Option<String>,
599    unexpanded: Option<Unexpanded>,
600    elided: bool,
601}
602
603impl Field {
604    /// A nullable, optional field.
605    pub fn new(name: impl Into<String>, data_type: DataType) -> Self {
606        Field {
607            name: name.into(),
608            data_type,
609            nullable: true,
610            required: false,
611            native_type: None,
612            description: None,
613            constraints: Vec::new(),
614            metadata: Vec::new(),
615            kind: FieldKind::Field,
616            reference: None,
617            unexpanded: None,
618            elided: false,
619        }
620    }
621
622    /// Whether the field may be null (default `true`).
623    pub fn nullable(mut self, nullable: bool) -> Self {
624        self.nullable = nullable;
625        self
626    }
627
628    /// Whether a value must be present (default `false`): JSON Schema's
629    /// `required`, SQL's `NOT NULL`, a non-nullable Arrow field.
630    pub fn required(mut self, required: bool) -> Self {
631        self.required = required;
632        self
633    }
634
635    /// The source format's own name for the type, kept as written
636    /// (`Int32`, `VARCHAR(20)`, `string | null`). A schema tree shows it in
637    /// place of the model's type.
638    pub fn with_native_type(mut self, native: impl Into<String>) -> Self {
639        self.native_type = Some(native.into());
640        self
641    }
642
643    /// A description, as the source gives it.
644    pub fn with_description(mut self, description: impl Into<String>) -> Self {
645        self.description = Some(description.into());
646        self
647    }
648
649    /// Replace the type.
650    pub fn with_type(mut self, data_type: DataType) -> Self {
651        self.data_type = data_type;
652        self
653    }
654
655    /// Add a constraint.
656    pub fn with_constraint(mut self, constraint: Constraint) -> Self {
657        self.constraints.push(constraint);
658        self
659    }
660
661    /// Add a metadata entry (Arrow's field metadata).
662    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
663        self.metadata.push((key.into(), value.into()));
664        self
665    }
666
667    /// How the field relates to its parent (default [`FieldKind::Field`]).
668    pub fn with_kind(mut self, kind: FieldKind) -> Self {
669        self.kind = kind;
670        self
671    }
672
673    /// The named definition the type was found through (`#/$defs/item`).
674    pub fn with_reference(mut self, reference: impl Into<String>) -> Self {
675        self.reference = Some(reference.into());
676        self
677    }
678
679    /// Mark the type as named by a reference that is not drawn in place.
680    pub fn with_unexpanded(mut self, unexpanded: Unexpanded) -> Self {
681        self.unexpanded = Some(unexpanded);
682        self
683    }
684
685    /// Mark the children as left out, at a depth limit.
686    pub fn elided(mut self, elided: bool) -> Self {
687        self.elided = elided;
688        self
689    }
690
691    /// The name.
692    pub fn name(&self) -> &str {
693        &self.name
694    }
695
696    /// The type.
697    pub fn data_type(&self) -> &DataType {
698        &self.data_type
699    }
700
701    /// The type, to change in place.
702    pub fn data_type_mut(&mut self) -> &mut DataType {
703        &mut self.data_type
704    }
705
706    /// Whether the field may be null.
707    pub fn is_nullable(&self) -> bool {
708        self.nullable
709    }
710
711    /// Whether a value must be present.
712    pub fn is_required(&self) -> bool {
713        self.required
714    }
715
716    /// The source's own type name, if one was recorded.
717    pub fn native_type(&self) -> Option<&str> {
718        self.native_type.as_deref()
719    }
720
721    /// The type as a tree shows it: the native type, else the model's.
722    pub fn type_label(&self) -> String {
723        self.native_type
724            .clone()
725            .unwrap_or_else(|| self.data_type.to_string())
726    }
727
728    /// The description, if any.
729    pub fn description(&self) -> Option<&str> {
730        self.description.as_deref()
731    }
732
733    /// The constraints, in the order the source gives them.
734    pub fn constraints(&self) -> &[Constraint] {
735        &self.constraints
736    }
737
738    /// The constraints, to change in place.
739    pub fn constraints_mut(&mut self) -> &mut Vec<Constraint> {
740        &mut self.constraints
741    }
742
743    /// The first constraint with `key` ([`Constraint::key`]).
744    pub fn constraint(&self, key: &str) -> Option<&Constraint> {
745        self.constraints.iter().find(|c| c.key() == key)
746    }
747
748    /// The metadata entries, in order.
749    pub fn metadata(&self) -> &[(String, String)] {
750        &self.metadata
751    }
752
753    /// The listed values, when the field is an enum.
754    pub fn enum_values(&self) -> Option<&[Literal]> {
755        self.constraints.iter().find_map(|c| match c {
756            Constraint::Enum(values) => Some(values.as_slice()),
757            _ => None,
758        })
759    }
760
761    /// The default value, if any.
762    pub fn default_value(&self) -> Option<&Literal> {
763        self.constraints.iter().find_map(|c| match c {
764            Constraint::Default(value) => Some(value),
765            _ => None,
766        })
767    }
768
769    /// Whether the field is (part of) the primary key.
770    pub fn is_primary_key(&self) -> bool {
771        self.constraints
772            .iter()
773            .any(|c| matches!(c, Constraint::PrimaryKey(_)))
774    }
775
776    /// Whether the field's values are unique on their own.
777    pub fn is_unique(&self) -> bool {
778        self.constraints
779            .iter()
780            .any(|c| matches!(c, Constraint::Unique(columns) if columns.is_empty()))
781    }
782
783    /// What the field refers to, if it is a foreign key.
784    pub fn references(&self) -> Option<&ForeignKey> {
785        self.constraints.iter().find_map(|c| match c {
786            Constraint::References(key) => Some(key),
787            _ => None,
788        })
789    }
790
791    /// How the field relates to its parent.
792    pub fn kind(&self) -> FieldKind {
793        self.kind
794    }
795
796    /// The named definition the type was found through.
797    pub fn reference(&self) -> Option<&str> {
798        self.reference.as_deref()
799    }
800
801    /// Why the type is not drawn in place, when it is not.
802    pub fn unexpanded(&self) -> Option<&Unexpanded> {
803        self.unexpanded.as_ref()
804    }
805
806    /// Whether the children were left out at a depth limit.
807    pub fn is_elided(&self) -> bool {
808        self.elided
809    }
810
811    /// The nested fields ([`DataType::children`]).
812    pub fn children(&self) -> impl Iterator<Item = &Field> {
813        self.data_type.children()
814    }
815}
816
817/// An ordered list of top-level fields, optionally named (a table, a
818/// record type, a document), and the further tables a schema of several
819/// holds (SQL DDL's `CREATE TABLE`s).
820///
821/// Keys that span columns (a composite primary key, `UNIQUE (a, b)`, a
822/// `FOREIGN KEY`) are the schema's own [constraints](Schema::constraints);
823/// a key on one column is that field's.
824#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
825#[must_use = "builder methods return a new value; it does nothing until it is rendered or used"]
826pub struct Schema {
827    name: Option<String>,
828    description: Option<String>,
829    fields: Vec<Field>,
830    constraints: Vec<Constraint>,
831    tables: Vec<Schema>,
832    metadata: Vec<(String, String)>,
833    truncated: bool,
834}
835
836impl Schema {
837    /// A schema of `fields`, in order.
838    pub fn new(fields: impl IntoIterator<Item = Field>) -> Self {
839        Schema {
840            fields: fields.into_iter().collect(),
841            ..Schema::default()
842        }
843    }
844
845    /// A schema of `tables`, with no fields of its own.
846    pub fn of_tables(tables: impl IntoIterator<Item = Schema>) -> Self {
847        Schema {
848            tables: tables.into_iter().collect(),
849            ..Schema::default()
850        }
851    }
852
853    /// Name the schema (a table or type name).
854    pub fn named(mut self, name: impl Into<String>) -> Self {
855        self.name = Some(name.into());
856        self
857    }
858
859    /// Describe the schema.
860    pub fn with_description(mut self, description: impl Into<String>) -> Self {
861        self.description = Some(description.into());
862        self
863    }
864
865    /// Add a table-level constraint.
866    pub fn with_constraint(mut self, constraint: Constraint) -> Self {
867        self.constraints.push(constraint);
868        self
869    }
870
871    /// Add a metadata entry (Arrow's schema metadata).
872    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
873        self.metadata.push((key.into(), value.into()));
874        self
875    }
876
877    /// Mark the schema as cut short by a reader's limit.
878    pub fn truncated(mut self, truncated: bool) -> Self {
879        self.truncated = truncated;
880        self
881    }
882
883    /// The name, if any.
884    pub fn name(&self) -> Option<&str> {
885        self.name.as_deref()
886    }
887
888    /// The description, if any.
889    pub fn description(&self) -> Option<&str> {
890        self.description.as_deref()
891    }
892
893    /// The top-level fields, in order.
894    pub fn fields(&self) -> &[Field] {
895        &self.fields
896    }
897
898    /// The top-level fields, to change in place.
899    pub fn fields_mut(&mut self) -> &mut Vec<Field> {
900        &mut self.fields
901    }
902
903    /// Append a field.
904    pub fn push(&mut self, field: Field) -> &mut Self {
905        self.fields.push(field);
906        self
907    }
908
909    /// The first top-level field called `name`.
910    pub fn field(&self, name: &str) -> Option<&Field> {
911        self.fields.iter().find(|f| f.name == name)
912    }
913
914    /// The position of the first top-level field called `name`.
915    pub fn index_of(&self, name: &str) -> Option<usize> {
916        self.fields.iter().position(|f| f.name == name)
917    }
918
919    /// The number of top-level fields.
920    pub fn len(&self) -> usize {
921        self.fields.len()
922    }
923
924    /// Whether there are no fields.
925    pub fn is_empty(&self) -> bool {
926        self.fields.is_empty()
927    }
928
929    /// The table-level constraints.
930    pub fn constraints(&self) -> &[Constraint] {
931        &self.constraints
932    }
933
934    /// The table-level constraints, to change in place.
935    pub fn constraints_mut(&mut self) -> &mut Vec<Constraint> {
936        &mut self.constraints
937    }
938
939    /// The primary key's columns: the table's key when it spans columns,
940    /// else every field marked as one.
941    pub fn primary_key(&self) -> Vec<&str> {
942        let table = self.constraints.iter().find_map(|c| match c {
943            Constraint::PrimaryKey(columns) if !columns.is_empty() => Some(columns),
944            _ => None,
945        });
946        match table {
947            Some(columns) => columns.iter().map(String::as_str).collect(),
948            None => self
949                .fields
950                .iter()
951                .filter(|f| f.is_primary_key())
952                .map(Field::name)
953                .collect(),
954        }
955    }
956
957    /// Every foreign key: the table's, then each column's (with the column
958    /// filled in).
959    pub fn foreign_keys(&self) -> Vec<ForeignKey> {
960        let table = self.constraints.iter().filter_map(|c| match c {
961            Constraint::References(key) => Some(key.clone()),
962            _ => None,
963        });
964        let columns = self.fields.iter().filter_map(|f| {
965            f.references().map(|key| ForeignKey {
966                columns: vec![f.name.clone()],
967                ..key.clone()
968            })
969        });
970        table.chain(columns).collect()
971    }
972
973    /// The further tables the schema holds.
974    pub fn tables(&self) -> &[Schema] {
975        &self.tables
976    }
977
978    /// The further tables, to change in place.
979    pub fn tables_mut(&mut self) -> &mut Vec<Schema> {
980        &mut self.tables
981    }
982
983    /// Add a table.
984    pub fn push_table(&mut self, table: Schema) -> &mut Self {
985        self.tables.push(table);
986        self
987    }
988
989    /// The table called `name`.
990    pub fn table(&self, name: &str) -> Option<&Schema> {
991        self.tables.iter().find(|t| t.name() == Some(name))
992    }
993
994    /// The metadata entries, in order.
995    pub fn metadata(&self) -> &[(String, String)] {
996        &self.metadata
997    }
998
999    /// Whether a reader's limit cut the schema short.
1000    pub fn is_truncated(&self) -> bool {
1001        self.truncated
1002    }
1003}
1004
1005impl FromIterator<Field> for Schema {
1006    fn from_iter<I: IntoIterator<Item = Field>>(iter: I) -> Self {
1007        Schema::new(iter)
1008    }
1009}
1010
1011#[cfg(test)]
1012mod tests {
1013    use super::*;
1014
1015    #[test]
1016    fn types_display_with_their_parameters() {
1017        let cases = [
1018            (DataType::Any, "any"),
1019            (DataType::Unknown, "unknown"),
1020            (DataType::Boolean, "boolean"),
1021            (DataType::Float, "float"),
1022            (DataType::decimal(), "decimal"),
1023            (
1024                DataType::Decimal {
1025                    precision: Some(10),
1026                    scale: Some(2),
1027                },
1028                "decimal(10,2)",
1029            ),
1030            (
1031                DataType::Decimal {
1032                    precision: Some(38),
1033                    scale: None,
1034                },
1035                "decimal(38)",
1036            ),
1037            (DataType::Binary, "binary"),
1038            (DataType::Date, "date"),
1039            (DataType::timestamp(), "timestamp"),
1040            (
1041                DataType::Timestamp {
1042                    timezone: Some("UTC".into()),
1043                },
1044                "timestamp[UTC]",
1045            ),
1046            (
1047                DataType::list(DataType::list(DataType::Integer)),
1048                "list<list<integer>>",
1049            ),
1050            (
1051                DataType::map(DataType::String, DataType::Float),
1052                "map<string, float>",
1053            ),
1054            (DataType::Struct(vec![]), "struct"),
1055        ];
1056        for (data_type, expected) in cases {
1057            assert_eq!(data_type.to_string(), expected);
1058        }
1059    }
1060
1061    #[test]
1062    fn children_cover_structs_lists_and_maps() {
1063        let map = DataType::map(DataType::String, DataType::Integer);
1064        let names: Vec<&str> = map.children().map(Field::name).collect();
1065        assert_eq!(names, ["key", "value"]);
1066        assert!(!map.children().next().unwrap().is_nullable());
1067        let list = DataType::list(DataType::Date);
1068        assert_eq!(list.children().count(), 1);
1069        assert_eq!(DataType::String.children().count(), 0);
1070        assert!(DataType::Struct(vec![]).is_nested());
1071        assert!(DataType::decimal().is_numeric());
1072        assert!(!DataType::String.is_numeric());
1073    }
1074
1075    #[test]
1076    fn fields_carry_native_types_and_descriptions() {
1077        let field = Field::new("id", DataType::Integer)
1078            .nullable(false)
1079            .with_native_type("Int32")
1080            .with_description("primary key");
1081        assert_eq!(field.name(), "id");
1082        assert!(!field.is_nullable());
1083        assert_eq!(field.native_type(), Some("Int32"));
1084        assert_eq!(field.description(), Some("primary key"));
1085        let schema: Schema = [field.clone()].into_iter().collect();
1086        let schema = schema.named("users");
1087        assert_eq!(schema.name(), Some("users"));
1088        assert_eq!(schema.field("id"), Some(&field));
1089        assert_eq!(schema.index_of("missing"), None);
1090        assert_eq!(schema.len(), 1);
1091        assert!(!schema.is_empty());
1092    }
1093
1094    #[test]
1095    fn constraints_show_compare_and_name_themselves() {
1096        let lit = Literal::new;
1097        let cases = [
1098            (Constraint::Const(lit("5")), "= 5", "const", "5"),
1099            (
1100                Constraint::Enum(vec![lit("\"a\""), lit("1")]),
1101                "one of \"a\", 1",
1102                "enum",
1103                "\"a\", 1",
1104            ),
1105            (
1106                Constraint::Format("email".into()),
1107                "format=email",
1108                "format",
1109                "email",
1110            ),
1111            (
1112                Constraint::Pattern("^a".into()),
1113                "pattern=/^a/",
1114                "pattern",
1115                "/^a/",
1116            ),
1117            (
1118                Constraint::MinLength(lit("1")),
1119                "minLength=1",
1120                "minLength",
1121                "1",
1122            ),
1123            (
1124                Constraint::UniqueItems(lit("true")),
1125                "unique items",
1126                "uniqueItems",
1127                "true",
1128            ),
1129            (
1130                Constraint::UniqueItems(lit("false")),
1131                "",
1132                "uniqueItems",
1133                "false",
1134            ),
1135            (
1136                Constraint::Deprecated(lit("true")),
1137                "deprecated",
1138                "deprecated",
1139                "true",
1140            ),
1141            (
1142                Constraint::Additional(lit("false")),
1143                "no other properties",
1144                "additional properties",
1145                "refused",
1146            ),
1147            (
1148                Constraint::Additional(lit("true")),
1149                "",
1150                "additional properties",
1151                "allowed",
1152            ),
1153            (
1154                Constraint::PrimaryKey(vec![]),
1155                "primary key",
1156                "primary key",
1157                "",
1158            ),
1159            (
1160                Constraint::PrimaryKey(vec!["a".into(), "b".into()]),
1161                "primary key (a, b)",
1162                "primary key",
1163                "(a, b)",
1164            ),
1165            (Constraint::Unique(vec![]), "unique", "unique", ""),
1166            (
1167                Constraint::References(ForeignKey::to("users", "id")),
1168                "→ users.id",
1169                "references",
1170                "users.id",
1171            ),
1172            (
1173                Constraint::Other {
1174                    name: "minContains".into(),
1175                    value: lit("2"),
1176                },
1177                "minContains=2",
1178                "minContains",
1179                "2",
1180            ),
1181        ];
1182        for (constraint, shown, name, value) in cases {
1183            assert_eq!(constraint.to_string(), shown, "{constraint:?}");
1184            assert_eq!(constraint.name(), name, "{constraint:?}");
1185            assert_eq!(constraint.value_text(), value, "{constraint:?}");
1186        }
1187        let long = Literal::new("x".repeat(50));
1188        assert_eq!(long.short().chars().count(), 40);
1189        assert!(long.short().ends_with('…'));
1190        assert_eq!(Literal::new("1.5e3").as_f64(), Some(1500.0));
1191        assert_eq!(Literal::new("\"3\"").as_f64(), None);
1192        assert_eq!(Literal::new("inf").as_f64(), None);
1193        assert_eq!(
1194            Literal::json(&serde_json::json!({"a": [1]})).as_str(),
1195            r#"{"a":[1]}"#
1196        );
1197    }
1198
1199    #[test]
1200    fn schemas_hold_tables_and_keys() {
1201        let line = Schema::new([
1202            Field::new("order", DataType::Integer)
1203                .with_constraint(Constraint::References(ForeignKey::to("orders", "id"))),
1204            Field::new("n", DataType::Integer),
1205        ])
1206        .named("lines")
1207        .with_constraint(Constraint::PrimaryKey(vec!["order".into(), "n".into()]))
1208        .with_constraint(Constraint::References(ForeignKey {
1209            columns: vec!["order".into(), "n".into()],
1210            table: "x".into(),
1211            references: vec![],
1212        }));
1213        assert_eq!(line.primary_key(), ["order", "n"]);
1214        let keys: Vec<String> = line
1215            .foreign_keys()
1216            .iter()
1217            .map(ToString::to_string)
1218            .collect();
1219        assert_eq!(keys, ["(order, n) → x", "order → orders.id"]);
1220        let mut all = Schema::of_tables([line.clone()]);
1221        all.push_table(Schema::new([]).named("orders"));
1222        assert_eq!(all.tables().len(), 2);
1223        assert_eq!(all.table("lines"), Some(&line));
1224        assert!(all.is_empty());
1225        assert!(all.truncated(true).is_truncated());
1226    }
1227
1228    #[test]
1229    fn unexpanded_references_say_why() {
1230        assert_eq!(
1231            Unexpanded::Recursive("#/$defs/n".into()).to_string(),
1232            "→ #/$defs/n (recursive)"
1233        );
1234        let unresolved = Unexpanded::Unresolved {
1235            reference: "a.json".into(),
1236            reason: "another document".into(),
1237        };
1238        assert_eq!(unresolved.to_string(), "→ a.json (another document)");
1239        assert_eq!(unresolved.reference(), "a.json");
1240        assert_eq!(Composition::AnyOf.title(), "any of");
1241    }
1242}