Skip to main content

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