ion_schema/
schema.rs

1//! Represents a [`Schema`] which is collection of zero or more [`TypeDefinition`]s.
2//! Provides functions to get the underlying [`TypeDefinition`]s from the [`Schema`] that can be used to validate an Ion value.
3//!
4//! * `get_types`: This function returns an [`SchemaTypeIterator`] which can be used to iterate over the [`TypeDefinition`]s.
5//! * `get_type`: This function requires to pass the name of a type definition that you want to use for validation.
6//! It returns the [`TypeDefinition`] if it is defined in the [`Schema`] otherwise returns [`None`].
7//!
8
9use crate::import::Import;
10use crate::system::{TypeId, TypeStore};
11use crate::types::{TypeDefinition, TypeDefinitionImpl};
12use std::sync::Arc;
13
14/// A Schema is a collection of zero or more [`TypeDefinition`]s.
15///
16/// Each type may refer to other types within the same schema,
17/// or types imported into this schema from other schemas.
18/// To instantiate a [`Schema`], see [`SchemaSystem`].
19///
20/// [`SchemaSystem`]: crate::system::SchemaSystem
21#[derive(Debug, Clone)]
22pub struct Schema {
23    id: String,
24    types: Arc<TypeStore>,
25}
26
27impl Schema {
28    pub(crate) fn new<A: AsRef<str>>(id: A, types: Arc<TypeStore>) -> Self {
29        Self {
30            id: id.as_ref().to_owned(),
31            types,
32        }
33    }
34
35    /// Returns the id for this Schema
36    pub fn id(&self) -> &str {
37        &self.id
38    }
39
40    /// Returns an [Import] representing all the types imported from
41    /// the specified schema [id].
42    fn import(&self, id: String) -> Option<Import> {
43        todo!()
44    }
45
46    /// Returns an iterator over the imports of this [`Schema`].
47    fn imports(&self) -> SchemaTypeIterator {
48        todo!()
49    }
50
51    /// Returns an iterator over the imported types of this [`Schema`].
52    fn imported_types(&self) -> SchemaTypeIterator {
53        SchemaTypeIterator::new(Arc::clone(&self.types), self.types.get_imports())
54    }
55
56    /// Returns the requested type, if imported in this schema;
57    /// otherwise returns None.
58    pub fn get_imported_type<A: AsRef<str>>(&self, name: A) -> Option<TypeDefinition> {
59        let type_id = self.types.get_imported_type_id_by_name(name.as_ref())?;
60        Some(TypeDefinition::new(*type_id, Arc::clone(&self.types)))
61    }
62
63    /// Returns the requested type, if present in this schema or a a built in type;
64    /// otherwise returns None.
65    pub fn get_built_in_or_defined_type<A: AsRef<str>>(&self, name: A) -> Option<TypeDefinition> {
66        let type_id = self
67            .types
68            .get_built_in_type_id_or_defined_type_id_by_name(name.as_ref())?;
69        Some(TypeDefinition::new(*type_id, Arc::clone(&self.types)))
70    }
71
72    /// Returns the requested type, if present in this schema or a a built in type or imported in the schema;
73    /// otherwise returns None.
74    pub fn get_type<A: AsRef<str>>(&self, name: A) -> Option<TypeDefinition> {
75        let type_id = self.types.get_type_id_by_name(name.as_ref())?;
76        Some(TypeDefinition::new(*type_id, Arc::clone(&self.types)))
77    }
78
79    /// Returns an iterator over the types in this schema.
80    // This only includes named types defined within this schema.
81    pub fn get_types(&self) -> SchemaTypeIterator {
82        SchemaTypeIterator::new(Arc::clone(&self.types), self.types.get_types())
83    }
84
85    /// Returns a new [`Schema`] instance containing all the types of this
86    /// instance plus the provided type.  Note that the added type
87    /// in the returned instance will hide a type of the same name
88    /// from this instance.
89    fn plus_type(&self, schema_type: TypeDefinitionImpl) -> Self {
90        todo!()
91    }
92}
93
94/// Provides an Iterator which returns [`TypeDefinition`]s inside a [`Schema`]
95pub struct SchemaTypeIterator {
96    type_store: Arc<TypeStore>,
97    index: usize,
98    types: Vec<TypeId>,
99}
100
101impl SchemaTypeIterator {
102    fn new(type_store: Arc<TypeStore>, types: Vec<TypeId>) -> Self {
103        Self {
104            type_store,
105            index: 0,
106            types,
107        }
108    }
109}
110
111impl Iterator for SchemaTypeIterator {
112    type Item = TypeDefinition;
113
114    fn next(&mut self) -> Option<Self::Item> {
115        if self.index >= self.types.len() {
116            return None;
117        }
118        self.index += 1;
119        Some(TypeDefinition::new(
120            self.types[self.index - 1],
121            Arc::clone(&self.type_store),
122        ))
123    }
124}
125
126#[cfg(test)]
127mod schema_tests {
128    use super::*;
129    use crate::authority::MapDocumentAuthority;
130    use crate::system::{Resolver, SchemaSystem};
131    use ion_rs::Element;
132    use rstest::*;
133    use std::sync::Arc;
134
135    // helper function to be used by schema tests
136    fn load(text: &str) -> Vec<Element> {
137        Element::read_all(text.as_bytes())
138            .expect("parsing failed unexpectedly")
139            .into_iter()
140            .collect()
141    }
142
143    // helper function to be used by validation tests
144    fn load_schema_from_text(text: &str) -> Arc<Schema> {
145        // map with (id, ion content)
146        let map_authority = [("sample.isl", text)];
147        let mut schema_system =
148            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
149        schema_system.load_schema("sample.isl").unwrap()
150    }
151
152    #[rstest(
153        owned_elements, total_types,
154        case::type_constraint_with_named_type(
155            load(r#" // For a schema with named type as below:
156            type:: { name: my_type, type: any }
157        "#).into_iter(),
158            1 // this includes the named type my_int
159        ),
160        case::type_constraint_with_self_reference_type(
161            load(r#" // For a schema with self reference type as below:
162            type:: { name: my_int, type: my_int }
163        "#).into_iter(),
164            1 // this includes only my_int type
165        ),
166        case::type_constraint_with_nested_self_reference_type(
167            load(r#" // For a schema with nested self reference type as below:
168            type:: { name: my_int, type: { type: my_int } }
169        "#).into_iter(),
170            1 // this includes my_int type
171        ),
172        case::type_constraint_with_nested_type(
173            load(r#" // For a schema with nested types as below:
174            type:: { name: my_int, type: { type: int } }
175        "#).into_iter(),
176            1 // this includes my_int type
177        ),
178        case::type_constraint_with_nested_multiple_types(
179            load(r#" // For a schema with nested multiple types as below:
180            type:: { name: my_int, type: { type: int }, type: { type: my_int } }
181        "#).into_iter(),
182            1 //  this includes my_int type
183        ),
184        case::type_constraint_with_multiple_types(
185            load(r#" // For a schema with multiple type as below:
186             type:: { name: my_int, type: int }
187             type:: { name: my_bool, type: bool }
188        "#).into_iter(),
189            2
190        ),
191        case::all_of_constraint(
192            load(r#" // For a schema with all_of type as below:
193            type:: { name: all_of_type, all_of: [{ type: int }] }
194        "#).into_iter(),
195            1 // this includes named type all_of_type
196        ),
197        case::any_of_constraint(
198            load(r#" // For a schema with any_of constraint as below:
199                type:: { name: any_of_type, any_of: [{ type: int }, { type: decimal }] }
200            "#).into_iter(),
201            1 // this includes named type any_of_type
202        ),
203        case::one_of_constraint(
204            load(r#" // For a schema with one_of constraint as below:
205                type:: { name: one_of_type, one_of: [{ type: int }, { type: decimal }] }
206            "#).into_iter(),
207            1 // this includes named type one_of_type
208        ),
209        case::not_constraint(
210            load(r#" // For a schema with not constraint as below:
211                type:: { name: not_type, not: { type: int } }
212            "#).into_iter(),
213            1 // this includes named type not_type
214        ),
215        case::ordred_elements_constraint(
216            load(r#" // For a schema with ordered_elements constraint as below:
217                type:: { name: ordred_elements_type, ordered_elements: [ symbol, { type: int, occurs: optional }, ] }
218            "#).into_iter(),
219            1 // this includes named type ordered_elements_type
220        ),
221        case::fields_constraint(
222            load(r#" // For a schema with fields constraint as below:
223                type:: { name: fields_type, fields: { name: string, id: int} }
224            "#).into_iter(),
225            1 // this includes named type fields_type
226        ),
227        case::field_names_constraint(
228            load(r#" // For a schema with field_names constraint as below:
229                    $ion_schema_2_0
230                    type:: { name: field_names_type, field_names: distinct::symbol } 
231            "#).into_iter(),
232            1 // this includes named type field_names_type
233        ),
234        case::contains_constraint(
235            load(r#" // For a schema with contains constraint as below:
236                type:: { name: contains_type, contains: [true, 1, "hello"] }
237            "#).into_iter(),
238            1 // this includes named type contains_type
239        ),
240        case::container_length_constraint(
241            load(r#" // For a schema with container_length constraint as below:
242                    type:: { name: container_length_type, container_length: 3 }
243                "#).into_iter(),
244            1 // this includes named type container_length_type
245        ),
246        case::byte_length_constraint(
247            load(r#" // For a schema with byte_length constraint as below:
248                    type:: { name: byte_length_type, byte_length: 3 }
249                "#).into_iter(),
250            1 // this includes named type byte_length_type
251        ),
252        case::codepoint_length_constraint(
253            load(r#" // For a schema with codepoint_length constraint as below:
254                        type:: { name: codepoint_length_type, codepoint_length: 3 }
255                    "#).into_iter(),
256            1 // this includes named type codepoint_length_type
257        ),
258        case::element_constraint(
259            load(r#" // For a schema with element constraint as below:
260                    type:: { name: element_type, element: int }
261                 "#).into_iter(),
262            1 // this includes named type element_type
263        ),
264        case::distinct_element_constraint(
265            load(r#" // For a schema with distinct element constraint as below:
266                        $ion_schema_2_0
267                        type:: { name: distinct_element_type, element: distinct::int }
268                     "#).into_iter(),
269            1 // this includes named type distinct_element_type
270        ),
271        case::annotations_constraint(
272            load(r#" // For a schema with annotations constraint as below:
273                    type:: { name: annotations_type, annotations: closed::[red, blue, green] }
274                 "#).into_iter(),
275            1 // this includes named type annotations_type
276        ),
277        case::precision_constraint(
278            load(r#" // For a schema with precision constraint as below:
279                        type:: { name: precision_type, precision: 2 }
280                     "#).into_iter(),
281            1 // this includes named type precision_type
282        ),
283        case::scale_constraint(
284            load(r#" // For a schema with scale constraint as below:
285                    type:: { name: scale_type, scale: 2 }
286                 "#).into_iter(),
287            1 // this includes named type scale_type
288        ),
289        case::exponent_constraint(
290            load(r#" // For a schema with exponent constraint as below:
291                    $ion_schema_2_0
292                    type:: { name: exponent_type, exponent: -2 }
293                 "#).into_iter(),
294            1 // this includes named type exponent_type
295        ),
296        case::timestamp_precision_constraint(
297            load(r#" // For a schema with timestamp_precision constraint as below:
298                    type:: { name: timestamp_precision_type, timestamp_precision: month }
299                 "#).into_iter(),
300            1 // this includes named type timestamp_precision_type
301        ),
302        case::valid_values_constraint(
303            load(r#" // For a schema with valid_values constraint as below:
304                    type:: { name: valid_values_type, valid_values: range::[1, 3] }
305                 "#).into_iter(),
306            1 // this includes named type valid_values_type
307        ),
308        case::utf8_byte_length_constraint(
309            load(r#" // For a schema with utf8_byte_length constraint as below:
310                        type:: { name: utf8_byte_length_type, utf8_byte_length: 3 }
311                    "#).into_iter(),
312            1 // this includes named type utf8_byte_length_type
313        ),
314        case::regex_constraint(
315            load(r#" // For a schema with regex constraint as below:
316                    type:: { name: regex_type, regex: "[abc]" }
317                 "#).into_iter(),
318            1 // this includes named type regex_type
319        ),
320        case::timestamp_offset_constraint(
321            load(r#" // For a schema with timestamp_offset constraint as below:
322                        type:: { name: timestamp_offset_type, timestamp_offset: ["+07:00", "+08:00", "+08:45", "+09:00"] }
323                     "#).into_iter(),
324            1 // this includes named type regex_type
325        ),
326        case::ieee754_float_constraint(
327            load(r#" // For a schema with ieee754_float constraint as below:
328                        $ion_schema_2_0
329                        type:: { name: ieee754_float_type, ieee754_float: binary16 }
330                     "#).into_iter(),
331            1 // this includes named type ieee754_float_type
332        ),
333    )]
334    fn owned_elements_to_schema<I: Iterator<Item = Element>>(
335        owned_elements: I,
336        total_types: usize,
337    ) {
338        // create a type_store and resolver instance to be used for loading Elements as schema
339        let type_store = &mut TypeStore::default();
340        let mut resolver = Resolver::new(vec![]);
341
342        // create a isl from owned_elements and verifies if the result is `ok`
343        let isl_result = resolver.isl_schema_from_elements(owned_elements, "my_schema.isl");
344        assert!(isl_result.is_ok());
345
346        let isl = isl_result.expect("ISL schema should be syntactically correct");
347        // create a schema from isl and verifies if the result is `ok`
348        let schema = resolver.schema_from_isl_schema(isl.version(), isl, type_store, None);
349        assert!(schema.is_ok());
350
351        // check if the types of the created schema matches with the actual types specified by test case
352        assert_eq!(schema.unwrap().get_types().count(), total_types);
353    }
354
355    #[rstest(
356        valid_values, invalid_values, schema, type_name,
357        case::built_in_type(
358            load(r#"
359                5
360                0
361                -2
362            "#),
363            load(r#"
364                false
365                "hello"
366                5.4
367            "#),
368            load_schema_from_text(r#" // No schema defined, uses built-in types"#),
369        "int"
370        ),
371        case::type_constraint(
372            load(r#"
373                5
374                0
375                -2
376            "#),
377            load(r#"
378                false
379                "hello"
380                5.4
381            "#),
382            load_schema_from_text(r#" // For a schema with named type as below: 
383                type:: { name: my_int, type: int }
384            "#),
385        "my_int"
386        ),
387        case::nullable_annotation_int_type_constraint(
388            load(r#"
389                    null
390                    null.null
391                    null.int
392                    0
393                    -5
394                "#),
395            load(r#"
396                    null.decimal
397                    a
398                    "hello"
399                    false
400                "#),
401            load_schema_from_text(r#" // For a schema with named type and `nullable` annotation as below: 
402                    type:: { name: my_int, type: nullable::int }
403                "#),
404        "my_int"
405        ),
406        case::null_or_annotation_int_type_constraint(
407            load(r#"
408                        null
409                        null.null
410                        0
411                        -5
412                    "#),
413            load(r#"
414                        null.decimal
415                        a
416                        "hello"
417                        false
418                    "#),
419            load_schema_from_text(r#" // For a schema with named type and `$null_or` annotation as below: 
420                        $ion_schema_2_0
421                        type:: { name: my_int, type: $null_or::int }
422                    "#),
423        "my_int"
424        ),
425        case::nullable_annotation_float_type_constraint(
426            load(r#"
427                    null
428                    null.null
429                    null.float
430                    5e2
431                "#),
432            load(r#"
433                    null.decimal
434                    a
435                    "hello"
436                    false
437                    10
438                "#),
439            load_schema_from_text(r#" // For a schema with named type and `nullable` annotation as below: 
440                    type:: { name: my_float, type: nullable::float }
441                "#),
442        "my_float"
443        ),
444        case::nullable_annotation_string_type_constraint(
445            load(r#"
446                    null
447                    null.null
448                    null.string
449                    "hi"
450                "#),
451            load(r#"
452                    null.decimal
453                    null.symbol
454                    hello
455                    false
456                    10
457                "#),
458            load_schema_from_text(r#" // For a schema with named type and `nullable` annotation as below: 
459                    type:: { name: my_string, type: nullable::string }
460                "#),
461        "my_string"
462        ),
463        case::nullable_annotation_symbol_type_constraint(
464            load(r#"
465                        null
466                        null.null
467                        null.symbol
468                        a
469                    "#),
470            load(r#"
471                        null.string
472                        10.5
473                        "hello"
474                        false
475                        10
476                    "#),
477            load_schema_from_text(r#" // For a schema with named type and `nullable` annotation as below: 
478                        type:: { name: my_symbol, type: nullable::symbol }
479                    "#),
480        "my_symbol"
481        ),
482        case::nullable_annotation_decimal_type_constraint(
483            load(r#"
484                    null
485                    null.null
486                    null.decimal
487                    10.5
488                "#),
489            load(r#"
490                    null.int
491                    a
492                    "hello"
493                    false
494                    10
495                "#),
496            load_schema_from_text(r#" // For a schema with named type and `nullable` annotation as below: 
497                    type:: { name: my_decimal, type: nullable::decimal }
498                "#),
499        "my_decimal"
500        ),
501        case::nullable_annotation_timestamp_type_constraint(
502            load(r#"
503                    null
504                    null.null
505                    null.timestamp
506                    2000-01T
507                "#),
508            load(r#"
509                    null.decimal
510                    a
511                    "hello"
512                    false
513                    10
514                "#),
515            load_schema_from_text(r#" // For a schema with named type and `nullable` annotation as below: 
516                    type:: { name: my_timestamp, type: nullable::timestamp }
517                "#),
518        "my_timestamp"
519        ),
520        case::nullable_annotation_blob_type_constraint(
521            load(r#"
522                    null
523                    null.null
524                    null.blob
525                    {{ aGVsbG8= }}
526                "#),
527            load(r#"
528                    null.clob
529                    a
530                    "hello"
531                    false
532                    10
533                "#),
534            load_schema_from_text(r#" // For a schema with named type and `nullable` annotation as below: 
535                    type:: { name: my_blob, type: nullable::blob }
536                "#),
537        "my_blob"
538        ),
539        case::nullable_annotation_clob_type_constraint(
540            load(r#"
541                    null
542                    null.null
543                    null.clob
544                    {{"12345"}}
545                "#),
546            load(r#"
547                    null.blob
548                    a
549                    "hello"
550                    false
551                    10
552                "#),
553            load_schema_from_text(r#" // For a schema with named type and `nullable` annotation as below: 
554                    type:: { name: my_clob, type: nullable::clob }
555                "#),
556        "my_clob"
557        ),
558        case::nullable_annotation_text_type_constraint(
559            load(r#"
560                    null
561                    null.null
562                    null.symbol
563                    null.string
564                    "hello"
565                    hello
566                "#),
567            load(r#"
568                    null.decimal
569                    10.5
570                    false
571                    10
572                "#),
573            load_schema_from_text(r#" // For a schema with named type and `nullable` annotation as below: 
574                    type:: { name: my_text, type: nullable::text }
575                "#),
576        "my_text"
577        ),
578        case::nullable_annotation_lob_type_constraint(
579            load(r#"
580                    null
581                    null.null
582                    null.blob
583                    null.clob
584                    {{ aGVsbG8= }}
585                    {{"12345"}}
586                "#),
587            load(r#"
588                    null.decimal
589                    10.5
590                    false
591                    10
592                "#),
593            load_schema_from_text(r#" // For a schema with named type and `nullable` annotation as below: 
594                    type:: { name: my_lob, type: nullable::lob }
595                "#),
596        "my_lob"
597        ),
598        case::nullable_annotation_number_type_constraint(
599            load(r#"
600                    null
601                    null.null
602                    null.int
603                    null.float
604                    null.decimal
605                    10.5
606                    10e2
607                    5
608                "#),
609            load(r#"
610                    null.string
611                    "hello"
612                    false
613                    a
614                "#),
615            load_schema_from_text(r#" // For a schema with named type and `nullable` annotation as below: 
616                    type:: { name: my_number, type: nullable::number }
617                "#),
618        "my_number"
619        ),
620        case::nullable_atomic_type_constraint(
621            load(r#"
622                5
623                0
624                -2
625                null.int
626            "#),
627            load(r#"
628                false
629                "hello"
630                5.4
631            "#),
632            load_schema_from_text(r#" // For a schema with named type as below: 
633                type:: { name: my_nullable_int, type: $int }
634            "#),
635        "my_nullable_int"
636        ),
637        case::nullable_derived_type_constraint(
638            load(r#"
639                "hello"
640                hello
641                null.string
642                null.symbol
643            "#),
644            load(r#"
645                false
646                5
647                null.int
648                null.decimal
649                null.null
650                5.4
651            "#),
652            load_schema_from_text(r#" // For a schema with named type as below: 
653                    type:: { name: my_nullable_text, type: $text }
654                "#),
655        "my_nullable_text"
656        ),
657        case::not_constraint(
658            load(r#"
659                true
660                "hello"
661                5.4
662                6e10
663            "#),
664            load(r#"
665                5
666                0
667                -1
668            "#),
669            load_schema_from_text(r#" // For a schema with not constraint as below: 
670                type:: { name: not_type, not: { type: int } }
671            "#),
672        "not_type"
673        ),
674        case::one_of_constraint(
675            load(r#"
676                5
677                -5
678                5.4
679                -5.4
680            "#),
681            load(r#"
682                false
683                "hello"
684                hey
685                null.int
686            "#),
687            load_schema_from_text(r#" // For a schema with one_of constraint as below: 
688                type:: { name: one_of_type, one_of: [int, decimal] }
689            "#),
690        "one_of_type"
691        ),
692    // TODO: add a test case for all_of constraint
693        case::any_of_constraint(
694            load(r#"
695                5
696                5.4
697                true
698            "#),
699            load(r#"
700                "hello"
701                hey
702                6e10
703            "#),
704            load_schema_from_text(r#" // For a schema with any_of constraint as below: 
705                type:: { name: any_of_type, any_of: [int, decimal, bool] }
706            "#),
707        "any_of_type"
708        ),
709        case::ordered_elements_constraint(
710            load(r#"
711                    [true, 5, 6, 7, "hey"]
712                    [false, 5, 6, 7]
713                    [false, 7, 8, "hello"]
714                    [true, 7, "hi"]
715                    [true, 8]
716               "#),
717            load(r#"
718                    [true]
719                    [5, true, "hey"]
720                    [null.bool, 5]
721                    ["hello", 5]
722                    [true, "hey"]
723                    "hello"
724                    hey
725                    6e10
726                    null.list
727               "#),
728            load_schema_from_text(r#" // For a schema with ordered_elements constraint as below:
729                    type:: { name: ordered_elements_type, ordered_elements: [bool, { type: int, occurs: range::[1, 3] }, { type: string, occurs: optional } ] }
730               "#),
731        "ordered_elements_type"
732        ),
733        case::ordered_elements_constraint_for_overlapping_types(
734            load(r#"
735                     [1, 2, 3]
736                     [1, 2, foo]
737                     [1.0, foo]
738                     [1, 2.0, 3]
739                     [1, 2]
740                     [1, foo]
741                "#),
742            load(r#"
743                     [1]
744                     [foo]
745                     [true, 1, foo]
746                "#),
747            load_schema_from_text(r#" // For a schema with ordered_elements constraint as below:
748                        type:: { name: ordered_elements_type, ordered_elements:[{ type: int, occurs: optional }, { type: number, occurs: required }, { type: any, occurs: required }] }
749                "#),
750        "ordered_elements_type"
751        ),
752        case::fields_constraint(
753            load(r#"
754                     { name: "Ion", id: 1 }
755                     { id: 1 }
756                     { name: "Ion" }
757                     { name: "Ion", id: 1, name: "Schema" }
758                     { } // This is valid because all fields are optional
759                     { greetings: "hello" } // This is valid because open content is allowed by default
760                "#),
761            load(r#"
762                    null.struct
763                    null
764                    { name: "Ion", id: 1, id: 2 }
765                "#),
766            load_schema_from_text(r#" // For a schema with fields constraint as below:
767                        type:: { name: fields_type,  fields: { name: { type: string, occurs: range::[0,2] }, id: int } }
768                "#),
769        "fields_type"
770        ),
771        case::fields_constraint_with_closed_content(
772            load(r#"
773                         { name: "Ion", id: 1 }
774                         { id: 1 }
775                         { name: "Ion" }
776                         { name: "Ion", id: 1, name: "Schema" }
777                         { } // This is valid because all fields are optional
778                         { greetings: "hello" } // This is valid because open content is allowed by default
779                    "#),
780            load(r#"
781                        null.struct
782                        null
783                        { name: "Ion", id: 1, id: 2 }
784                    "#),
785            load_schema_from_text(r#" // For a schema with fields constraint as below:
786                            type:: { name: fields_type,  fields: { name: { type: string, occurs: range::[0,2] }, id: int } }
787                    "#),
788        "fields_type"
789        ),
790        case::fields_constraint_with_closed_annotation(
791            load(r#"
792                     { name: "Ion", id: 1 }
793                     { id: 1 }
794                     { name: "Ion" }
795                     { name: "Ion", id: 1, name: "Schema" }
796                     { }
797                "#),
798            load(r#"
799                    null.struct
800                    null
801                    { name: "Ion", id: 1, id: 2 }
802                    { greetings: "hello" }
803                "#),
804            load_schema_from_text(r#" // For a schema with fields constraint with `closed` annotation as below:
805                        $ion_schema_2_0
806                        type:: { name: fields_type, fields: closed::{ name: { type: string, occurs: range::[0,2] }, id: int } }
807                "#),
808        "fields_type"
809        ),
810        case::field_names_constraint(
811            load(r#"
812                     { name: "Ion", id: 1 }
813                     { id: 1 }
814                     { name: "Ion" }
815                     { }
816                "#),
817            load(r#"
818                    null.struct
819                    null
820                    { name: "Ion", id: 1, name: "Schema" }
821                    { name: "Ion", id: 1, id: 2 }
822                "#),
823            load_schema_from_text(r#" // For a schema with field_names constraint as below:
824                        $ion_schema_2_0
825                        type:: { name: field_names_type, field_names: distinct::symbol }
826                "#),
827        "field_names_type"
828        ),
829        case::contains_constraint(
830            load(r#"
831                    [[5], '3', {a: 7}, true, 2.0, "4", (6), 1, extra_value]
832                    ([5]  '3'  {a: 7}  true  2.0  "4"  (6)  1 extra_value)
833                "#),
834            load(r#"
835                    null
836                    null.null
837                    null.int
838                    null.list
839                    null.sexp
840                    null.struct
841                    [true, 1, 2.0, '3', "4", [5], (6)]
842                "#),
843            load_schema_from_text(r#" // For a schema with contains constraint as below:
844                        type::{ name: contains_type, contains: [true, 1, 2.0, '3', "4", [5], (6), {a: 7} ] }
845                "#),
846        "contains_type"
847        ),
848        case::container_length_with_range_constraint(
849            load(r#"
850                        [1]
851                        [1, 2]
852                        [1, 2, 3]
853                        (4)
854                        (4 5)
855                        (4 5 6)
856                        { a: 7 }
857                        { a: 7, b: 8 }
858                        { a: 7, b: 8, c: 9 }
859                    "#),
860            load(r#"
861                        null
862                        null.bool
863                        null.null
864                        null.list
865                        null.sexp
866                        null.struct
867                        []
868                        ()
869                        {}
870                        [1, 2, 3, 4]
871                        (1 2 3 4)
872                        { a: 1, b:2, c:3, d:4}
873                    "#),
874            load_schema_from_text(r#" // For a schema with contianer_length constraint as below:
875                            type::{ name: container_length_type, container_length: range::[1,3] }
876                    "#),
877        "container_length_type"
878        ),
879        case::container_length_exact_constraint(
880            load(r#"
881                            [null, null, null]
882                            [1, 2, 3]
883                            (4 5 6)
884                            { a: 7, b: 8, c: 9 }
885                        "#),
886            load(r#"
887                            null
888                            null.bool
889                            null.null
890                            null.list
891                            null.sexp
892                            null.struct
893                            []
894                            ()
895                            {}
896                            [1]
897                            (1)
898                            { a: 1 }
899                            [1, 2, 3, 4]
900                            (1 2 3 4)
901                            { a: 1, b:2, c:3, d:4}
902                        "#),
903            load_schema_from_text(r#" // For a schema with contianer_length constraint as below:
904                                type::{ name: container_length_type, container_length: 3 }
905                        "#),
906        "container_length_type"
907        ),
908        case::byte_length_constraint(
909            load(r#"
910                            {{"12345"}}
911                            {{ aGVsbG8= }}
912                        "#),
913            load(r#"
914                            null
915                            null.bool
916                            null.null
917                            null.clob
918                            null.blob
919                            {{}}
920                            {{"1234"}}
921                            {{"123456"}}
922                        "#),
923            load_schema_from_text(r#" // For a schema with byte_length constraint as below:
924                                type::{ name: byte_length_type, byte_length: 5 }
925                        "#),
926        "byte_length_type"
927        ),
928        case::codepoint_length_constraint(
929            load(r#"
930                            '12345'
931                            "12345"
932                            "1234😎"
933                            "हैलो!"
934                        "#),
935            load(r#"
936                            null
937                            null.bool
938                            null.null
939                            null.string
940                            null.symbol
941                            ""
942                            "😎"
943                            '1234'
944                            "123456"
945                        "#),
946            load_schema_from_text(r#" // For a schema with codepoint_length constraint as below:
947                                type::{ name: codepoint_length_type, codepoint_length: 5 }
948                        "#),
949        "codepoint_length_type"
950        ),
951        case::element_constraint(
952            load(r#"
953                          []
954                          [1]
955                          [1, 2, 3]
956                          ()
957                          (1)
958                          (1 2 3)
959                          { a: 1, b: 2, c: 3 }
960                        "#),
961            load(r#"
962                          null.list
963                          [1.]
964                          [1e0]
965                          [1, 2, null.int]
966                          (1 2 3 true 4)
967                          { a: 1, b: 2, c: true }
968                          { a: 1, b: 2, c: null.int }
969                        "#),
970            load_schema_from_text(r#" // For a schema with element constraint as below:
971                                type::{ name: element_type, element: int }
972                        "#),
973        "element_type"
974        ),
975        case::distinct_element_constraint(
976            load(r#"
977                          []
978                          [1]
979                          [1, 2, 3]
980                          ()
981                          (1)
982                          (1 2 3)
983                          { a: 1, b: 2, c: 3 }
984                        "#),
985            load(r#"
986                          null.list
987                          [1.]
988                          [1e0]
989                          [1, 1, 2, 3]
990                          [a::1, b::1, a::2, a::2, 3]
991                          [1, 2, null.int]
992                          (1 2 3 true 4)
993                          (1 1 2 2 3)
994                          (a::1 b::1 a::2 a::2 3)
995                          { a: 1, b: 2, c: true }
996                          { a: 1, b: 1 }
997                          { a: c::1, b: c::1 }
998                          { a: 1, b: 2, c: null.int }
999                        "#),
1000            load_schema_from_text(r#" // For a schema with distinct element constraint as below:
1001                                $ion_schema_2_0
1002                                type::{ name: distinct_element_type, element: distinct::int }
1003                        "#),
1004        "distinct_element_type"
1005        ),
1006        case::element_with_self_ref_type_constraint(
1007            load(r#"
1008                          5
1009                          "hello"
1010                          [1, 5]
1011                          ["hi", "hello"]
1012                        "#),
1013            load(r#"
1014                          5.5
1015                          null
1016                          null.list
1017                          null.int
1018                          null.string
1019                          (1 2 3)
1020                        "#),
1021            load_schema_from_text(r#" // For a schema with element constraint with self referencing typeas below:
1022                                type::{ name: my_type, one_of: [ int, string, { type: list, element: my_type } ]  }
1023                        "#),
1024        "my_type"
1025        ),
1026        case::fields_with_self_ref_type_constraint(
1027            load(r#"
1028                          5
1029                          "hello"
1030                          { foo: "hi" }
1031                          { foo: 5 }
1032                          { foo: { foo: 5 } }
1033                        "#),
1034            load(r#"
1035                          5.5
1036                          null
1037                          null.struct
1038                          { foo: bar } 
1039                          { foo: 5.5 }
1040                        "#),
1041            load_schema_from_text(r#" // For a schema with fields constraint with self referencing typeas below:
1042                                type::{ name: my_type, one_of: [ int, string, { type: struct, fields: { foo: my_type} } ]  }
1043                        "#),
1044        "my_type"
1045        ),
1046        case::annotations_constraint(
1047            load(r#"
1048                          b::d::5
1049                          a::b::d::5
1050                          b::c::d::5
1051                          a::b::c::d::5
1052                          b::a::d::5    // 'a' is treated as open content
1053                          c::b::d::5       // 'c' is treated as open content
1054                          c::b::a::d::5    // 'a' and 'c' are treated as open content
1055                          open_content::open_content::b::d::5
1056                          b::d::3.5
1057                          b::d::"hello"
1058                        "#),
1059            load(r#"
1060                          b::5
1061                          d::5
1062                          d::b::5
1063                          5
1064                        "#),
1065            load_schema_from_text(r#" // For a schema with annotations constraint as below:
1066                                type::{ name: annotations_type, annotations: ordered::[a, required::b, c, required::d] }
1067                        "#),
1068        "annotations_type"
1069        ),
1070        case::precision_constraint(
1071            load(r#"
1072                          42.
1073                          42d0
1074                          42d-0
1075                          4.2d1
1076                          0.42d2
1077                        "#),
1078            load(r#"
1079                          null
1080                          null.null
1081                          null.decimal
1082                          null.string
1083                          4.
1084                          42.0
1085                        "#),
1086            load_schema_from_text(r#" // For a schema with precision constraint as below:
1087                                type::{ name: precision_type, precision: 2 }
1088                        "#),
1089        "precision_type"
1090        ),
1091        case::scale_constraint(
1092            load(r#"
1093                          0.4
1094                          0.42
1095                          0.432
1096                          0.4321
1097                          43d3
1098                          0d0
1099                        "#),
1100            load(r#"
1101                          null
1102                          null.null
1103                          null.decimal
1104                          null.symbol
1105                          0.43210
1106                        "#),
1107            load_schema_from_text(r#" // For a schema with scale constraint as below:
1108                                type::{ name: scale_type, scale: range::[min, 4] }
1109                        "#),
1110        "scale_type"
1111        ),
1112        case::exponent_constraint(
1113            load(r#"
1114                      0.4
1115                      0.42
1116                      0.432
1117                      0.4321
1118                      43d3
1119                      0d0
1120                    "#),
1121            load(r#"
1122                      null
1123                      null.null
1124                      null.decimal
1125                      null.symbol
1126                      0.43210
1127                    "#),
1128            load_schema_from_text(r#" // For a schema with exponent constraint as below:
1129                            $ion_schema_2_0
1130                            type::{ name: exponent_type, exponent: range::[-4, 4] }
1131                    "#),
1132        "exponent_type"
1133        ),
1134        case::timestamp_precision_constraint(
1135            load(r#"
1136                          2000-01T
1137                          2000-01-01T
1138                          2000-01-01T00:00Z
1139                          2000-01-01T00:00:00Z
1140                        "#),
1141            load(r#"
1142                          2000T
1143                          2000-01-01T00:00:00.0Z
1144                          null
1145                          null.timestamp
1146                          null.symbol
1147                        "#),
1148            load_schema_from_text(r#" // For a schema with timestamp precision constraint as below:
1149                                type::{ name: timestamp_precision_type, timestamp_precision: range::[month, second] }
1150                        "#),
1151        "timestamp_precision_type"
1152        ),
1153        case::utf8_byte_length_constraint(
1154            load(r#"
1155                          "hello"
1156                          hello
1157                          world
1158                          "world"
1159                          '\u00A2\u20AC'
1160                        "#),
1161            load(r#"
1162                          null
1163                          null.bool
1164                          null.null
1165                          null.string
1166                          null.symbol
1167                          ""
1168                          "hi"
1169                          hi
1170                          "greetings"
1171                          greetings
1172                          '\u20AC\u20AC'
1173                        "#),
1174            load_schema_from_text(r#" // For a schema with byte_length constraint as below:
1175                                type::{ name: utf8_byte_length_type, utf8_byte_length: 5 }
1176                        "#),
1177        "utf8_byte_length_type"
1178        ),
1179        case::valid_values_constraint(
1180            load(r#"
1181                          2
1182                          3
1183                          5.5  
1184                          "hello"
1185                        "#),
1186            load(r#"
1187                          5.6
1188                          1
1189                          [1, 2 ,3]
1190                          { greetings: "hello" }
1191                          {{"hello"}}
1192                          null
1193                        "#),
1194            load_schema_from_text(r#" // For a schema with valid values constraint as below:
1195                                type::{ name: valid_values_type, valid_values: [2, 3, 5.5, "hello"] }
1196                        "#),
1197        "valid_values_type"
1198        ),
1199        case::valid_values_with_range_constraint(
1200            load(r#"
1201                      1
1202                      2
1203                      3
1204                      4
1205                      5  
1206                      4.5
1207                      2d0
1208                      30e-1
1209                    "#),
1210            load(r#"
1211                      0
1212                      -2
1213                      5.6
1214                      6
1215                      null
1216                    "#),
1217            load_schema_from_text(r#" // For a schema with valid values constraint as below:
1218                            type::{ name: valid_values_type, valid_values: range::[1, 5.5] }
1219                    "#),
1220        "valid_values_type"
1221        ),
1222        case::regex_constraint(
1223            load(r#"
1224                      "ab"
1225                      "cd"
1226                      "ef"
1227                    "#),
1228            load(r#"
1229                      "a"
1230                      "ac"
1231                      "ace"
1232                      "bdf"
1233                    "#),
1234            load_schema_from_text(r#" // For a schema with regex constraint as below:
1235                            type::{ name: regex_type, regex: "ab|cd|ef" }
1236                    "#),
1237        "regex_type"
1238        ),
1239        case::regex_v2_0_constraint(
1240            load(r#"
1241                        "/"
1242                        ":"
1243                        "@"
1244                        "["
1245                        "`"
1246                        "{"
1247                        " "
1248                    "#),
1249            load(r#"
1250                        "a"
1251                        "A"
1252                        "z"
1253                        "Z"
1254                        "0"
1255                        "9"
1256                        "_"
1257                    "#),
1258            load_schema_from_text(r#" // For a schema with regex constraint as below:
1259                            $ion_schema_2_0
1260                            type::{ name: regex_type, regex: "\\W" }
1261                    "#),
1262        "regex_type"
1263        ),
1264        case::timestamp_offset_constraint(
1265            load(r#"
1266                      2000T
1267                      2000-01-01T00:00:00-00:00   // unknown local offset
1268                      2000-01-01T00:00:00Z        // UTC
1269                      2000-01-01T00:00:00+00:00   // UTC
1270                      2000-01-01T00:00:00+01:00
1271                      2000-01-01T00:00:00-01:01
1272                    "#),
1273            load(r#"
1274                      2000-01-01T00:00:00-01:00   
1275                      2000-01-01T00:00:00+01:01  
1276                      2000-01-01T00:00:00+07:00
1277                    "#),
1278            load_schema_from_text(r#" // For a schema with timestamp_offset constraint as below:
1279                            type::{ name: timestamp_offset_type, timestamp_offset: ["-00:00", "+00:00", "+01:00", "-01:01"] }
1280                    "#),
1281        "timestamp_offset_type"
1282        ),
1283        case::ieee754_float_constraint(
1284            load(r#"
1285                      1e0
1286                      -1e0
1287                      65504e0
1288                      -65504e0
1289                      nan
1290                      +inf
1291                      -inf
1292                    "#),
1293            load(r#"
1294                      null.float
1295                      5
1296                      5d0
1297                      (5e0)
1298                      [5e0]
1299                      65505e0
1300                      -65505e0
1301                    "#),
1302            load_schema_from_text(r#" // For a schema with timestamp precision constraint as below:
1303                            $ion_schema_2_0
1304                            type::{ name: ieee754_float_type, ieee754_float: binary16 }
1305                    "#),
1306        "ieee754_float_type"
1307        ),
1308        case::annotations_constraint_with_standard_syntax(
1309            load(r#"
1310                   a::0
1311                   b::1
1312                   c::2
1313                "#),
1314            load(r#"
1315                   0
1316                   $a::1
1317                   _c::2
1318                   ''::3
1319                "#),
1320            load_schema_from_text(r#" // For a schema with annotations constraint as below:
1321                            $ion_schema_2_0
1322                            type::{ name: standard_annotations_type, annotations: { element: { regex: "^[a-z]$" }, container_length: 1 } }
1323                    "#),
1324        "standard_annotations_type"
1325        )
1326    )]
1327    fn type_validation(
1328        valid_values: Vec<Element>,
1329        invalid_values: Vec<Element>,
1330        schema: Arc<Schema>,
1331        type_name: &str,
1332    ) {
1333        let type_ref: TypeDefinition = schema.get_built_in_or_defined_type(type_name).unwrap();
1334        // check for validation without any violations
1335        for valid_value in valid_values.iter() {
1336            // there is only a single type in each schema defined above hence validate with that type
1337            let validation_result = type_ref.validate(valid_value);
1338            validation_result.unwrap();
1339        }
1340        // check for violations due to invalid values
1341        for invalid_value in invalid_values.iter() {
1342            // there is only a single type in each schema defined above hence validate with that type
1343            let validation_result = type_ref.validate(invalid_value);
1344            assert!(validation_result.is_err());
1345        }
1346    }
1347}