1use crate::isl::isl_import::{IslImport, IslImportType};
138use crate::isl::isl_type::IslType;
139use crate::UserReservedFields;
140use ion_rs::Element;
141use ion_rs::{IonResult, SequenceWriter, StructWriter, ValueWriter, WriteAsIon};
142use std::collections::HashMap;
143use std::fmt::{Display, Formatter};
144
145pub mod isl_constraint;
146pub mod isl_import;
147pub mod isl_type;
148pub mod isl_type_reference;
149pub mod ranges;
150pub mod util;
151
152#[derive(Debug, Clone, PartialEq, Eq, Copy)]
155pub enum IslVersion {
156 V1_0,
157 V2_0,
158}
159
160impl WriteAsIon for IslVersion {
161 fn write_as_ion<V: ValueWriter>(&self, writer: V) -> IonResult<()> {
162 let text = match self {
163 IslVersion::V1_0 => "$ion_schema_1_0",
164 IslVersion::V2_0 => "$ion_schema_2_0",
165 };
166 writer.write_symbol(text)
167 }
168}
169
170impl Display for IslVersion {
171 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
172 write!(
173 f,
174 "{}",
175 match self {
176 IslVersion::V1_0 => "ISL 1.0",
177 IslVersion::V2_0 => "ISL 2.0",
178 }
179 )
180 }
181}
182
183#[derive(Debug, Clone, PartialEq)]
185pub enum SchemaContent {
186 Version(IslVersion),
187 Header(SchemaHeader),
188 Type(IslType),
189 Footer(SchemaFooter),
190 OpenContent(Element),
191}
192impl From<IslVersion> for SchemaContent {
193 fn from(value: IslVersion) -> Self {
194 SchemaContent::Version(value)
195 }
196}
197impl From<SchemaHeader> for SchemaContent {
198 fn from(value: SchemaHeader) -> Self {
199 SchemaContent::Header(value)
200 }
201}
202impl From<IslType> for SchemaContent {
203 fn from(value: IslType) -> Self {
204 SchemaContent::Type(value)
205 }
206}
207impl From<SchemaFooter> for SchemaContent {
208 fn from(value: SchemaFooter) -> Self {
209 SchemaContent::Footer(value)
210 }
211}
212
213impl SchemaContent {
214 fn as_header(&self) -> Option<&SchemaHeader> {
215 if let SchemaContent::Header(schema_header) = self {
216 Some(schema_header)
217 } else {
218 None
219 }
220 }
221 fn as_isl_version(&self) -> Option<&IslVersion> {
222 if let SchemaContent::Version(value) = self {
223 Some(value)
224 } else {
225 None
226 }
227 }
228 fn as_type(&self) -> Option<&IslType> {
229 if let SchemaContent::Type(value) = self {
230 Some(value)
231 } else {
232 None
233 }
234 }
235 fn as_open_content(&self) -> Option<&Element> {
236 if let SchemaContent::OpenContent(value) = self {
237 Some(value)
238 } else {
239 None
240 }
241 }
242 fn as_footer(&self) -> Option<&SchemaFooter> {
243 if let SchemaContent::Footer(value) = self {
244 Some(value)
245 } else {
246 None
247 }
248 }
249 fn expect_header(&self) -> &SchemaHeader {
250 let SchemaContent::Header(value) = self else {
251 unreachable!("expected to find a Header, but found: {:?}", self)
252 };
253 value
254 }
255 fn expect_isl_version(&self) -> &IslVersion {
256 let SchemaContent::Version(value) = self else {
257 unreachable!("expected to find an IslVersion, but found: {:?}", self)
258 };
259 value
260 }
261 fn expect_type(&self) -> &IslType {
262 let SchemaContent::Type(value) = self else {
263 unreachable!("expected to find a Type, but found: {:?}", self)
264 };
265 value
266 }
267 fn expect_open_content(&self) -> &Element {
268 let SchemaContent::OpenContent(value) = self else {
269 unreachable!("expected to find OpenContent, but found: {:?}", self)
270 };
271 value
272 }
273 fn expect_footer(&self) -> &SchemaFooter {
274 let SchemaContent::Footer(value) = self else {
275 unreachable!("expected to find a Footer, but found: {:?}", self)
276 };
277 value
278 }
279}
280
281impl WriteAsIon for SchemaContent {
282 fn write_as_ion<V: ValueWriter>(&self, writer: V) -> IonResult<()> {
283 match self {
284 SchemaContent::Version(value) => writer.write(value),
285 SchemaContent::Header(value) => writer.write(value),
286 SchemaContent::Type(value) => writer.write(value),
287 SchemaContent::Footer(value) => writer.write(value),
288 SchemaContent::OpenContent(value) => writer.write(value),
289 }
290 }
291}
292
293#[derive(Debug, Clone, PartialEq, Default)]
294pub struct SchemaHeader {
295 user_reserved_fields: UserReservedFields,
297 imports: Vec<IslImport>,
300 user_content: Vec<(String, Element)>,
302}
303
304impl WriteAsIon for SchemaHeader {
305 fn write_as_ion<V: ValueWriter>(&self, writer: V) -> IonResult<()> {
306 let mut struct_writer = writer
307 .with_annotations(["schema_header"])?
308 .struct_writer()?;
309 if !self.imports.is_empty() {
310 struct_writer.field_writer("imports").write(&self.imports)?;
311 }
312 if !self.user_reserved_fields.is_empty() {
313 struct_writer
314 .field_writer("user_reserved_fields")
315 .write(&self.user_reserved_fields)?;
316 }
317 if !self.user_content.is_empty() {
318 for (k, v) in &self.user_content {
319 struct_writer.write(k.as_str(), v)?;
320 }
321 }
322 struct_writer.close()
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Default)]
327pub struct SchemaFooter {
328 user_content: Vec<(String, Element)>,
330}
331
332impl WriteAsIon for SchemaFooter {
333 fn write_as_ion<V: ValueWriter>(&self, writer: V) -> IonResult<()> {
334 let mut struct_writer = writer
335 .with_annotations(["schema_footer"])?
336 .struct_writer()?;
337 if !self.user_content.is_empty() {
338 for (k, v) in &self.user_content {
339 struct_writer.write(k.as_str(), v)?;
340 }
341 }
342 struct_writer.close()
343 }
344}
345
346#[derive(Debug, Clone, PartialEq)]
348pub struct IslSchema {
349 id: String,
351 schema_content: Vec<SchemaContent>,
353 header_index: Option<usize>,
355 footer_index: Option<usize>,
357 types_by_name: HashMap<String, usize>,
359
360 inline_imported_types: Vec<IslImportType>,
362}
363
364impl IslSchema {
365 fn new_from_content_vec(
366 id: String,
367 schema_content: Vec<SchemaContent>,
368 inline_imported_types: Vec<IslImportType>,
369 ) -> Self {
370 assert!(matches!(schema_content[0], SchemaContent::Version(_)));
371 let header_index = schema_content
372 .iter()
373 .position(|x| matches!(x, SchemaContent::Header(_)));
374 let footer_index = schema_content
375 .iter()
376 .position(|x| matches!(x, SchemaContent::Footer(_)));
377 let types_by_name: HashMap<String, usize> = schema_content
379 .iter()
380 .enumerate()
381 .filter(|&(_, x)| matches!(x, SchemaContent::Type(_)))
382 .map(|(i, t)| (t.expect_type().name().unwrap().to_string(), i))
383 .collect();
384
385 Self {
386 id,
387 schema_content,
388 header_index,
389 footer_index,
390 types_by_name,
391 inline_imported_types,
392 }
393 }
394
395 pub(crate) fn new<A: AsRef<str>>(
397 id: A,
398 version: IslVersion,
399 user_reserved_fields: Option<UserReservedFields>,
400 imports: Vec<IslImport>,
401 types: Vec<IslType>,
402 inline_imports: Vec<IslImportType>,
403 open_content: Vec<Element>,
404 ) -> Self {
405 let mut schema_content: Vec<SchemaContent> = vec![];
406 schema_content.push(version.into());
407 schema_content.push(
408 SchemaHeader {
409 user_reserved_fields: user_reserved_fields.unwrap_or_default(),
410 imports,
411 user_content: vec![],
412 }
413 .into(),
414 );
415 for t in types {
416 schema_content.push(t.into());
417 }
418 for e in open_content {
419 schema_content.push(SchemaContent::OpenContent(e));
420 }
421 Self::new_from_content_vec(id.as_ref().to_string(), schema_content, inline_imports)
422 }
423
424 pub fn schema_v_1_0<A: AsRef<str>>(
427 id: A,
428 imports: Vec<IslImport>,
429 types: Vec<IslType>,
430 inline_imports: Vec<IslImportType>,
431 open_content: Vec<Element>,
432 ) -> IslSchema {
433 IslSchema::new(
434 id.as_ref(),
435 IslVersion::V1_0,
436 None,
437 imports,
438 types,
439 inline_imports,
440 open_content,
441 )
442 }
443
444 pub fn schema_v_2_0<A: AsRef<str>>(
447 id: A,
448 user_reserved_fields: UserReservedFields,
449 imports: Vec<IslImport>,
450 types: Vec<IslType>,
451 inline_imports: Vec<IslImportType>,
452 open_content: Vec<Element>,
453 ) -> IslSchema {
454 IslSchema::new(
455 id.as_ref(),
456 IslVersion::V2_0,
457 Some(user_reserved_fields),
458 imports,
459 types,
460 inline_imports,
461 open_content,
462 )
463 }
464
465 pub fn id(&self) -> String {
466 self.id.to_owned()
467 }
468
469 pub fn version(&self) -> IslVersion {
470 *(self.schema_content[0].expect_isl_version())
471 }
472
473 fn header(&self) -> Option<&SchemaHeader> {
474 self.header_index
475 .map(|i| self.schema_content.get(i).unwrap())
476 .map(SchemaContent::expect_header)
477 }
478
479 fn footer(&self) -> Option<&SchemaFooter> {
480 self.footer_index
481 .map(|i| self.schema_content.get(i).unwrap())
482 .map(SchemaContent::expect_footer)
483 }
484
485 const EMPTY_VEC: Vec<IslImport> = vec![];
486 const EMPTY_VEC_REF: &'static Vec<IslImport> = &Self::EMPTY_VEC;
487
488 pub fn imports(&self) -> impl Iterator<Item = &IslImport> {
489 self.header()
490 .map(|x| &x.imports)
491 .unwrap_or(Self::EMPTY_VEC_REF)
492 .iter()
493 }
494
495 pub fn types(&self) -> impl Iterator<Item = &IslType> {
496 self.schema_content.iter().filter_map(|x| x.as_type())
497 }
498
499 pub fn inline_imported_types(&self) -> impl Iterator<Item = &IslImportType> {
500 self.inline_imported_types.iter()
501 }
502
503 pub fn open_content(&self) -> impl Iterator<Item = &Element> {
506 self.schema_content
507 .iter()
508 .filter_map(|x| x.as_open_content())
509 }
510
511 pub fn user_reserved_fields(&self) -> Option<&UserReservedFields> {
514 self.header().map(|x| &x.user_reserved_fields)
515 }
516}
517
518impl IslSchema {
519 fn write_as_ion<W: SequenceWriter>(&self, writer: &mut W) -> IonResult<()> {
520 for item in &self.schema_content {
521 writer.write(item)?;
522 }
523 Ok(())
524 }
525}
526
527impl<'a> IntoIterator for &'a IslSchema {
528 type Item = &'a SchemaContent;
529 type IntoIter = SchemaIterator<'a>;
530
531 fn into_iter(self) -> Self::IntoIter {
532 SchemaIterator {
533 content: &self.schema_content,
534 i: 0,
535 }
536 }
537}
538
539pub struct SchemaIterator<'a> {
540 content: &'a Vec<SchemaContent>,
541 i: usize,
542}
543
544impl<'a> Iterator for SchemaIterator<'a> {
545 type Item = &'a SchemaContent;
546
547 fn next(&mut self) -> Option<Self::Item> {
548 let next_item = self.content.get(self.i);
549 self.i += 1;
550 next_item
551 }
552}
553
554#[cfg(test)]
555mod isl_tests {
556 use crate::authority::FileSystemDocumentAuthority;
557 use crate::ion_extension::ElementExtensions;
558 use crate::isl::isl_constraint::v_1_0::*;
559 use crate::isl::isl_type::v_1_0::load_isl_type as load_isl_type_def;
560 use crate::isl::isl_type::v_1_0::*;
561 use crate::isl::isl_type::v_2_0::load_isl_type as load_isl_type_def_v2_0;
562 use crate::isl::isl_type::IslType;
563 use crate::isl::isl_type_reference::v_1_0::*;
564 use crate::isl::ranges::*;
565 use crate::isl::util::Ieee754InterchangeFormat;
566 use crate::isl::util::TimestampPrecision;
567 use crate::isl::util::ValidValue;
568 use crate::isl::*;
569 use crate::result::IonSchemaResult;
570 use crate::system::SchemaSystem;
571 use ion_rs::v1_0;
572 use ion_rs::Decimal;
573 use ion_rs::IonType;
574 use ion_rs::Symbol;
575 use ion_rs::{Element, TextFormat, WriteConfig, Writer};
576 use rstest::*;
577 use std::path::Path;
578 use test_generator::test_resources;
579
580 fn load_isl_type(text: &str) -> IslType {
582 load_isl_type_def(text.as_bytes()).unwrap()
583 }
584
585 fn load_isl_type_v2_0(text: &str) -> IslType {
587 load_isl_type_def_v2_0(text.as_bytes()).unwrap()
588 }
589
590 #[test]
591 fn test_open_content_for_type_def() -> IonSchemaResult<()> {
592 let type_def = load_isl_type(
593 r#"
594 // type definition with open content
595 type:: {
596 name: my_int,
597 type: int,
598 unknown_constraint: "this is an open content field value"
599 }
600 "#,
601 );
602
603 assert_eq!(
604 type_def.open_content(),
605 vec![(
606 "unknown_constraint".to_owned(),
607 Element::read_one(r#""this is an open content field value""#.as_bytes())?
608 )]
609 );
610 Ok(())
611 }
612
613 #[rstest(
614 isl_type1,isl_type2,
615 case::type_constraint_with_anonymous_type(
616 load_isl_type(r#" // For a schema with single anonymous type
617 {type: any}
618 "#),
619 anonymous_type([type_constraint(named_type_ref("any"))])
620 ),
621 case::type_constraint_with_nullable_annotation(
622 load_isl_type(r#" // For a schema with `nullable` annotation`
623 {type: nullable::int}
624 "#),
625 anonymous_type([type_constraint(nullable_built_in_type_ref(IonType::Int))])
626 ),
627 case::type_constraint_with_null_or_annotation(
628 load_isl_type_v2_0(r#" // For a schema with `$null_or` annotation`
629 {type: $null_or::int}
630 "#),
631 isl_type::v_2_0::anonymous_type([isl_constraint::v_2_0::type_constraint(isl_type_reference::v_2_0::null_or_named_type_ref("int"))])
632 ),
633 case::type_constraint_with_named_type(
634 load_isl_type(r#" // For a schema with named type
635 type:: { name: my_int, type: int }
636 "#),
637 named_type("my_int", [type_constraint(named_type_ref("int"))])
638 ),
639 case::type_constraint_with_named_nullable_type(
640 load_isl_type(r#" // For a schema with named type
641 type:: { name: my_nullable_int, type: $int }
642 "#),
643 named_type("my_nullable_int", [type_constraint(named_type_ref("$int"))])
644 ),
645 case::type_constraint_with_self_reference_type(
646 load_isl_type(r#" // For a schema with self reference type
647 type:: { name: my_int, type: my_int }
648 "#),
649 named_type("my_int", [type_constraint(named_type_ref("my_int"))])
650 ),
651 case::type_constraint_with_nested_self_reference_type(
652 load_isl_type(r#" // For a schema with nested self reference type
653 type:: { name: my_int, type: { type: my_int } }
654 "#),
655 named_type("my_int", [type_constraint(anonymous_type_ref([type_constraint(named_type_ref("my_int"))]))])
656 ),
657 case::type_constraint_with_nested_type(
658 load_isl_type(r#" // For a schema with nested types
659 type:: { name: my_int, type: { type: int } }
660 "#),
661 named_type("my_int", [type_constraint(anonymous_type_ref([type_constraint(named_type_ref("int"))]))])
662 ),
663 case::type_constraint_with_nested_multiple_types(
664 load_isl_type(r#" // For a schema with nested multiple types
665 type:: { name: my_int, type: { type: int }, type: { type: my_int } }
666 "#),
667 named_type("my_int", [type_constraint(anonymous_type_ref([type_constraint(named_type_ref("int"))])), type_constraint(anonymous_type_ref([type_constraint(named_type_ref("my_int"))]))])
668 ),
669 case::all_of_constraint(
670 load_isl_type(r#" // For a schema with all_of type as below:
671 { all_of: [{ type: int }] }
672 "#),
673 anonymous_type([all_of([anonymous_type_ref([type_constraint(named_type_ref("int"))])])])
674 ),
675 case::any_of_constraint(
676 load_isl_type(r#" // For a schema with any_of constraint as below:
677 { any_of: [{ type: int }, { type: decimal }] }
678 "#),
679 anonymous_type([any_of([anonymous_type_ref([type_constraint(named_type_ref("int"))]), anonymous_type_ref([type_constraint(named_type_ref("decimal"))])])])
680 ),
681 case::one_of_constraint(
682 load_isl_type(r#" // For a schema with one_of constraint as below:
683 { one_of: [{ type: int }, { type: decimal }] }
684 "#),
685 anonymous_type([one_of([anonymous_type_ref([type_constraint(named_type_ref("int"))]), anonymous_type_ref([type_constraint(named_type_ref("decimal"))])])])
686 ),
687 case::not_constraint(
688 load_isl_type(r#" // For a schema with not constraint as below:
689 { not: { type: int } }
690 "#),
691 anonymous_type([not(anonymous_type_ref([type_constraint(named_type_ref("int"))]))])
692 ),
693 case::ordered_elements_constraint(
694 load_isl_type(r#" // For a schema with ordered_elements constraint as below:
695 { ordered_elements: [ symbol, { type: int }, ] }
696 "#),
697 anonymous_type([ordered_elements([variably_occurring_type_ref(named_type_ref("symbol"), UsizeRange::new_single_value(1)), variably_occurring_type_ref(anonymous_type_ref([type_constraint(named_type_ref("int"))]), UsizeRange::new_single_value(1))])])
698 ),
699 case::closed_fields_constraint(
700 load_isl_type_v2_0(r#" // For a schema with fields constraint as below:
701 { fields: closed::{ name: string, id: int} }
702 "#),
703 anonymous_type([isl_constraint::v_2_0::fields(vec![("name".to_owned(), variably_occurring_type_ref(named_type_ref("string"), UsizeRange::zero_or_one())), ("id".to_owned(), variably_occurring_type_ref(named_type_ref("int"), UsizeRange::zero_or_one()))].into_iter(), true)]),
704 ),
705 case::fields_constraint(
706 load_isl_type(r#" // For a schema with fields constraint as below:
707 { fields: { name: string, id: int} }
708 "#),
709 anonymous_type([fields(vec![("name".to_owned(), variably_occurring_type_ref(named_type_ref("string"), UsizeRange::zero_or_one())), ("id".to_owned(), variably_occurring_type_ref(named_type_ref("int"), UsizeRange::zero_or_one()))].into_iter())]),
710 ),
711 case::field_names_constraint(
712 load_isl_type_v2_0(r#" // For a schema with field_names constraint as below:
713 { field_names: distinct::symbol }
714 "#),
715 isl_type::v_2_0::anonymous_type([isl_constraint::v_2_0::field_names(isl_type_reference::v_2_0::named_type_ref("symbol"), true)]),
716 ),
717 case::contains_constraint(
718 load_isl_type(r#" // For a schema with contains constraint as below:
719 { contains: [true, 1, "hello"] }
720 "#),
721 anonymous_type([contains([true.into(), 1.into(), "hello".to_owned().into()])])
722 ),
723 case::container_length_constraint(
724 load_isl_type(r#" // For a schema with container_length constraint as below:
725 { container_length: 3 }
726 "#),
727 anonymous_type([container_length(3.into())])
728 ),
729 case::byte_length_constraint(
730 load_isl_type(r#" // For a schema with byte_length constraint as below:
731 { byte_length: 3 }
732 "#),
733 anonymous_type([byte_length(3.into())])
734 ),
735 case::codepoint_length_constraint(
736 load_isl_type(r#" // For a schema with codepoint_length constraint as below:
737 { codepoint_length: 3 }
738 "#),
739 anonymous_type([codepoint_length(3.into())])
740 ),
741 case::element_constraint(
742 load_isl_type(r#" // For a schema with element constraint as below:
743 { element: int }
744 "#),
745 anonymous_type([element(named_type_ref("int"))])
746 ),
747 case::distinct_element_constraint(
748 load_isl_type_v2_0(r#" // For a schema with distinct element constraint as below:
749 { element: distinct::int }
750 "#),
751 isl_type::v_2_0::anonymous_type([isl_constraint::v_2_0::element(named_type_ref("int"), true)])
752 ),
753 case::annotations_constraint(
754 load_isl_type(r#" // For a schema with annotations constraint as below:
755 { annotations: closed::[red, blue, green] }
756 "#),
757 anonymous_type([annotations(vec!["closed"], vec![Symbol::from("red").into(), Symbol::from("blue").into(), Symbol::from("green").into()])])
758 ),
759 case::standard_syantx_annotations_constraint(
760 load_isl_type_v2_0(r#" // For a schema with annotations constraint as below:
761 { annotations: { container_length: 1 } }
762 "#),
763 isl_type::v_2_0::anonymous_type([isl_constraint::v_2_0::annotations(isl_type_reference::v_2_0::anonymous_type_ref([isl_constraint::v_2_0::container_length(1.into())]))])
764 ),
765 case::precision_constraint(
766 load_isl_type(r#" // For a schema with precision constraint as below:
767 { precision: 2 }
768 "#),
769 anonymous_type([precision(2.into())])
770 ),
771 case::scale_constraint(
772 load_isl_type(r#" // For a schema with scale constraint as below:
773 { scale: 2 }
774 "#),
775 anonymous_type([scale(2.into())])
776 ),
777 case::exponent_constraint(
778 load_isl_type_v2_0(r#" // For a schema with exponent constraint as below:
779 { exponent: 2 }
780 "#),
781 isl_type::v_2_0::anonymous_type([isl_constraint::v_2_0::exponent(2.into())])
782 ),
783 case::timestamp_precision_constraint(
784 load_isl_type(r#" // For a schema with timestamp_precision constraint as below:
785 { timestamp_precision: year }
786 "#),
787 anonymous_type([timestamp_precision(TimestampPrecisionRange::new_single_value(TimestampPrecision::Year))])
788 ),
789 case::valid_values_constraint(
790 load_isl_type(r#" // For a schema with valid_values constraint as below:
791 { valid_values: [2, 3.5, 5e7, "hello", hi] }
792 "#),
793 anonymous_type([valid_values(vec![2.into(), Decimal::new(35, -1).into(), 5e7.into(), "hello".to_owned().into(), Symbol::from("hi").into()]).unwrap()])
794 ),
795 case::valid_values_with_range_constraint(
796 load_isl_type(r#" // For a schema with valid_values constraint as below:
797 { valid_values: range::[1, 5.5] }
798 "#),
799 anonymous_type(
800 [valid_values(vec![
801 ValidValue::NumberRange(
802 NumberRange::new_inclusive(
803 1.into(),
804 Decimal::new(55, -1)
805 ).unwrap()
806 )
807 ]).unwrap()]
808 )
809 ),
810 case::utf8_byte_length_constraint(
811 load_isl_type(r#" // For a schema with utf8_byte_length constraint as below:
812 { utf8_byte_length: 3 }
813 "#),
814 anonymous_type([utf8_byte_length(3.into())])
815 ),
816 case::regex_constraint(
817 load_isl_type(r#" // For a schema with regex constraint as below:
818 { regex: "[abc]" }
819 "#),
820 anonymous_type(
821 [
822 regex(
823 false, false, "[abc]".to_string()
826 )
827 ]
828 )
829 ),
830 case::timestamp_offset_constraint(
831 load_isl_type(r#" // For a schema with timestamp_offset constraint as below:
832 { timestamp_offset: ["+00:00"] }
833 "#),
834 anonymous_type(
835 [timestamp_offset(vec!["+00:00".try_into().unwrap()])]
836 )
837 ),
838 case::ieee754_float_constraint(
839 load_isl_type_v2_0(r#" // For a schema with ieee754_float constraint as below:
840 { ieee754_float: binary16 }
841 "#),
842 isl_type::v_2_0::anonymous_type([isl_constraint::v_2_0::ieee754_float(Ieee754InterchangeFormat::Binary16)])
843 ),
844 )]
845 fn owned_struct_to_isl_type(isl_type1: IslType, isl_type2: IslType) {
846 assert_eq!(isl_type1, isl_type2);
848 }
849
850 fn load_timestamp_precision_range(text: &str) -> IonSchemaResult<TimestampPrecisionRange> {
852 TimestampPrecisionRange::from_ion_element(
853 &Element::read_one(text.as_bytes()).expect("parsing failed unexpectedly"),
854 |e| {
855 let symbol_text = e.as_symbol().and_then(Symbol::text)?;
856 TimestampPrecision::try_from(symbol_text).ok()
857 },
858 )
859 }
860
861 fn load_number_range(text: &str) -> IonSchemaResult<NumberRange> {
863 NumberRange::from_ion_element(
864 &Element::read_one(text.as_bytes()).expect("parsing failed unexpectedly"),
865 Element::any_number_as_decimal,
866 )
867 }
868
869 fn elements<T: Into<Element> + std::clone::Clone>(values: &[T]) -> Vec<Element> {
871 values.iter().cloned().map(|v| v.into()).collect()
872 }
873
874 const SKIP_LIST: [&str; 5] = [
875 "ion-schema-schemas/json/json.isl", "ion-schema-tests/ion_schema_1_0/nullable.isl", "ion-schema-tests/ion_schema_1_0/schema/import/import_inline.isl",
880 "ion-schema-tests/ion_schema_2_0/imports/tree/inline_import_a.isl",
881 "ion-schema-tests/ion_schema_2_0/imports/tree/inline_import_c.isl",
882 ];
883
884 fn is_skip_list_path(file_name: &str) -> bool {
885 SKIP_LIST
886 .iter()
887 .map(|p| p.replace('/', std::path::MAIN_SEPARATOR_STR))
888 .any(|p| p == file_name)
889 }
890
891 #[test_resources("ion-schema-tests/**/*.isl")]
892 #[test_resources("ion-schema-schemas/**/*.isl")]
893 fn test_write_to_isl(file_name: &str) -> IonSchemaResult<()> {
894 if is_skip_list_path(file_name) {
895 return Ok(());
896 }
897
898 let mut schema_system =
900 SchemaSystem::new(vec![Box::new(FileSystemDocumentAuthority::new(
901 Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap(),
902 ))]);
903
904 let expected_schema = schema_system.load_isl_schema(file_name)?;
906
907 let buffer = Vec::new();
909 let config = WriteConfig::<v1_0::Text>::new(TextFormat::Pretty);
910 let mut writer = Writer::new(config, buffer)?;
911
912 let write_schema_result = expected_schema.write_as_ion(&mut writer);
914 assert!(write_schema_result.is_ok());
915
916 let output = writer.close()?;
917
918 let actual_schema = schema_system.new_isl_schema(output.as_slice(), file_name)?;
920
921 assert_eq!(actual_schema, expected_schema);
924 Ok(())
925 }
926}