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 {
224 text.parse().ok()
225 } else {
226 None
227 }
228 }
229
230 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#[derive(Clone, Debug, PartialEq, Eq, Hash)]
249pub struct ForeignKey {
250 pub columns: Vec<String>,
253 pub table: String,
255 pub references: Vec<String>,
258}
259
260impl ForeignKey {
261 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
271impl 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#[derive(Clone, Debug, PartialEq, Eq, Hash)]
297#[non_exhaustive]
298pub enum Constraint {
299 Const(Literal),
301 Enum(Vec<Literal>),
303 Format(String),
305 Pattern(String),
307 MinLength(Literal),
309 MaxLength(Literal),
311 Minimum(Literal),
313 ExclusiveMinimum(Literal),
315 Maximum(Literal),
317 ExclusiveMaximum(Literal),
319 MultipleOf(Literal),
321 MinItems(Literal),
323 MaxItems(Literal),
325 UniqueItems(Literal),
327 Default(Literal),
329 Deprecated(Literal),
331 ReadOnly(Literal),
333 WriteOnly(Literal),
335 Additional(Literal),
340 PrimaryKey(Vec<String>),
343 Unique(Vec<String>),
346 References(ForeignKey),
348 Other {
350 name: String,
352 value: Literal,
354 },
355}
356
357impl Constraint {
358 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 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 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 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
452impl 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
499pub enum Composition {
500 OneOf,
502 AnyOf,
504 AllOf,
506}
507
508impl Composition {
509 pub const ALL: [Composition; 3] = [Composition::OneOf, Composition::AnyOf, Composition::AllOf];
511
512 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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
531#[non_exhaustive]
532pub enum FieldKind {
533 #[default]
535 Field,
536 Pattern,
538 Additional,
540 TupleItem,
542 Items,
544 Group(Composition),
546 Branch(Composition),
548 Condition,
550}
551
552#[derive(Clone, Debug, PartialEq, Eq, Hash)]
554#[non_exhaustive]
555pub enum Unexpanded {
556 Recursive(String),
558 Unresolved {
560 reference: String,
562 reason: String,
564 },
565}
566
567impl Unexpanded {
568 pub fn reference(&self) -> &str {
570 match self {
571 Unexpanded::Recursive(reference) => reference,
572 Unexpanded::Unresolved { reference, .. } => reference,
573 }
574 }
575}
576
577impl 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#[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 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 pub fn nullable(mut self, nullable: bool) -> Self {
627 self.nullable = nullable;
628 self
629 }
630
631 pub fn required(mut self, required: bool) -> Self {
634 self.required = required;
635 self
636 }
637
638 pub fn with_native_type(mut self, native: impl Into<String>) -> Self {
642 self.native_type = Some(native.into());
643 self
644 }
645
646 pub fn with_description(mut self, description: impl Into<String>) -> Self {
648 self.description = Some(description.into());
649 self
650 }
651
652 pub fn with_type(mut self, data_type: DataType) -> Self {
654 self.data_type = data_type;
655 self
656 }
657
658 pub fn with_constraint(mut self, constraint: Constraint) -> Self {
660 self.constraints.push(constraint);
661 self
662 }
663
664 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 pub fn with_kind(mut self, kind: FieldKind) -> Self {
672 self.kind = kind;
673 self
674 }
675
676 pub fn with_reference(mut self, reference: impl Into<String>) -> Self {
678 self.reference = Some(reference.into());
679 self
680 }
681
682 pub fn with_unexpanded(mut self, unexpanded: Unexpanded) -> Self {
684 self.unexpanded = Some(unexpanded);
685 self
686 }
687
688 pub fn elided(mut self, elided: bool) -> Self {
690 self.elided = elided;
691 self
692 }
693
694 pub fn name(&self) -> &str {
696 &self.name
697 }
698
699 pub fn data_type(&self) -> &DataType {
701 &self.data_type
702 }
703
704 pub fn data_type_mut(&mut self) -> &mut DataType {
706 &mut self.data_type
707 }
708
709 pub fn is_nullable(&self) -> bool {
711 self.nullable
712 }
713
714 pub fn is_required(&self) -> bool {
716 self.required
717 }
718
719 pub fn native_type(&self) -> Option<&str> {
721 self.native_type.as_deref()
722 }
723
724 pub fn type_label(&self) -> String {
726 self.native_type
727 .clone()
728 .unwrap_or_else(|| self.data_type.to_string())
729 }
730
731 pub fn description(&self) -> Option<&str> {
733 self.description.as_deref()
734 }
735
736 pub fn constraints(&self) -> &[Constraint] {
738 &self.constraints
739 }
740
741 pub fn constraints_mut(&mut self) -> &mut Vec<Constraint> {
743 &mut self.constraints
744 }
745
746 pub fn constraint(&self, key: &str) -> Option<&Constraint> {
748 self.constraints.iter().find(|c| c.key() == key)
749 }
750
751 pub fn metadata(&self) -> &[(String, String)] {
753 &self.metadata
754 }
755
756 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 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 pub fn is_primary_key(&self) -> bool {
774 self.constraints
775 .iter()
776 .any(|c| matches!(c, Constraint::PrimaryKey(_)))
777 }
778
779 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 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 pub fn kind(&self) -> FieldKind {
796 self.kind
797 }
798
799 pub fn reference(&self) -> Option<&str> {
801 self.reference.as_deref()
802 }
803
804 pub fn unexpanded(&self) -> Option<&Unexpanded> {
806 self.unexpanded.as_ref()
807 }
808
809 pub fn is_elided(&self) -> bool {
811 self.elided
812 }
813
814 pub fn children(&self) -> impl Iterator<Item = &Field> {
816 self.data_type.children()
817 }
818}
819
820#[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 pub fn new(fields: impl IntoIterator<Item = Field>) -> Self {
841 Schema {
842 fields: fields.into_iter().collect(),
843 ..Schema::default()
844 }
845 }
846
847 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 pub fn named(mut self, name: impl Into<String>) -> Self {
857 self.name = Some(name.into());
858 self
859 }
860
861 pub fn with_description(mut self, description: impl Into<String>) -> Self {
863 self.description = Some(description.into());
864 self
865 }
866
867 pub fn with_constraint(mut self, constraint: Constraint) -> Self {
869 self.constraints.push(constraint);
870 self
871 }
872
873 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 pub fn truncated(mut self, truncated: bool) -> Self {
881 self.truncated = truncated;
882 self
883 }
884
885 pub fn name(&self) -> Option<&str> {
887 self.name.as_deref()
888 }
889
890 pub fn description(&self) -> Option<&str> {
892 self.description.as_deref()
893 }
894
895 pub fn fields(&self) -> &[Field] {
897 &self.fields
898 }
899
900 pub fn fields_mut(&mut self) -> &mut Vec<Field> {
902 &mut self.fields
903 }
904
905 pub fn push(&mut self, field: Field) -> &mut Self {
907 self.fields.push(field);
908 self
909 }
910
911 pub fn field(&self, name: &str) -> Option<&Field> {
913 self.fields.iter().find(|f| f.name == name)
914 }
915
916 pub fn index_of(&self, name: &str) -> Option<usize> {
918 self.fields.iter().position(|f| f.name == name)
919 }
920
921 pub fn len(&self) -> usize {
923 self.fields.len()
924 }
925
926 pub fn is_empty(&self) -> bool {
928 self.fields.is_empty()
929 }
930
931 pub fn constraints(&self) -> &[Constraint] {
933 &self.constraints
934 }
935
936 pub fn constraints_mut(&mut self) -> &mut Vec<Constraint> {
938 &mut self.constraints
939 }
940
941 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 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 pub fn tables(&self) -> &[Schema] {
977 &self.tables
978 }
979
980 pub fn tables_mut(&mut self) -> &mut Vec<Schema> {
982 &mut self.tables
983 }
984
985 pub fn push_table(&mut self, table: Schema) -> &mut Self {
987 self.tables.push(table);
988 self
989 }
990
991 pub fn table(&self, name: &str) -> Option<&Schema> {
993 self.tables.iter().find(|t| t.name() == Some(name))
994 }
995
996 pub fn metadata(&self) -> &[(String, String)] {
998 &self.metadata
999 }
1000
1001 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}