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