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