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)]
591pub struct Field {
592    name: String,
593    data_type: DataType,
594    nullable: bool,
595    required: bool,
596    native_type: Option<String>,
597    description: Option<String>,
598    constraints: Vec<Constraint>,
599    metadata: Vec<(String, String)>,
600    kind: FieldKind,
601    reference: Option<String>,
602    unexpanded: Option<Unexpanded>,
603    elided: bool,
604}
605
606impl Field {
607    /// A nullable, optional field.
608    pub fn new(name: impl Into<String>, data_type: DataType) -> Self {
609        Field {
610            name: name.into(),
611            data_type,
612            nullable: true,
613            required: false,
614            native_type: None,
615            description: None,
616            constraints: Vec::new(),
617            metadata: Vec::new(),
618            kind: FieldKind::Field,
619            reference: None,
620            unexpanded: None,
621            elided: false,
622        }
623    }
624
625    /// Whether the field may be null (default `true`).
626    pub fn nullable(mut self, nullable: bool) -> Self {
627        self.nullable = nullable;
628        self
629    }
630
631    /// Whether a value must be present (default `false`): JSON Schema's
632    /// `required`, SQL's `NOT NULL`, a non-nullable Arrow field.
633    pub fn required(mut self, required: bool) -> Self {
634        self.required = required;
635        self
636    }
637
638    /// The source format's own name for the type, kept as written
639    /// (`Int32`, `VARCHAR(20)`, `string | null`). A schema tree shows it in
640    /// place of the model's type.
641    pub fn with_native_type(mut self, native: impl Into<String>) -> Self {
642        self.native_type = Some(native.into());
643        self
644    }
645
646    /// A description, as the source gives it.
647    pub fn with_description(mut self, description: impl Into<String>) -> Self {
648        self.description = Some(description.into());
649        self
650    }
651
652    /// Replace the type.
653    pub fn with_type(mut self, data_type: DataType) -> Self {
654        self.data_type = data_type;
655        self
656    }
657
658    /// Add a constraint.
659    pub fn with_constraint(mut self, constraint: Constraint) -> Self {
660        self.constraints.push(constraint);
661        self
662    }
663
664    /// Add a metadata entry (Arrow's field metadata).
665    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
666        self.metadata.push((key.into(), value.into()));
667        self
668    }
669
670    /// How the field relates to its parent (default [`FieldKind::Field`]).
671    pub fn with_kind(mut self, kind: FieldKind) -> Self {
672        self.kind = kind;
673        self
674    }
675
676    /// The named definition the type was found through (`#/$defs/item`).
677    pub fn with_reference(mut self, reference: impl Into<String>) -> Self {
678        self.reference = Some(reference.into());
679        self
680    }
681
682    /// Mark the type as named by a reference that is not drawn in place.
683    pub fn with_unexpanded(mut self, unexpanded: Unexpanded) -> Self {
684        self.unexpanded = Some(unexpanded);
685        self
686    }
687
688    /// Mark the children as left out, at a depth limit.
689    pub fn elided(mut self, elided: bool) -> Self {
690        self.elided = elided;
691        self
692    }
693
694    /// The name.
695    pub fn name(&self) -> &str {
696        &self.name
697    }
698
699    /// The type.
700    pub fn data_type(&self) -> &DataType {
701        &self.data_type
702    }
703
704    /// The type, to change in place.
705    pub fn data_type_mut(&mut self) -> &mut DataType {
706        &mut self.data_type
707    }
708
709    /// Whether the field may be null.
710    pub fn is_nullable(&self) -> bool {
711        self.nullable
712    }
713
714    /// Whether a value must be present.
715    pub fn is_required(&self) -> bool {
716        self.required
717    }
718
719    /// The source's own type name, if one was recorded.
720    pub fn native_type(&self) -> Option<&str> {
721        self.native_type.as_deref()
722    }
723
724    /// The type as a tree shows it: the native type, else the model's.
725    pub fn type_label(&self) -> String {
726        self.native_type
727            .clone()
728            .unwrap_or_else(|| self.data_type.to_string())
729    }
730
731    /// The description, if any.
732    pub fn description(&self) -> Option<&str> {
733        self.description.as_deref()
734    }
735
736    /// The constraints, in the order the source gives them.
737    pub fn constraints(&self) -> &[Constraint] {
738        &self.constraints
739    }
740
741    /// The constraints, to change in place.
742    pub fn constraints_mut(&mut self) -> &mut Vec<Constraint> {
743        &mut self.constraints
744    }
745
746    /// The first constraint with `key` ([`Constraint::key`]).
747    pub fn constraint(&self, key: &str) -> Option<&Constraint> {
748        self.constraints.iter().find(|c| c.key() == key)
749    }
750
751    /// The metadata entries, in order.
752    pub fn metadata(&self) -> &[(String, String)] {
753        &self.metadata
754    }
755
756    /// The listed values, when the field is an enum.
757    pub fn enum_values(&self) -> Option<&[Literal]> {
758        self.constraints.iter().find_map(|c| match c {
759            Constraint::Enum(values) => Some(values.as_slice()),
760            _ => None,
761        })
762    }
763
764    /// The default value, if any.
765    pub fn default_value(&self) -> Option<&Literal> {
766        self.constraints.iter().find_map(|c| match c {
767            Constraint::Default(value) => Some(value),
768            _ => None,
769        })
770    }
771
772    /// Whether the field is (part of) the primary key.
773    pub fn is_primary_key(&self) -> bool {
774        self.constraints
775            .iter()
776            .any(|c| matches!(c, Constraint::PrimaryKey(_)))
777    }
778
779    /// Whether the field's values are unique on their own.
780    pub fn is_unique(&self) -> bool {
781        self.constraints
782            .iter()
783            .any(|c| matches!(c, Constraint::Unique(columns) if columns.is_empty()))
784    }
785
786    /// What the field refers to, if it is a foreign key.
787    pub fn references(&self) -> Option<&ForeignKey> {
788        self.constraints.iter().find_map(|c| match c {
789            Constraint::References(key) => Some(key),
790            _ => None,
791        })
792    }
793
794    /// How the field relates to its parent.
795    pub fn kind(&self) -> FieldKind {
796        self.kind
797    }
798
799    /// The named definition the type was found through.
800    pub fn reference(&self) -> Option<&str> {
801        self.reference.as_deref()
802    }
803
804    /// Why the type is not drawn in place, when it is not.
805    pub fn unexpanded(&self) -> Option<&Unexpanded> {
806        self.unexpanded.as_ref()
807    }
808
809    /// Whether the children were left out at a depth limit.
810    pub fn is_elided(&self) -> bool {
811        self.elided
812    }
813
814    /// The nested fields ([`DataType::children`]).
815    pub fn children(&self) -> impl Iterator<Item = &Field> {
816        self.data_type.children()
817    }
818}
819
820/// An ordered list of top-level fields, optionally named (a table, a
821/// record type, a document), and the further tables a schema of several
822/// holds (SQL DDL's `CREATE TABLE`s).
823///
824/// Keys that span columns (a composite primary key, `UNIQUE (a, b)`, a
825/// `FOREIGN KEY`) are the schema's own [constraints](Schema::constraints);
826/// a key on one column is that field's.
827#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
828pub struct Schema {
829    name: Option<String>,
830    description: Option<String>,
831    fields: Vec<Field>,
832    constraints: Vec<Constraint>,
833    tables: Vec<Schema>,
834    metadata: Vec<(String, String)>,
835    truncated: bool,
836}
837
838impl Schema {
839    /// A schema of `fields`, in order.
840    pub fn new(fields: impl IntoIterator<Item = Field>) -> Self {
841        Schema {
842            fields: fields.into_iter().collect(),
843            ..Schema::default()
844        }
845    }
846
847    /// A schema of `tables`, with no fields of its own.
848    pub fn of_tables(tables: impl IntoIterator<Item = Schema>) -> Self {
849        Schema {
850            tables: tables.into_iter().collect(),
851            ..Schema::default()
852        }
853    }
854
855    /// Name the schema (a table or type name).
856    pub fn named(mut self, name: impl Into<String>) -> Self {
857        self.name = Some(name.into());
858        self
859    }
860
861    /// Describe the schema.
862    pub fn with_description(mut self, description: impl Into<String>) -> Self {
863        self.description = Some(description.into());
864        self
865    }
866
867    /// Add a table-level constraint.
868    pub fn with_constraint(mut self, constraint: Constraint) -> Self {
869        self.constraints.push(constraint);
870        self
871    }
872
873    /// Add a metadata entry (Arrow's schema metadata).
874    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
875        self.metadata.push((key.into(), value.into()));
876        self
877    }
878
879    /// Mark the schema as cut short by a reader's limit.
880    pub fn truncated(mut self, truncated: bool) -> Self {
881        self.truncated = truncated;
882        self
883    }
884
885    /// The name, if any.
886    pub fn name(&self) -> Option<&str> {
887        self.name.as_deref()
888    }
889
890    /// The description, if any.
891    pub fn description(&self) -> Option<&str> {
892        self.description.as_deref()
893    }
894
895    /// The top-level fields, in order.
896    pub fn fields(&self) -> &[Field] {
897        &self.fields
898    }
899
900    /// The top-level fields, to change in place.
901    pub fn fields_mut(&mut self) -> &mut Vec<Field> {
902        &mut self.fields
903    }
904
905    /// Append a field.
906    pub fn push(&mut self, field: Field) -> &mut Self {
907        self.fields.push(field);
908        self
909    }
910
911    /// The first top-level field called `name`.
912    pub fn field(&self, name: &str) -> Option<&Field> {
913        self.fields.iter().find(|f| f.name == name)
914    }
915
916    /// The position of the first top-level field called `name`.
917    pub fn index_of(&self, name: &str) -> Option<usize> {
918        self.fields.iter().position(|f| f.name == name)
919    }
920
921    /// The number of top-level fields.
922    pub fn len(&self) -> usize {
923        self.fields.len()
924    }
925
926    /// Whether there are no fields.
927    pub fn is_empty(&self) -> bool {
928        self.fields.is_empty()
929    }
930
931    /// The table-level constraints.
932    pub fn constraints(&self) -> &[Constraint] {
933        &self.constraints
934    }
935
936    /// The table-level constraints, to change in place.
937    pub fn constraints_mut(&mut self) -> &mut Vec<Constraint> {
938        &mut self.constraints
939    }
940
941    /// The primary key's columns: the table's key when it spans columns,
942    /// else every field marked as one.
943    pub fn primary_key(&self) -> Vec<&str> {
944        let table = self.constraints.iter().find_map(|c| match c {
945            Constraint::PrimaryKey(columns) if !columns.is_empty() => Some(columns),
946            _ => None,
947        });
948        match table {
949            Some(columns) => columns.iter().map(String::as_str).collect(),
950            None => self
951                .fields
952                .iter()
953                .filter(|f| f.is_primary_key())
954                .map(Field::name)
955                .collect(),
956        }
957    }
958
959    /// Every foreign key: the table's, then each column's (with the column
960    /// filled in).
961    pub fn foreign_keys(&self) -> Vec<ForeignKey> {
962        let table = self.constraints.iter().filter_map(|c| match c {
963            Constraint::References(key) => Some(key.clone()),
964            _ => None,
965        });
966        let columns = self.fields.iter().filter_map(|f| {
967            f.references().map(|key| ForeignKey {
968                columns: vec![f.name.clone()],
969                ..key.clone()
970            })
971        });
972        table.chain(columns).collect()
973    }
974
975    /// The further tables the schema holds.
976    pub fn tables(&self) -> &[Schema] {
977        &self.tables
978    }
979
980    /// The further tables, to change in place.
981    pub fn tables_mut(&mut self) -> &mut Vec<Schema> {
982        &mut self.tables
983    }
984
985    /// Add a table.
986    pub fn push_table(&mut self, table: Schema) -> &mut Self {
987        self.tables.push(table);
988        self
989    }
990
991    /// The table called `name`.
992    pub fn table(&self, name: &str) -> Option<&Schema> {
993        self.tables.iter().find(|t| t.name() == Some(name))
994    }
995
996    /// The metadata entries, in order.
997    pub fn metadata(&self) -> &[(String, String)] {
998        &self.metadata
999    }
1000
1001    /// Whether a reader's limit cut the schema short.
1002    pub fn is_truncated(&self) -> bool {
1003        self.truncated
1004    }
1005}
1006
1007impl FromIterator<Field> for Schema {
1008    fn from_iter<I: IntoIterator<Item = Field>>(iter: I) -> Self {
1009        Schema::new(iter)
1010    }
1011}
1012
1013#[cfg(test)]
1014mod tests {
1015    use super::*;
1016
1017    #[test]
1018    fn types_display_with_their_parameters() {
1019        let cases = [
1020            (DataType::Any, "any"),
1021            (DataType::Unknown, "unknown"),
1022            (DataType::Boolean, "boolean"),
1023            (DataType::Float, "float"),
1024            (DataType::decimal(), "decimal"),
1025            (
1026                DataType::Decimal {
1027                    precision: Some(10),
1028                    scale: Some(2),
1029                },
1030                "decimal(10,2)",
1031            ),
1032            (
1033                DataType::Decimal {
1034                    precision: Some(38),
1035                    scale: None,
1036                },
1037                "decimal(38)",
1038            ),
1039            (DataType::Binary, "binary"),
1040            (DataType::Date, "date"),
1041            (DataType::timestamp(), "timestamp"),
1042            (
1043                DataType::Timestamp {
1044                    timezone: Some("UTC".into()),
1045                },
1046                "timestamp[UTC]",
1047            ),
1048            (
1049                DataType::list(DataType::list(DataType::Integer)),
1050                "list<list<integer>>",
1051            ),
1052            (
1053                DataType::map(DataType::String, DataType::Float),
1054                "map<string, float>",
1055            ),
1056            (DataType::Struct(vec![]), "struct"),
1057        ];
1058        for (data_type, expected) in cases {
1059            assert_eq!(data_type.to_string(), expected);
1060        }
1061    }
1062
1063    #[test]
1064    fn children_cover_structs_lists_and_maps() {
1065        let map = DataType::map(DataType::String, DataType::Integer);
1066        let names: Vec<&str> = map.children().map(Field::name).collect();
1067        assert_eq!(names, ["key", "value"]);
1068        assert!(!map.children().next().unwrap().is_nullable());
1069        let list = DataType::list(DataType::Date);
1070        assert_eq!(list.children().count(), 1);
1071        assert_eq!(DataType::String.children().count(), 0);
1072        assert!(DataType::Struct(vec![]).is_nested());
1073        assert!(DataType::decimal().is_numeric());
1074        assert!(!DataType::String.is_numeric());
1075    }
1076
1077    #[test]
1078    fn fields_carry_native_types_and_descriptions() {
1079        let field = Field::new("id", DataType::Integer)
1080            .nullable(false)
1081            .with_native_type("Int32")
1082            .with_description("primary key");
1083        assert_eq!(field.name(), "id");
1084        assert!(!field.is_nullable());
1085        assert_eq!(field.native_type(), Some("Int32"));
1086        assert_eq!(field.description(), Some("primary key"));
1087        let schema: Schema = [field.clone()].into_iter().collect();
1088        let schema = schema.named("users");
1089        assert_eq!(schema.name(), Some("users"));
1090        assert_eq!(schema.field("id"), Some(&field));
1091        assert_eq!(schema.index_of("missing"), None);
1092        assert_eq!(schema.len(), 1);
1093        assert!(!schema.is_empty());
1094    }
1095
1096    #[test]
1097    fn constraints_show_compare_and_name_themselves() {
1098        let lit = Literal::new;
1099        let cases = [
1100            (Constraint::Const(lit("5")), "= 5", "const", "5"),
1101            (
1102                Constraint::Enum(vec![lit("\"a\""), lit("1")]),
1103                "one of \"a\", 1",
1104                "enum",
1105                "\"a\", 1",
1106            ),
1107            (
1108                Constraint::Format("email".into()),
1109                "format=email",
1110                "format",
1111                "email",
1112            ),
1113            (
1114                Constraint::Pattern("^a".into()),
1115                "pattern=/^a/",
1116                "pattern",
1117                "/^a/",
1118            ),
1119            (
1120                Constraint::MinLength(lit("1")),
1121                "minLength=1",
1122                "minLength",
1123                "1",
1124            ),
1125            (
1126                Constraint::UniqueItems(lit("true")),
1127                "unique items",
1128                "uniqueItems",
1129                "true",
1130            ),
1131            (
1132                Constraint::UniqueItems(lit("false")),
1133                "",
1134                "uniqueItems",
1135                "false",
1136            ),
1137            (
1138                Constraint::Deprecated(lit("true")),
1139                "deprecated",
1140                "deprecated",
1141                "true",
1142            ),
1143            (
1144                Constraint::Additional(lit("false")),
1145                "no other properties",
1146                "additional properties",
1147                "refused",
1148            ),
1149            (
1150                Constraint::Additional(lit("true")),
1151                "",
1152                "additional properties",
1153                "allowed",
1154            ),
1155            (
1156                Constraint::PrimaryKey(vec![]),
1157                "primary key",
1158                "primary key",
1159                "",
1160            ),
1161            (
1162                Constraint::PrimaryKey(vec!["a".into(), "b".into()]),
1163                "primary key (a, b)",
1164                "primary key",
1165                "(a, b)",
1166            ),
1167            (Constraint::Unique(vec![]), "unique", "unique", ""),
1168            (
1169                Constraint::References(ForeignKey::to("users", "id")),
1170                "→ users.id",
1171                "references",
1172                "users.id",
1173            ),
1174            (
1175                Constraint::Other {
1176                    name: "minContains".into(),
1177                    value: lit("2"),
1178                },
1179                "minContains=2",
1180                "minContains",
1181                "2",
1182            ),
1183        ];
1184        for (constraint, shown, name, value) in cases {
1185            assert_eq!(constraint.to_string(), shown, "{constraint:?}");
1186            assert_eq!(constraint.name(), name, "{constraint:?}");
1187            assert_eq!(constraint.value_text(), value, "{constraint:?}");
1188        }
1189        let long = Literal::new("x".repeat(50));
1190        assert_eq!(long.short().chars().count(), 40);
1191        assert!(long.short().ends_with('…'));
1192        assert_eq!(Literal::new("1.5e3").as_f64(), Some(1500.0));
1193        assert_eq!(Literal::new("\"3\"").as_f64(), None);
1194        assert_eq!(Literal::new("inf").as_f64(), None);
1195        assert_eq!(
1196            Literal::json(&serde_json::json!({"a": [1]})).as_str(),
1197            r#"{"a":[1]}"#
1198        );
1199    }
1200
1201    #[test]
1202    fn schemas_hold_tables_and_keys() {
1203        let line = Schema::new([
1204            Field::new("order", DataType::Integer)
1205                .with_constraint(Constraint::References(ForeignKey::to("orders", "id"))),
1206            Field::new("n", DataType::Integer),
1207        ])
1208        .named("lines")
1209        .with_constraint(Constraint::PrimaryKey(vec!["order".into(), "n".into()]))
1210        .with_constraint(Constraint::References(ForeignKey {
1211            columns: vec!["order".into(), "n".into()],
1212            table: "x".into(),
1213            references: vec![],
1214        }));
1215        assert_eq!(line.primary_key(), ["order", "n"]);
1216        let keys: Vec<String> = line
1217            .foreign_keys()
1218            .iter()
1219            .map(ToString::to_string)
1220            .collect();
1221        assert_eq!(keys, ["(order, n) → x", "order → orders.id"]);
1222        let mut all = Schema::of_tables([line.clone()]);
1223        all.push_table(Schema::new([]).named("orders"));
1224        assert_eq!(all.tables().len(), 2);
1225        assert_eq!(all.table("lines"), Some(&line));
1226        assert!(all.is_empty());
1227        assert!(all.truncated(true).is_truncated());
1228    }
1229
1230    #[test]
1231    fn unexpanded_references_say_why() {
1232        assert_eq!(
1233            Unexpanded::Recursive("#/$defs/n".into()).to_string(),
1234            "→ #/$defs/n (recursive)"
1235        );
1236        let unresolved = Unexpanded::Unresolved {
1237            reference: "a.json".into(),
1238            reason: "another document".into(),
1239        };
1240        assert_eq!(unresolved.to_string(), "→ a.json (another document)");
1241        assert_eq!(unresolved.reference(), "a.json");
1242        assert_eq!(Composition::AnyOf.title(), "any of");
1243    }
1244}