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