1use std::fmt;
51
52#[derive(Clone, Debug, PartialEq, Eq, Hash)]
61#[non_exhaustive]
62pub enum DataType {
63 Any,
65 Unknown,
67 Boolean,
69 Integer,
71 Float,
73 Decimal {
76 precision: Option<u32>,
78 scale: Option<i32>,
80 },
81 String,
83 Binary,
85 Date,
87 Timestamp {
89 timezone: Option<String>,
91 },
92 List(Box<Field>),
94 Struct(Vec<Field>),
96 Map {
98 key: Box<Field>,
100 value: Box<Field>,
102 },
103}
104
105impl DataType {
106 pub fn list(item: DataType) -> Self {
108 DataType::List(Box::new(Field::new("item", item)))
109 }
110
111 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 pub fn timestamp() -> Self {
122 DataType::Timestamp { timezone: None }
123 }
124
125 pub fn decimal() -> Self {
127 DataType::Decimal {
128 precision: None,
129 scale: None,
130 }
131 }
132
133 pub fn is_numeric(&self) -> bool {
135 matches!(
136 self,
137 DataType::Integer | DataType::Float | DataType::Decimal { .. }
138 )
139 }
140
141 pub fn is_nested(&self) -> bool {
143 matches!(
144 self,
145 DataType::List(_) | DataType::Struct(_) | DataType::Map { .. }
146 )
147 }
148
149 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
162impl 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#[derive(Clone, Debug, PartialEq, Eq, Hash)]
197pub struct Literal(String);
198
199impl Literal {
200 pub fn new(text: impl Into<String>) -> Self {
202 Literal(text.into())
203 }
204
205 pub fn json(value: &serde_json::Value) -> Self {
207 Literal(value.to_string())
208 }
209
210 pub fn as_str(&self) -> &str {
212 &self.0
213 }
214
215 pub fn as_f64(&self) -> Option<f64> {
217 let text = self.0.trim();
218 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 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#[derive(Clone, Debug, PartialEq, Eq, Hash)]
245pub struct ForeignKey {
246 pub columns: Vec<String>,
249 pub table: String,
251 pub references: Vec<String>,
254}
255
256impl ForeignKey {
257 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
267impl 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#[derive(Clone, Debug, PartialEq, Eq, Hash)]
293#[non_exhaustive]
294pub enum Constraint {
295 Const(Literal),
297 Enum(Vec<Literal>),
299 Format(String),
301 Pattern(String),
303 MinLength(Literal),
305 MaxLength(Literal),
307 Minimum(Literal),
309 ExclusiveMinimum(Literal),
311 Maximum(Literal),
313 ExclusiveMaximum(Literal),
315 MultipleOf(Literal),
317 MinItems(Literal),
319 MaxItems(Literal),
321 UniqueItems(Literal),
323 Default(Literal),
325 Deprecated(Literal),
327 ReadOnly(Literal),
329 WriteOnly(Literal),
331 Additional(Literal),
336 PrimaryKey(Vec<String>),
339 Unique(Vec<String>),
342 References(ForeignKey),
344 Other {
346 name: String,
348 value: Literal,
350 },
351}
352
353impl Constraint {
354 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 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 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 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
448impl 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
495pub enum Composition {
496 OneOf,
498 AnyOf,
500 AllOf,
502}
503
504impl Composition {
505 pub const ALL: [Composition; 3] = [Composition::OneOf, Composition::AnyOf, Composition::AllOf];
507
508 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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
527#[non_exhaustive]
528pub enum FieldKind {
529 #[default]
531 Field,
532 Pattern,
534 Additional,
536 TupleItem,
538 Items,
540 Group(Composition),
542 Branch(Composition),
544 Condition,
546}
547
548#[derive(Clone, Debug, PartialEq, Eq, Hash)]
550#[non_exhaustive]
551pub enum Unexpanded {
552 Recursive(String),
554 Unresolved {
556 reference: String,
558 reason: String,
560 },
561}
562
563impl Unexpanded {
564 pub fn reference(&self) -> &str {
566 match self {
567 Unexpanded::Recursive(reference) => reference,
568 Unexpanded::Unresolved { reference, .. } => reference,
569 }
570 }
571}
572
573impl 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#[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 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 pub fn nullable(mut self, nullable: bool) -> Self {
624 self.nullable = nullable;
625 self
626 }
627
628 pub fn required(mut self, required: bool) -> Self {
631 self.required = required;
632 self
633 }
634
635 pub fn with_native_type(mut self, native: impl Into<String>) -> Self {
639 self.native_type = Some(native.into());
640 self
641 }
642
643 pub fn with_description(mut self, description: impl Into<String>) -> Self {
645 self.description = Some(description.into());
646 self
647 }
648
649 pub fn with_type(mut self, data_type: DataType) -> Self {
651 self.data_type = data_type;
652 self
653 }
654
655 pub fn with_constraint(mut self, constraint: Constraint) -> Self {
657 self.constraints.push(constraint);
658 self
659 }
660
661 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 pub fn with_kind(mut self, kind: FieldKind) -> Self {
669 self.kind = kind;
670 self
671 }
672
673 pub fn with_reference(mut self, reference: impl Into<String>) -> Self {
675 self.reference = Some(reference.into());
676 self
677 }
678
679 pub fn with_unexpanded(mut self, unexpanded: Unexpanded) -> Self {
681 self.unexpanded = Some(unexpanded);
682 self
683 }
684
685 pub fn elided(mut self, elided: bool) -> Self {
687 self.elided = elided;
688 self
689 }
690
691 pub fn name(&self) -> &str {
693 &self.name
694 }
695
696 pub fn data_type(&self) -> &DataType {
698 &self.data_type
699 }
700
701 pub fn data_type_mut(&mut self) -> &mut DataType {
703 &mut self.data_type
704 }
705
706 pub fn is_nullable(&self) -> bool {
708 self.nullable
709 }
710
711 pub fn is_required(&self) -> bool {
713 self.required
714 }
715
716 pub fn native_type(&self) -> Option<&str> {
718 self.native_type.as_deref()
719 }
720
721 pub fn type_label(&self) -> String {
723 self.native_type
724 .clone()
725 .unwrap_or_else(|| self.data_type.to_string())
726 }
727
728 pub fn description(&self) -> Option<&str> {
730 self.description.as_deref()
731 }
732
733 pub fn constraints(&self) -> &[Constraint] {
735 &self.constraints
736 }
737
738 pub fn constraints_mut(&mut self) -> &mut Vec<Constraint> {
740 &mut self.constraints
741 }
742
743 pub fn constraint(&self, key: &str) -> Option<&Constraint> {
745 self.constraints.iter().find(|c| c.key() == key)
746 }
747
748 pub fn metadata(&self) -> &[(String, String)] {
750 &self.metadata
751 }
752
753 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 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 pub fn is_primary_key(&self) -> bool {
771 self.constraints
772 .iter()
773 .any(|c| matches!(c, Constraint::PrimaryKey(_)))
774 }
775
776 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 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 pub fn kind(&self) -> FieldKind {
793 self.kind
794 }
795
796 pub fn reference(&self) -> Option<&str> {
798 self.reference.as_deref()
799 }
800
801 pub fn unexpanded(&self) -> Option<&Unexpanded> {
803 self.unexpanded.as_ref()
804 }
805
806 pub fn is_elided(&self) -> bool {
808 self.elided
809 }
810
811 pub fn children(&self) -> impl Iterator<Item = &Field> {
813 self.data_type.children()
814 }
815}
816
817#[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 pub fn new(fields: impl IntoIterator<Item = Field>) -> Self {
839 Schema {
840 fields: fields.into_iter().collect(),
841 ..Schema::default()
842 }
843 }
844
845 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 pub fn named(mut self, name: impl Into<String>) -> Self {
855 self.name = Some(name.into());
856 self
857 }
858
859 pub fn with_description(mut self, description: impl Into<String>) -> Self {
861 self.description = Some(description.into());
862 self
863 }
864
865 pub fn with_constraint(mut self, constraint: Constraint) -> Self {
867 self.constraints.push(constraint);
868 self
869 }
870
871 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 pub fn truncated(mut self, truncated: bool) -> Self {
879 self.truncated = truncated;
880 self
881 }
882
883 pub fn name(&self) -> Option<&str> {
885 self.name.as_deref()
886 }
887
888 pub fn description(&self) -> Option<&str> {
890 self.description.as_deref()
891 }
892
893 pub fn fields(&self) -> &[Field] {
895 &self.fields
896 }
897
898 pub fn fields_mut(&mut self) -> &mut Vec<Field> {
900 &mut self.fields
901 }
902
903 pub fn push(&mut self, field: Field) -> &mut Self {
905 self.fields.push(field);
906 self
907 }
908
909 pub fn field(&self, name: &str) -> Option<&Field> {
911 self.fields.iter().find(|f| f.name == name)
912 }
913
914 pub fn index_of(&self, name: &str) -> Option<usize> {
916 self.fields.iter().position(|f| f.name == name)
917 }
918
919 pub fn len(&self) -> usize {
921 self.fields.len()
922 }
923
924 pub fn is_empty(&self) -> bool {
926 self.fields.is_empty()
927 }
928
929 pub fn constraints(&self) -> &[Constraint] {
931 &self.constraints
932 }
933
934 pub fn constraints_mut(&mut self) -> &mut Vec<Constraint> {
936 &mut self.constraints
937 }
938
939 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 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 pub fn tables(&self) -> &[Schema] {
975 &self.tables
976 }
977
978 pub fn tables_mut(&mut self) -> &mut Vec<Schema> {
980 &mut self.tables
981 }
982
983 pub fn push_table(&mut self, table: Schema) -> &mut Self {
985 self.tables.push(table);
986 self
987 }
988
989 pub fn table(&self, name: &str) -> Option<&Schema> {
991 self.tables.iter().find(|t| t.name() == Some(name))
992 }
993
994 pub fn metadata(&self) -> &[(String, String)] {
996 &self.metadata
997 }
998
999 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}