Skip to main content

ion_schema/
system.rs

1//! Provides functions for getting instances of [`Schema`] using [`SchemaSystem`].
2//!
3//! ## Example:
4//! In general, users will create one or more authorities and use them to build the [`SchemaSystem`].
5//! Then this [`SchemaSystem`] is used to create instances of [`Schema`].
6//!
7//! ```
8//! use ion_schema::authority::{MapDocumentAuthority, FileSystemDocumentAuthority, DocumentAuthority};
9//! use ion_schema::system::SchemaSystem;
10//! use std::path::Path;
11//!
12//! // create a vector of authorities and construct schema system
13//! let authorities: Vec<Box<dyn DocumentAuthority>> = vec![Box::new(
14//!             FileSystemDocumentAuthority::new(Path::new("schemas")),
15//!         )];
16//! let mut schema_system = SchemaSystem::new(authorities);
17//!
18//! // use this schema_system to load a schema as following
19//! let schema = schema_system.load_schema("sample.isl");
20//! ```
21
22use crate::authority::DocumentAuthority;
23use crate::ion_schema_element::IonSchemaElementType;
24use crate::isl::isl_import::{IslImport, IslImportType};
25use crate::isl::isl_type::IslType;
26use crate::isl::{IslSchema, IslVersion};
27use crate::result::{
28    invalid_schema_error, invalid_schema_error_raw, unresolvable_schema_error,
29    unresolvable_schema_error_raw, IonSchemaError, IonSchemaResult,
30};
31use crate::schema::Schema;
32use crate::types::{BuiltInTypeDefinition, Nullability, TypeDefinitionImpl, TypeDefinitionKind};
33use crate::{is_isl_version_marker, is_reserved_word, UserReservedFields};
34use ion_rs::IonType;
35use ion_rs::{Annotations, Element};
36use std::collections::{HashMap, HashSet};
37use std::io::ErrorKind;
38use std::sync::Arc;
39
40// TODO: Shift PendingTypes and TypeStore implementations to a separate module
41/// Stores information about types that are in the process of being defined.
42///
43/// An ISL type definition can include types that are not yet fully defined.
44/// For example, an ISL type definition might include:
45/// * A reference to itself. This could happen in a recursive structure like a
46///   linked list or binary tree.
47/// * A nested anonymous type definition.
48/// * A reference to type definition that is followed by current type definition. These type references
49///   will be deferred to later check if a type definition with that name exists in the schema.
50///
51/// Because the [`SchemaSystem`] does not yet know the complete definition
52/// of these types, it cannot find them in the [`TypeStore`].
53/// An instance of [`PendingTypes`] is used to track information about types
54/// that we do not have a complete definition for yet. When the
55/// [`SchemaSystem`] finishes loading these types, the type definitions in
56/// [`PendingTypes`] can be promoted the [`TypeStore`].
57///
58/// Deferred type definition:
59/// A deferred type definition is added to [`PendingTypes`] whenever encountered type reference whose definition
60/// is outside the scope of current type definition that is being resolved.
61/// e.g.
62/// type:: {
63///     name: foo,
64///     type: bar,
65/// }
66/// type:: {
67///     name: bar,
68///     type: int
69/// }
70///
71/// For above example, `bar` will be saved as deferred type definition until we resolve the definition of `bar`.
72/// When the [`SchemaSystem`] finishes loading the type `foo`, the type definitions in
73/// [`PendingTypes`] including deferred type definitions will be promoted to the [`TypeStore`].
74/// Once we resolve type definition for `bar` it will be updated in the [`TypeStore`].
75#[derive(Debug, Clone, Default)]
76pub struct PendingTypes {
77    builtin_type_ids_by_name: HashMap<String, TypeId>,
78    ids_by_name: HashMap<String, TypeId>,
79    parent: Option<(String, TypeId)>,
80    types_by_id: Vec<Option<TypeDefinitionKind>>, // a None in this vector represents a not-yet-resolved type
81}
82
83impl PendingTypes {
84    /// Adds all the types from PendingTypes into given [`TypeStore`] including adding all the imported types into imports of [`TypeStore`].
85    /// It also clears [`PendingTypes`] types for loading next set of types.
86    /// This method is used after a schema named type/root type is loaded entirely into [`PendingTypes`]
87    /// * `type_store` - The TypeStore which will be updated with the types within this PendingType
88    /// * `load_isl_import` - If this argument is Some(isl_import), then we are not within an import process of schema.
89    ///   Based on given enum variant isl_import we will add the types to type_store.
90    ///   Otherwise we will add all the types from this PendingTypes to TypeStore.
91    /// * `isl_type_names` - The isl type names defined within the schema. This will be used to determine
92    ///   if a type definition actually exists within the schema. If a type definition from this list
93    ///   exists in [`PendingTypes`] it would have been added as a deferred type definition.
94    ///   This deferred type will be loaded into [`TypeStore`] as it is and will be replaced with a type definition
95    ///   once it is resolved.
96    ///
97    /// Returns true, if this update is not for an isl import type or it is for an isl import type but it is added to the type_store
98    /// Otherwise, returns false if this update is for an isl import type and it is not yet added to the type_store.
99    pub fn update_type_store(
100        &mut self,
101        type_store: &mut TypeStore,
102        load_isl_import: Option<&IslImport>,
103        isl_type_names: &HashSet<&str>,
104    ) -> IonSchemaResult<bool> {
105        // if load_isl_import is not None, then match the enum variant and update type store with import types accordingly
106        if let Some(import) = load_isl_import {
107            match import {
108                IslImport::Schema(_) => {
109                    self.update_type_store_with_all_isl_imported_types(
110                        None,
111                        type_store,
112                        isl_type_names,
113                    )?;
114                }
115                IslImport::Type(isl_import) => {
116                    // if import has a specified type to import then only add that type
117                    if let Some(named_type_def) =
118                        self.get_type_by_name_for_import(isl_import.type_name(), type_store)
119                    {
120                        type_store
121                            .add_isl_imported_type(isl_import.alias().as_ref(), named_type_def?);
122                        self.update_type_store_with_all_isl_imported_types(
123                            Some(isl_import.type_name()),
124                            type_store,
125                            isl_type_names,
126                        )?;
127                    } else {
128                        // if the named_type_def appears as None then it means we haven't reached
129                        // the named type to be imported yet hence we return with false pointing
130                        // we haven't yet resolved this import.
131                        return Ok(false);
132                    }
133                }
134                IslImport::TypeAlias(isl_import) => {
135                    // if import has a specified type with an alias then renamed that type to the given alias and add it
136                    if type_store
137                        .imported_type_ids_by_name
138                        .contains_key(isl_import.alias().as_ref().unwrap())
139                    {
140                        // if the type_store already has the import in it then return true (i.e. TypeAlias has already been imported)
141                        return Ok(true);
142                    } else if let Some(named_type_def) =
143                        self.get_type_by_name_for_import(isl_import.type_name(), type_store)
144                    {
145                        let aliased_type_def = named_type_def?
146                            .with_name(isl_import.alias().as_ref().unwrap().to_owned());
147                        type_store
148                            .add_isl_imported_type(isl_import.alias().as_ref(), aliased_type_def);
149                        self.update_type_store_with_all_isl_imported_types(
150                            Some(isl_import.type_name()),
151                            type_store,
152                            isl_type_names,
153                        )?;
154                    } else {
155                        // if the named_type_def appears as None then it means we haven't reached
156                        // the named type to be imported yet hence we return with false pointing
157                        // we haven't yet resolved this import.
158                        return Ok(false);
159                    }
160                }
161            }
162        } else {
163            // if load_isl_import is None i.e. it is the root schema, then update type_store with all the types inside this PendingTypes
164            self.update_type_store_with_all_types(type_store, isl_type_names)?;
165        }
166        self.types_by_id.clear();
167        self.ids_by_name.clear();
168        Ok(true)
169    }
170
171    // helper method get named type for given import_type_name from this PendingTypes
172    // this return type will be used by update_type_store method to then update type_store with this named type as import
173    fn get_type_by_name_for_import(
174        &self,
175        import_type_name: &str,
176        type_store: &mut TypeStore,
177    ) -> Option<IonSchemaResult<TypeDefinitionImpl>> {
178        match self.ids_by_name.get(import_type_name) {
179            Some(id) => self.types_by_id[*id]
180                .to_owned()
181                .map(|type_def| match type_def {
182                    TypeDefinitionKind::Named(named_type_def) => Ok(named_type_def),
183                    TypeDefinitionKind::Anonymous(_) => {
184                        unreachable!(
185                            "The TypeDefinition for the imported type '{}' was Anonymous.",
186                            import_type_name
187                        )
188                    }
189                    TypeDefinitionKind::BuiltIn(_) => {
190                        unreachable!(
191                            "The TypeDefinition for the imported type '{}' was a builtin type.",
192                            import_type_name
193                        )
194                    }
195                }),
196            None => {
197                match type_store.get_defined_type_id_or_imported_type_id_by_name(import_type_name) {
198                    Some(id) => match type_store.types_by_id[*id].to_owned() {
199                        TypeDefinitionKind::Named(named_type_def) => Some(Ok(named_type_def)),
200                        TypeDefinitionKind::Anonymous(_) => {
201                            unreachable!(
202                                "The TypeDefinition for the imported type '{}' was Anonymous.",
203                                import_type_name
204                            )
205                        }
206                        TypeDefinitionKind::BuiltIn(_) => {
207                            unreachable!(
208                                "The TypeDefinition for the imported type '{}' was a builtin type.",
209                                import_type_name
210                            )
211                        }
212                    },
213                    None => None,
214                }
215            }
216        }
217    }
218
219    // helper method to update type store with all the types from this PendingTypes
220    fn update_type_store_with_all_types(
221        &self,
222        type_store: &mut TypeStore,
223        isl_type_names: &HashSet<&str>,
224    ) -> IonSchemaResult<()> {
225        for optional_type in &self.types_by_id {
226            // return an error if any of the type in types_by_id vector is None/Unresolved
227            let type_def = optional_type.to_owned().ok_or_else(|| {
228                unresolvable_schema_error_raw("Unable to load schema due to unresolvable type")
229            })?;
230
231            match type_def {
232                TypeDefinitionKind::Named(named_type_def) => {
233                    // check if the type definitions that are not yet resolved actually exists within the schema
234                    // we can use the isl_type_names to make sure if they exists, otherwise return error.
235                    if named_type_def.is_deferred_type_def()
236                        && !isl_type_names
237                            .contains(named_type_def.name().as_ref().unwrap().as_str())
238                    {
239                        return unresolvable_schema_error(format!(
240                            "Unable to load schema due to unresolvable type {}",
241                            named_type_def.name().as_ref().unwrap()
242                        ));
243                    }
244                    type_store.add_named_type(named_type_def)
245                }
246                TypeDefinitionKind::Anonymous(anonymous_type_def) => {
247                    type_store.add_anonymous_type(anonymous_type_def)
248                }
249                TypeDefinitionKind::BuiltIn(builtin_type) => {
250                    type_store.add_builtin_type(&builtin_type)
251                }
252            };
253        }
254        Ok(())
255    }
256
257    // helper method to update type store with all the types from this PendingTypes
258    // import_type_name: this argument represents whether the import type is SchemaImport or a TypeImport (includes both TypeImport and TypeAliasImport)
259    //                   None - represents its a schema import which imports all types into imported_type_ids_by_name section of type_store
260    //                   Some(_) - represents a type import which import all types into types_by_id of type_store,
261    //                             except specified import type as it will be already loaded by parent method that uses this helper method
262    fn update_type_store_with_all_isl_imported_types(
263        &self,
264        isl_imported_type_name: Option<&str>,
265        type_store: &mut TypeStore,
266        isl_type_names: &HashSet<&str>,
267    ) -> IonSchemaResult<()> {
268        for optional_type in &self.types_by_id {
269            // return an error if any of the type in types_by_id vector is None/Unresolved
270            let type_def = optional_type.to_owned().ok_or_else(|| {
271                unresolvable_schema_error_raw("Unable to load schema due to unresolvable type")
272            })?;
273
274            match type_def.to_owned() {
275                TypeDefinitionKind::Named(named_type_def) => {
276                    match isl_imported_type_name {
277                        None => {
278                            // check if the type definitions that are not yet resolved actually exists within the schema
279                            // we can use the isl_type_names to make sure if they exists, otherwise return error.
280                            if named_type_def.is_deferred_type_def()
281                                && !isl_type_names
282                                    .contains(named_type_def.name().as_ref().unwrap().as_str())
283                            {
284                                return unresolvable_schema_error(format!(
285                                    "Unable to load schema due to unresolvable type {}",
286                                    named_type_def.name().as_ref().unwrap()
287                                ));
288                            }
289                            // imports all types into imported_type_ids_by_name section of type_store
290                            type_store.add_isl_imported_type(None, named_type_def);
291                        }
292                        Some(import_type_name) => {
293                            // check if the type definitions that are not yet resolved actually exists within the schema
294                            // we can use the isl_type_names to make sure if they exists, otherwise return error.
295                            if named_type_def.is_deferred_type_def()
296                                && !isl_type_names
297                                    .contains(named_type_def.name().as_ref().unwrap().as_str())
298                            {
299                                return unresolvable_schema_error(format!(
300                                    "Unable to load schema due to unresolvable type {}",
301                                    named_type_def.name().as_ref().unwrap()
302                                ));
303                            }
304                            // skip the specified import type as it will be already loaded by parent method that uses this helper method
305                            if named_type_def.name().as_ref().unwrap().eq(import_type_name) {
306                                continue;
307                            }
308                            // import all types into types_by_id of type_store which will help resolving the given import type
309                            type_store
310                                .types_by_id
311                                .push(TypeDefinitionKind::Named(named_type_def));
312                        }
313                    }
314                }
315                TypeDefinitionKind::Anonymous(anonymous_type_def) => {
316                    type_store.add_anonymous_type(anonymous_type_def);
317                }
318                TypeDefinitionKind::BuiltIn(builtin_type) => {
319                    type_store.add_builtin_type(&builtin_type);
320                }
321            };
322        }
323        Ok(())
324    }
325
326    /// Returns total number of types stored in the [`TypeStore`]
327    pub(crate) fn get_total_types(&self, type_store: &mut TypeStore) -> usize {
328        self.types_by_id.len() + type_store.types_by_id.len()
329    }
330
331    /// Provides the [`TypeId`] associated with given name if it exists in the [`TypeStore`] or [`PendingTypes`]  
332    /// Otherwise returns None
333    pub(crate) fn get_type_id_by_name(
334        &self,
335        name: &str,
336        type_store: &mut TypeStore,
337    ) -> Option<TypeId> {
338        match self.ids_by_name.get(name) {
339            Some(id) => Some(*id + type_store.types_by_id.len()),
340            None => type_store
341                .get_defined_type_id_or_imported_type_id_by_name(name)
342                .copied(),
343        }
344    }
345
346    /// Updates the unresolved named type that was added as None while loading types in a schema
347    /// with a resolved type definition
348    pub(crate) fn update_named_type(
349        &mut self,
350        type_id: TypeId,
351        name: &str,
352        type_def: TypeDefinitionImpl,
353        type_store: &mut TypeStore,
354    ) -> TypeId {
355        if let Some(exists) = self.ids_by_name.get(name) {
356            return exists.to_owned() + type_store.types_by_id.len();
357        }
358
359        if let Some(exists) = type_store.imported_type_ids_by_name.get(name) {
360            return exists.to_owned();
361        }
362
363        match type_store.update_deferred_type_def(type_def.to_owned(), name) {
364            None => {
365                let type_id = type_id - type_store.types_by_id.len();
366                self.ids_by_name.insert(name.to_owned(), type_id);
367                self.types_by_id[type_id] = Some(TypeDefinitionKind::Named(type_def));
368                type_id + type_store.types_by_id.len()
369            }
370            Some(exists) => exists,
371        }
372    }
373
374    /// Updates the unresolved anonymous type that was added as None while loading types in a schema
375    /// with a resolved type definition
376    pub(crate) fn update_anonymous_type(
377        &mut self,
378        type_id: TypeId,
379        type_def: TypeDefinitionImpl,
380        type_store: &mut TypeStore,
381    ) -> TypeId {
382        self.types_by_id[type_id - type_store.types_by_id.len()] =
383            Some(TypeDefinitionKind::Anonymous(type_def));
384        type_id
385    }
386
387    /// Adds parent information storing the name and possible TypeId of the parent
388    pub(crate) fn add_parent(&mut self, name: String, type_store: &mut TypeStore) {
389        // Parent information is used when resolving a self referencing type
390        // while we resolve a type using the PendingTypes (a temporary type store used while we resolve a type definition)
391        // the type id for any type definition should be the PendingType's types_by_id length in  + TypeStore's types_by_id length
392        // This gives a correct type id when all the types within PendingTypes are shifted to TypeStore
393        self.parent = Some((name, self.types_by_id.len() + type_store.types_by_id.len()))
394    }
395
396    /// Provides parent information: (parent name, type id)
397    pub(crate) fn get_parent(&self) -> &Option<(String, TypeId)> {
398        &self.parent
399    }
400
401    /// Clears parent information once that tree of types is traversed
402    pub(crate) fn clear_parent(&mut self) {
403        self.parent = None
404    }
405
406    /// Adds the unresolved type as None before it gets resolved and gets the associated [`TypeId`]
407    pub(crate) fn add_type(
408        &mut self,
409        type_store: &mut TypeStore,
410        type_name: Option<String>,
411    ) -> TypeId {
412        if let Some(name) = type_name {
413            if let Some(exists) = self.ids_by_name.get(&name) {
414                return exists.to_owned() + type_store.types_by_id.len();
415            }
416            if let Some(exists) = type_store.get_defined_type_id_or_imported_type_id_by_name(&name)
417            {
418                return exists.to_owned();
419            }
420        }
421        let type_id = self.types_by_id.len();
422        self.types_by_id.push(None);
423        type_id + type_store.types_by_id.len()
424    }
425
426    /// Adds the unresolved type as None before it gets resolved and gets the associated [`TypeId`]
427    pub(crate) fn add_deferred_type_with_name(
428        &mut self,
429        alias: &str,
430        type_store: &mut TypeStore,
431    ) -> TypeId {
432        let type_id = self.types_by_id.len();
433        self.ids_by_name.insert(alias.to_owned(), type_id);
434        self.types_by_id.push(Some(TypeDefinitionKind::Named(
435            TypeDefinitionImpl::new_deferred_type_def(alias.to_owned()),
436        )));
437        type_id + type_store.types_by_id.len()
438    }
439}
440
441/// Represents an array of BuiltIn derived ISL types
442/// for more information: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#type-system
443static DERIVED_ISL_TYPES: [&str; 9] = [
444    "type::{ name: lob, one_of: [ blob, clob ] }",
445    "type::{ name: number, one_of: [ decimal, float, int ] }",
446    "type::{ name: text, one_of: [ string, symbol ] }",
447    "type::{ name: $lob, one_of: [ $blob, $clob ] }",
448    "type::{ name: $number, one_of: [ $decimal, $float, $int ] }",
449    "type::{ name: $text, one_of: [ $string, $symbol ] }",
450    "type::{ name: $any, one_of: [ $blob, $bool, $clob, $decimal,
451                                    $float, $int, $string, $symbol, $timestamp,
452                                    $list, $sexp, $struct, $null, document ] }",
453    "type::{ name: nothing, not: $any }",
454    "type::{ name: any, one_of: [ blob, bool, clob, decimal,
455                                    float, int, string, symbol, timestamp,
456                                    list, sexp, struct, document ] }",
457];
458
459pub type TypeId = usize;
460
461/// Defines a cache that can be used to store resolved type definitions of a [`Schema`]
462#[derive(Debug, Clone)]
463pub struct TypeStore {
464    builtin_type_ids_by_name: HashMap<String, TypeId>, // stores all the builtin types used within this schema
465    imported_type_ids_by_name: HashMap<String, TypeId>, // stores all the imported types of a schema
466    ids_by_name: HashMap<String, TypeId>, // stores named types defined within the schema
467    types_by_id: Vec<TypeDefinitionKind>,
468}
469
470impl Default for TypeStore {
471    fn default() -> Self {
472        let mut type_store = Self {
473            builtin_type_ids_by_name: HashMap::new(),
474            imported_type_ids_by_name: HashMap::new(),
475            ids_by_name: HashMap::new(),
476            types_by_id: Vec::new(),
477        };
478        type_store
479            .preload()
480            .expect("The type store didn't preload with built-in types correctly");
481        type_store
482    }
483}
484
485impl TypeStore {
486    /// Preloads all [builtin isl types] into the TypeStore
487    /// [builtin isl types]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#type-system
488    /// TODO: add document builtin type
489    pub(crate) fn preload(&mut self) -> IonSchemaResult<()> {
490        let isl_version = IslVersion::V1_0;
491        // add all ion types to the type store
492        // TODO: this array can be turned into an iterator implementation in ion-rust for IonType
493        use IonSchemaElementType::*;
494        let built_in_atomic_types: [IonSchemaElementType; 12] = [
495            Int, Float, Decimal, Timestamp, String, Symbol, Bool, Blob, Clob, SExp, List, Struct,
496        ];
497        // add all the atomic ion types that doesn't allow nulls [type_ids: 0 - 11]
498        for atomic_type in built_in_atomic_types {
499            self.add_builtin_type(&BuiltInTypeDefinition::Atomic(
500                atomic_type.to_owned(),
501                Nullability::NotNullable,
502            ));
503        }
504
505        // add all the atomic ion types that allows nulls [type_ids: 12 - 23]
506        for atomic_type in built_in_atomic_types {
507            self.add_builtin_type(&BuiltInTypeDefinition::Atomic(
508                atomic_type.to_owned(),
509                Nullability::Nullable,
510            ));
511        }
512
513        // add $null to the built-in types [type_id: 24]
514        self.add_builtin_type(&BuiltInTypeDefinition::Atomic(Null, Nullability::Nullable));
515
516        // add document type
517        self.add_builtin_type(&BuiltInTypeDefinition::Atomic(
518            Document,
519            Nullability::NotNullable,
520        ));
521
522        // get the derived built in types map and related text value for given type_name [type_ids: 25 - 33]
523        let pending_types = &mut PendingTypes::default();
524        for text in DERIVED_ISL_TYPES {
525            let isl_type = IslType::from_owned_element(
526                isl_version,
527                &Element::read_one(text.as_bytes()).expect("parsing failed unexpectedly"),
528                &mut vec![],
529            )
530            .unwrap();
531            let type_def = BuiltInTypeDefinition::parse_from_isl_type(
532                isl_version,
533                &isl_type,
534                self,
535                pending_types,
536            )?;
537            self.add_builtin_type(&type_def);
538        }
539        Ok(())
540    }
541
542    /// Returns [`TypeId`]s stored in the [`TypeStore`] to be used by [`SchemaTypeIterator`]
543    pub(crate) fn get_types(&self) -> Vec<TypeId> {
544        self.ids_by_name.values().cloned().collect()
545    }
546
547    /// Returns import [`TypeId`]s stored in the [`TypeStore`] to be used by [`SchemaTypeIterator`]
548    pub(crate) fn get_imports(&self) -> Vec<TypeId> {
549        self.imported_type_ids_by_name.values().cloned().collect()
550    }
551
552    /// Provides the [`TypeId`] associated with given name if it exists in the [`TypeStore`] as an imported type;
553    /// Otherwise returns None
554    pub(crate) fn get_imported_type_id_by_name(&self, name: &str) -> Option<&TypeId> {
555        self.imported_type_ids_by_name.get(name)
556    }
557
558    /// Provides the [`Type`] associated with given name if it exists in the [`TypeStore`]  
559    /// Otherwise returns None
560    pub(crate) fn get_type_by_name(&self, name: &str) -> Option<&TypeDefinitionKind> {
561        self.ids_by_name
562            .get(name)
563            .and_then(|id| self.types_by_id.get(*id))
564            .or_else(|| {
565                self.imported_type_ids_by_name
566                    .get(name)
567                    .and_then(|id| self.types_by_id.get(*id))
568            })
569    }
570
571    /// Provides the [`TypeId`] associated with given name if it exists in the [`TypeStore`] either as
572    /// a built-in type or a type defined within schema; Otherwise returns None
573    pub(crate) fn get_built_in_type_id_or_defined_type_id_by_name(
574        &self,
575        name: &str,
576    ) -> Option<&TypeId> {
577        self.ids_by_name
578            .get(name)
579            .or_else(|| self.builtin_type_ids_by_name.get(name))
580    }
581
582    /// Provides the [`TypeId`] associated with given name if it exists in the [`TypeStore`] as a type
583    /// defined within schema (This doesn't include built-in types); Otherwise returns None
584    pub(crate) fn get_defined_type_id_or_imported_type_id_by_name(
585        &self,
586        name: &str,
587    ) -> Option<&TypeId> {
588        self.ids_by_name
589            .get(name)
590            .or_else(|| self.imported_type_ids_by_name.get(name))
591    }
592
593    /// Provides the [`TypeId`] associated with given name if it exists in the [`TypeStore`] as a type
594    /// defined within schema (This includes built-in types); Otherwise returns None
595    pub(crate) fn get_type_id_by_name(&self, name: &str) -> Option<&TypeId> {
596        self.ids_by_name
597            .get(name)
598            .or_else(|| self.imported_type_ids_by_name.get(name))
599            .or_else(|| self.builtin_type_ids_by_name.get(name))
600    }
601
602    /// Provides the [`Type`] associated with given name if it exists in the [`TypeStore`] as a type
603    /// defined within schema (This doesn't include built-in types and imported types); Otherwise returns None
604    pub(crate) fn get_type_def_by_name(&self, name: &str) -> Option<&TypeDefinitionKind> {
605        self.ids_by_name
606            .get(name)
607            .and_then(|id| self.types_by_id.get(*id))
608    }
609
610    /// Provides the [`TypeId`] associated with given type name if it exists in the [`TypeStore`]  
611    /// Otherwise returns None
612    pub(crate) fn get_builtin_type_id(&self, type_name: &str) -> Option<TypeId> {
613        self.builtin_type_ids_by_name
614            .get(type_name)
615            .map(|t| t.to_owned())
616    }
617
618    /// Provides the [`Type`] associated with given [`TypeId`] if it exists in the [`TypeStore`]  
619    /// Otherwise returns None
620    pub(crate) fn get_type_by_id(&self, id: TypeId) -> Option<&TypeDefinitionKind> {
621        self.types_by_id.get(id)
622    }
623
624    /// Adds the [`NamedTypeDefinition`] and the associated name in the [`TypeStore`] and returns the [`TypeId`] for it
625    /// If the name already exists in the [`TypeStore`] it returns the associated [`TypeId`]
626    pub(crate) fn add_named_type(&mut self, type_def: TypeDefinitionImpl) -> TypeId {
627        let name = type_def.name().as_ref().unwrap();
628        if let Some(exists) = self.ids_by_name.get(name) {
629            return exists.to_owned();
630        }
631        let type_id = self.types_by_id.len();
632        self.ids_by_name.insert(name.to_owned(), type_id);
633        self.types_by_id.push(TypeDefinitionKind::Named(type_def));
634        type_id
635    }
636
637    /// Updates the deferred [`NamedTypeDefinition`] for given type definition name
638    /// If the name already exists in the [`TypeStore`] it returns the associated [`TypeId`]
639    /// otherwise return [`None`]
640    pub(crate) fn update_deferred_type_def(
641        &mut self,
642        type_def: TypeDefinitionImpl,
643        name: &str,
644    ) -> Option<TypeId> {
645        if let Some(exists) = self.ids_by_name.get(name) {
646            if let Some(TypeDefinitionKind::Named(existing_type_def)) = self.get_type_by_id(*exists)
647            {
648                // if existing_type_def is a deferred type def then this is the definition for it,
649                // resolve the deferred type definition here by replacing with given type definition
650                if existing_type_def.is_deferred_type_def() {
651                    self.types_by_id[*exists] = TypeDefinitionKind::Named(type_def);
652                }
653            }
654            return Some(*exists);
655        }
656        None
657    }
658
659    /// Adds the [BuiltInTypeDefinition] in the [TypeStore] and returns the [TypeId] for it
660    /// If the name already exists in the [TypeStore] it returns the associated [TypeId]
661    pub(crate) fn add_builtin_type(
662        &mut self,
663        builtin_type_definition: &BuiltInTypeDefinition,
664    ) -> TypeId {
665        let builtin_type_name = match builtin_type_definition {
666            BuiltInTypeDefinition::Atomic(ion_type, is_nullable) => match is_nullable {
667                Nullability::Nullable => format!("${ion_type}"),
668                Nullability::NotNullable => format!("{ion_type}"),
669            },
670            BuiltInTypeDefinition::Derived(other_type) => other_type.name().to_owned().unwrap(),
671        };
672
673        if let Some(exists) = self.builtin_type_ids_by_name.get(&builtin_type_name) {
674            return exists.to_owned();
675        }
676        let type_id = self.types_by_id.len();
677        self.builtin_type_ids_by_name
678            .insert(builtin_type_name, type_id);
679        self.types_by_id.push(TypeDefinitionKind::BuiltIn(
680            builtin_type_definition.to_owned(),
681        ));
682        type_id
683    }
684
685    /// Adds the [`NamedTypeDefinition`] and the associated name as the imports of [`TypeStore`]
686    ///  and returns the [`TypeId`] for it. If the name already exists in the [`TypeStore`] it returns the associated [`TypeId`]
687    pub(crate) fn add_isl_imported_type(
688        &mut self,
689        alias: Option<&String>,
690        type_def: TypeDefinitionImpl,
691    ) -> TypeId {
692        let name = match alias {
693            None => type_def.name().as_ref().unwrap(),
694            Some(name) => name,
695        };
696
697        if let Some(exists) = self.imported_type_ids_by_name.get(name) {
698            return exists.to_owned();
699        }
700        let type_id = self.types_by_id.len();
701        self.imported_type_ids_by_name
702            .insert(name.to_owned(), type_id);
703        self.types_by_id.push(TypeDefinitionKind::Named(type_def));
704        type_id
705    }
706
707    /// Adds the [`Type`] in the [`TypeStore`] and returns the [`TypeId`] for it
708    pub(crate) fn add_anonymous_type(&mut self, type_def: TypeDefinitionImpl) -> TypeId {
709        let type_id = self.types_by_id.len();
710        self.types_by_id
711            .push(TypeDefinitionKind::Anonymous(type_def));
712        type_id
713    }
714}
715
716/// Provides functions to load [`Schema`] with type definitions using authorities for [`SchemaSystem`]
717pub struct Resolver {
718    authorities: Vec<Box<dyn DocumentAuthority>>,
719    resolved_schema_cache: HashMap<String, Arc<Schema>>,
720}
721
722impl Resolver {
723    pub fn new(authorities: Vec<Box<dyn DocumentAuthority>>) -> Self {
724        Self {
725            authorities,
726            resolved_schema_cache: HashMap::new(),
727        }
728    }
729
730    pub fn schema_from_isl_types<A: AsRef<str>, B: Into<Vec<IslType>>>(
731        &self,
732        isl_version: IslVersion,
733        id: A,
734        isl_types: B,
735    ) -> IonSchemaResult<Schema> {
736        let isl_types = isl_types.into();
737        // create type_store and pending types which will be used to create type definition
738        let mut type_store = TypeStore::default();
739        let pending_types = &mut PendingTypes::default();
740
741        // get all isl type names from given isl types
742        // this will be used to resolve type references which might not have yet resolved while loading a type definition
743        let isl_type_names: HashSet<&str> =
744            HashSet::from_iter(isl_types.iter().filter_map(|t| t.name()));
745
746        for isl_type in &isl_types {
747            // convert [IslType] into [TypeDefinitionKind]
748            match &isl_type.name() {
749                Some(isl_type_name) => {
750                    // verify that the ISL type doesn't contain constraints from another ISL version
751                    let has_other_isl_constraints = isl_type
752                        .constraints()
753                        .iter()
754                        .any(|c| c.version != isl_version);
755
756                    if has_other_isl_constraints {
757                        return invalid_schema_error(format!("ISL type: {isl_type_name} contains constraints from another ISL version. Only use {isl_version} constraints for this method."));
758                    }
759
760                    TypeDefinitionImpl::parse_from_isl_type_and_update_pending_types(
761                        isl_version,
762                        isl_type,
763                        &mut type_store,
764                        pending_types,
765                    )?
766                }
767                None => {
768                    // top level schema types can not be anonymous
769                    return invalid_schema_error("Top level types must be named type definitions");
770                }
771            };
772        }
773
774        // add all types from pending_types to type_store
775        pending_types.update_type_store(&mut type_store, None, &isl_type_names)?;
776        Ok(Schema::new(id, Arc::new(type_store)))
777    }
778
779    /// Converts given owned elements into ISL v2.0 representation
780    pub fn isl_schema_from_elements<I: Iterator<Item = Element>>(
781        &mut self,
782        elements: I,
783        id: &str,
784    ) -> IonSchemaResult<IslSchema> {
785        // properties that will be stored in the ISL representation
786        let mut isl_imports: Vec<IslImport> = vec![];
787        let mut isl_types: Vec<IslType> = vec![];
788        let mut isl_inline_imports: Vec<IslImportType> = vec![];
789        let mut open_content = vec![];
790        let mut isl_user_reserved_fields = UserReservedFields::default();
791        let mut isl_version = IslVersion::V1_0;
792
793        let mut found_header = false;
794        let mut found_footer = false;
795        let mut found_type_definition = false;
796        let mut found_isl_version_marker = false;
797
798        for value in elements {
799            let annotations: &Annotations = value.annotations();
800
801            // load header for schema
802            if !found_isl_version_marker
803                && value.ion_type() == IonType::Symbol
804                && is_isl_version_marker(value.as_text().unwrap())
805            {
806                // This implementation supports Ion Schema 1.0 and Ion Schema 2.0
807                isl_version = match value.as_text().unwrap() {
808                    "$ion_schema_1_0" => IslVersion::V1_0,
809                    "$ion_schema_2_0" => IslVersion::V2_0,
810                    _ => {
811                        return invalid_schema_error(format!(
812                            "Unsupported Ion Schema Language version: {value}"
813                        ));
814                    }
815                };
816                found_isl_version_marker = true;
817            } else if annotations.contains("schema_header") {
818                if isl_version == IslVersion::V2_0 {
819                    if found_type_definition {
820                        return invalid_schema_error(
821                            "The schema header must come before top level type definitions",
822                        );
823                    }
824
825                    if found_header {
826                        return invalid_schema_error(
827                            "Schema must only contain a single schema header",
828                        );
829                    }
830
831                    if annotations.len() > 1 {
832                        return invalid_schema_error(
833                            "schema header must not have any other annotations then `schema_header`",
834                        );
835                    }
836                }
837
838                found_header = true;
839
840                // if we didn't find an isl version marker before finding a schema header
841                // then isl version will be defaulted to be ISL 1.0
842                if !found_isl_version_marker {
843                    found_isl_version_marker = true;
844                }
845
846                let schema_header = try_to!(value.as_struct());
847                if let Some(imports) = schema_header.get("imports").and_then(|it| it.as_sequence())
848                {
849                    for import in imports.elements() {
850                        let isl_import = IslImport::from_ion_element(import)?;
851                        isl_imports.push(isl_import);
852                    }
853                }
854                if isl_version == IslVersion::V2_0 {
855                    if let Some(user_reserved_fields_element) =
856                        schema_header.get("user_reserved_fields")
857                    {
858                        if !user_reserved_fields_element.annotations().is_empty() {
859                            return invalid_schema_error(
860                                "User reserved field must be an unannotated struct",
861                            )?;
862                        }
863                        let user_reserved_fields_struct = user_reserved_fields_element
864                            .as_struct()
865                            .ok_or(invalid_schema_error_raw(
866                                "User reserved field must be a non-null struct",
867                            ))?;
868
869                        isl_user_reserved_fields =
870                            UserReservedFields::from_ion_elements(user_reserved_fields_struct)?;
871                    }
872                    isl_user_reserved_fields.validate_field_names_in_header(schema_header)?;
873                }
874            }
875            // load types for schema
876            else if annotations.contains("type") {
877                found_type_definition = true;
878
879                if isl_version == IslVersion::V2_0 && annotations.len() > 1 {
880                    return invalid_schema_error(
881                        "Top level types definitions must not have any other annotations then `type`",
882                    );
883                }
884                // if we didn't find an isl version marker before finding a type definition
885                // then isl version will be defaulted to be ISL 1.0
886                if !found_isl_version_marker {
887                    found_isl_version_marker = true;
888                }
889
890                // convert Element to IslType
891                let isl_type: IslType =
892                    IslType::from_owned_element(isl_version, &value, &mut isl_inline_imports)?;
893                if isl_type.name().is_none() {
894                    // if a top level type definition doesn't contain `name` field return an error
895                    return invalid_schema_error(
896                        "Top level types must contain field `name` in their definition",
897                    );
898                }
899
900                if isl_version == IslVersion::V2_0 {
901                    isl_user_reserved_fields.validate_field_names_in_type(&isl_type)?;
902                }
903
904                // top level named type definition can not contain `occurs` field as per ISL specification
905                if value.as_struct().unwrap().get("occurs").is_some() {
906                    return invalid_schema_error(
907                        "Top level types must not contain `occurs` field in their definition",
908                    );
909                }
910
911                isl_types.push(isl_type);
912            }
913            // load footer for schema
914            else if annotations.contains("schema_footer") {
915                found_footer = true;
916                if isl_version == IslVersion::V2_0 {
917                    if annotations.len() > 1 {
918                        return invalid_schema_error(
919                            "schema footer must not have any other annotations then `schema_footer`",
920                        );
921                    }
922                    let schema_footer = try_to!(value.as_struct());
923                    isl_user_reserved_fields.validate_field_names_in_footer(schema_footer)?;
924                }
925            } else {
926                // open content
927                if value.ion_type() == IonType::Symbol
928                    && !value.is_null()
929                    && is_isl_version_marker(value.as_text().unwrap())
930                {
931                    return invalid_schema_error(
932                        "top level open content can not be an Ion Schema version marker",
933                    );
934                }
935
936                if isl_version == IslVersion::V2_0
937                    && value
938                        .annotations()
939                        .iter()
940                        .any(|a| is_reserved_word(a.text().unwrap()))
941                {
942                    return invalid_schema_error(
943                        "top level open content may not be annotated with any reserved keyword",
944                    );
945                }
946
947                open_content.push(value);
948                continue;
949            }
950        }
951
952        if found_footer ^ found_header {
953            return invalid_schema_error("For any schema while a header and footer are both optional, a footer is required if a header is present (and vice-versa).");
954        }
955
956        match isl_version {
957            IslVersion::V1_0 => Ok(IslSchema::schema_v_1_0(
958                id,
959                isl_imports,
960                isl_types,
961                isl_inline_imports,
962                open_content,
963            )),
964            IslVersion::V2_0 => Ok(IslSchema::schema_v_2_0(
965                id,
966                isl_user_reserved_fields,
967                isl_imports,
968                isl_types,
969                isl_inline_imports,
970                open_content,
971            )),
972        }
973    }
974
975    /// Converts given ISL representation into a [`Schema`] based on given ISL version
976    pub fn schema_from_isl_schema(
977        &mut self,
978        isl_version: IslVersion,
979        isl: IslSchema,
980        type_store: &mut TypeStore,
981        load_isl_import: Option<&IslImport>,
982    ) -> IonSchemaResult<Arc<Schema>> {
983        // This is used while resolving an import, it is initialized as `false` to indicate that
984        // the type to be imported is not yet added to the type_store.
985        // This will be changed to `true` as soon as the type to be imported is resolved and is added to the type_store
986        let mut added_imported_type_to_type_store = false;
987
988        if isl_version != isl.version() {
989            return invalid_schema_error(format!(
990                "Expected {isl_version} schema but found {}",
991                isl.version()
992            ));
993        }
994
995        let isl_types = isl.types();
996
997        // Resolve all inline import types if there are any
998        // this will help resolve all inline imports before they are used as a reference to another type
999        for isl_inline_imported_type in isl.inline_imported_types() {
1000            let import_id = isl_inline_imported_type.id();
1001            let inline_imported_type = self.load_schema(
1002                import_id,
1003                type_store,
1004                Some(&IslImport::Type(isl_inline_imported_type.to_owned())),
1005            )?;
1006        }
1007
1008        // Resolve all ISL imports
1009        for isl_import in isl.imports() {
1010            let import_id = isl_import.id();
1011            let imported_schema = self.load_schema(import_id, type_store, Some(isl_import))?;
1012        }
1013
1014        // get all isl type names that are defined within the schema
1015        // this will be used to resolve type references which might not have yet resolved while loading a type definition
1016        let isl_type_names: HashSet<&str> =
1017            HashSet::from_iter(isl.types().filter_map(|t| t.name()));
1018
1019        // Resolve all ISL types and constraints
1020        for isl_type in isl_types {
1021            let pending_types = &mut PendingTypes::default();
1022
1023            if let Some(isl_type_name) = &isl_type.name() {
1024                // verify if there are any constraints with ISL 2.0 for this isl_type
1025                let has_other_isl_constraints = isl_type
1026                    .constraints()
1027                    .iter()
1028                    .any(|c| c.version != isl_version);
1029
1030                if has_other_isl_constraints {
1031                    return invalid_schema_error(format!("ISL type: {isl_type_name} contains constraints from other ISL version. Only use {isl_version} constraints for this method."));
1032                }
1033
1034                // convert IslType to TypeDefinitionKind
1035                let type_id: TypeId =
1036                    TypeDefinitionImpl::parse_from_isl_type_and_update_pending_types(
1037                        isl_version,
1038                        isl_type,
1039                        type_store,
1040                        pending_types,
1041                    )?;
1042            }
1043
1044            // add all types from pending types to type_store
1045            added_imported_type_to_type_store =
1046                pending_types.update_type_store(type_store, load_isl_import, &isl_type_names)?;
1047        }
1048
1049        // if currently loading an ISL import (i.e. load_isl_import != None)
1050        // then check if the type to be imported is added to the type_store or not
1051        if load_isl_import.is_some() && !added_imported_type_to_type_store {
1052            unreachable!(
1053                "Unable to load import: {} as the type/types were not added to the type_store correctly",
1054                isl.id()
1055            );
1056        }
1057
1058        let schema = Arc::new(Schema::new(isl.id(), Arc::new(type_store.clone())));
1059
1060        // add schema to schema cache
1061        // if we are loading an import of the schema then we can only add this schema to cache if its a full schema import
1062        // and can not add it to cache if we are loading specific type imports from the schema.
1063        match load_isl_import {
1064            None => {
1065                self.resolved_schema_cache
1066                    .insert(isl.id(), Arc::clone(&schema));
1067            }
1068            Some(IslImport::Schema(_)) => {
1069                self.resolved_schema_cache
1070                    .insert(isl.id(), Arc::clone(&schema));
1071            }
1072            _ => {
1073                // No op for type imports
1074            }
1075        }
1076
1077        Ok(schema)
1078    }
1079
1080    /// Loads a [`Schema`] with resolved [`Type`]s using authorities and type_store
1081    // If we are loading the root schema then this will be set to `None` ( i.e. in the beginning when
1082    // this method is called from the load_schema method of schema_system it is set to `None`)
1083    // Otherwise if we are loading an import of the schema then this will be set to `Some(isl_import)`
1084    // to be loaded (i.e. Inside schema_from_elements while loading imports this will be set to
1085    // `Some(isl_import)`)
1086    fn load_schema<A: AsRef<str>>(
1087        &mut self,
1088        id: A,
1089        type_store: &mut TypeStore,
1090        load_isl_import: Option<&IslImport>,
1091    ) -> IonSchemaResult<Arc<Schema>> {
1092        let id: &str = id.as_ref();
1093
1094        if let Some(schema) = self.resolved_schema_cache.get(id) {
1095            return Ok(Arc::clone(schema));
1096        }
1097
1098        for authority in &self.authorities {
1099            return match authority.elements(id) {
1100                Err(error) => match error {
1101                    IonSchemaError::IoError { source } => match source.kind() {
1102                        ErrorKind::NotFound => continue,
1103                        _ => Err(IonSchemaError::IoError { source }),
1104                    },
1105                    _ => Err(error),
1106                },
1107                Ok(schema_content) => {
1108                    let isl = self.isl_schema_from_elements(schema_content.into_iter(), id)?;
1109                    self.schema_from_isl_schema(isl.version(), isl, type_store, load_isl_import)
1110                }
1111            };
1112        }
1113        unresolvable_schema_error("Unable to load schema: ".to_owned() + id)
1114    }
1115
1116    /// Loads an [`IslSchema`] using authorities and type_store based on ISL version.
1117    // If we are loading the root schema then this will be set to `None` ( i.e. in the beginning when
1118    // this method is called from the load_schema method of schema_system it is set to `None`)
1119    // Otherwise if we are loading an import of the schema then this will be set to `Some(isl_import)`
1120    // to be loaded (i.e. Inside schema_from_elements while loading imports this will be set to
1121    // `Some(isl_import)`)
1122    fn load_isl_schema<A: AsRef<str>>(
1123        &mut self,
1124        id: A,
1125        load_isl_import: Option<&IslImport>,
1126    ) -> IonSchemaResult<IslSchema> {
1127        let id: &str = id.as_ref();
1128
1129        for authority in &self.authorities {
1130            return match authority.elements(id) {
1131                Ok(schema_content) => self.isl_schema_from_elements(schema_content.into_iter(), id),
1132                Err(IonSchemaError::IoError { source: e }) if e.kind() == ErrorKind::NotFound => {
1133                    continue;
1134                }
1135                Err(error) => Err(error),
1136            };
1137        }
1138        unresolvable_schema_error("Unable to load ISL model: ".to_owned() + id)
1139    }
1140}
1141
1142/// Provides functions for instantiating instances of [`Schema`].
1143///
1144/// [`SchemaSystem`] is *[Send and Sync]* i.e. it is safe to send it to another thread and to be shared between threads.
1145/// For cases when one does need thread-safe interior mutability, they can use the explicit locking via [`std::sync::Mutex`] and [`std::sync::RwLock`].
1146///
1147/// [Send and Sync]: https://doc.rust-lang.org/nomicon/send-and-sync.html
1148pub struct SchemaSystem {
1149    resolver: Resolver,
1150}
1151
1152// TODO: make methods public based on the requirements
1153impl SchemaSystem {
1154    pub fn new(authorities: Vec<Box<dyn DocumentAuthority>>) -> Self {
1155        Self {
1156            resolver: Resolver::new(authorities),
1157        }
1158    }
1159
1160    /// Requests each of the provided [`DocumentAuthority`]s, in order, to resolve the requested schema id
1161    /// until one successfully resolves it.
1162    /// If an authority throws an exception, resolution silently proceeds to the next authority.
1163    /// This method returns an `Arc<Schema>` which allows to load this schema once re-use it across threads.
1164    // TODO: Add support for Rc<Schema> by providing a trait implementation of schema and schema cache. This should
1165    //  allow users to choose what variant of schema they want.
1166    pub fn load_schema<A: AsRef<str>>(&mut self, id: A) -> IonSchemaResult<Arc<Schema>> {
1167        self.resolver
1168            .load_schema(id, &mut TypeStore::default(), None)
1169    }
1170
1171    /// Constructs a new schema using provided ISL content.
1172    pub fn new_schema(&mut self, schema_content: &[u8], id: &str) -> IonSchemaResult<Arc<Schema>> {
1173        let elements = Element::read_all(schema_content)?;
1174        let isl = self
1175            .resolver
1176            .isl_schema_from_elements(elements.into_iter(), id)?;
1177        self.resolver
1178            .schema_from_isl_schema(isl.version(), isl, &mut TypeStore::default(), None)
1179    }
1180
1181    /// Requests each of the provided [`DocumentAuthority`]s, in order, to get ISL model for the
1182    /// requested schema id until one successfully resolves it.
1183    /// If an authority throws an exception, resolution silently proceeds to the next authority.
1184    pub fn load_isl_schema<A: AsRef<str>>(&mut self, id: A) -> IonSchemaResult<IslSchema> {
1185        self.resolver.load_isl_schema(id, None)
1186    }
1187
1188    /// Constructs a new ISL model using provided ISL content.
1189    pub fn new_isl_schema(
1190        &mut self,
1191        schema_content: &[u8],
1192        id: &str,
1193    ) -> IonSchemaResult<IslSchema> {
1194        let elements = Element::read_all(schema_content)?;
1195        self.resolver
1196            .isl_schema_from_elements(elements.into_iter(), id)
1197    }
1198
1199    /// Resolves given ISL 1.0 model into a [Schema].
1200    /// If the given ISL model has any ISL 2.0 related types/constraints, resolution returns an error.
1201    /// This method returns an `Arc<Schema>` which allows to load this schema once re-use it across threads.
1202    // TODO: Add support for Rc<Schema> by providing a trait implementation of schema and schema cache. This should
1203    //  allow users to choose what variant of schema they want.
1204    pub fn load_schema_from_isl_schema_v1_0(
1205        &mut self,
1206        isl: IslSchema,
1207    ) -> IonSchemaResult<Arc<Schema>> {
1208        self.resolver
1209            .schema_from_isl_schema(IslVersion::V1_0, isl, &mut TypeStore::default(), None)
1210    }
1211
1212    /// Resolves given ISL 2.0 model into a [Schema].
1213    /// If the given ISL model has any ISL 1.0 related types/constraints, resolution returns an error.
1214    /// This method returns an `Arc<Schema>` which allows to load this schema once re-use it across threads.
1215    // TODO: Add support for Rc<Schema> by providing a trait implementation of schema and schema cache. This should
1216    //  allow users to choose what variant of schema they want.
1217    pub fn load_schema_from_isl_schema_v2_0(
1218        &mut self,
1219        isl: IslSchema,
1220    ) -> IonSchemaResult<Arc<Schema>> {
1221        self.resolver
1222            .schema_from_isl_schema(IslVersion::V2_0, isl, &mut TypeStore::default(), None)
1223    }
1224
1225    /// Returns authorities associated with this [`SchemaSystem`]
1226    fn authorities(&mut self) -> &[Box<dyn DocumentAuthority>] {
1227        &self.resolver.authorities
1228    }
1229
1230    /// Adds the provided authority to the list of [`DocumentAuthority`]s.
1231    fn add_authority(&mut self, authority: Box<dyn DocumentAuthority>) {
1232        self.resolver.authorities.push(authority);
1233    }
1234
1235    /// Replaces the list of [`DocumentAuthority`]s with a list containing only the specified authority.
1236    fn with_authority(&mut self, authority: Box<dyn DocumentAuthority>) {
1237        let authorities: Vec<Box<dyn DocumentAuthority>> = vec![authority];
1238        self.resolver.authorities = authorities;
1239    }
1240
1241    // TODO: Use IntoIterator here instead of a Vec
1242    /// Replaces the list of [`DocumentAuthority`]s with the specified list of [`DocumentAuthority`]s.
1243    fn with_authorities(&mut self, authorities: Vec<Box<dyn DocumentAuthority>>) {
1244        self.resolver.authorities = authorities;
1245    }
1246
1247    /// Creates a schema from given [`IslType`]s using ISL 1.0
1248    /// Note: This method assumes that there are no imported type definitions used for these [`IslType`]s
1249    pub fn schema_from_isl_types_v1_0<A: AsRef<str>, B: Into<Vec<IslType>>>(
1250        &self,
1251        id: A,
1252        isl_types: B,
1253    ) -> IonSchemaResult<Schema> {
1254        self.resolver
1255            .schema_from_isl_types(IslVersion::V1_0, id, isl_types)
1256    }
1257
1258    /// Creates a schema from given [`IslType`]s using ISL 2.0
1259    /// Note: This method assumes that there are no imported type definitions used for these [`IslType`]s
1260    pub fn schema_from_isl_types_v2_0<A: AsRef<str>, B: Into<Vec<IslType>>>(
1261        &self,
1262        id: A,
1263        isl_types: B,
1264    ) -> IonSchemaResult<Schema> {
1265        self.resolver
1266            .schema_from_isl_types(IslVersion::V2_0, id, isl_types)
1267    }
1268}
1269
1270#[cfg(test)]
1271mod schema_system_tests {
1272    use super::*;
1273    use crate::authority::{FileSystemDocumentAuthority, MapDocumentAuthority};
1274    use crate::isl::isl_constraint;
1275    use crate::isl::isl_type;
1276    use crate::isl::isl_type_reference;
1277    use crate::system::IonSchemaError::InvalidSchemaError;
1278    use std::path::Path;
1279
1280    #[test]
1281    fn schema_system_add_authorities_test() {
1282        let mut schema_system = SchemaSystem::new(vec![Box::new(
1283            FileSystemDocumentAuthority::new(Path::new("")),
1284        )]);
1285        schema_system.add_authority(Box::new(FileSystemDocumentAuthority::new(Path::new(
1286            "test",
1287        ))));
1288        let schema_system_authorities = schema_system.authorities();
1289        assert_eq!(2, schema_system_authorities.len());
1290    }
1291
1292    #[test]
1293    fn schema_system_with_authority_test() {
1294        let mut schema_system = SchemaSystem::new(vec![Box::new(
1295            FileSystemDocumentAuthority::new(Path::new("")),
1296        )]);
1297        schema_system.with_authority(Box::new(FileSystemDocumentAuthority::new(Path::new(
1298            "test",
1299        ))));
1300        let schema_system_authorities = schema_system.authorities();
1301        assert_eq!(1, schema_system_authorities.len());
1302    }
1303
1304    #[test]
1305    fn schema_system_with_authorities_test() {
1306        let mut schema_system = SchemaSystem::new(vec![Box::new(
1307            FileSystemDocumentAuthority::new(Path::new("")),
1308        )]);
1309        schema_system.with_authorities(vec![
1310            Box::new(FileSystemDocumentAuthority::new(Path::new("test"))),
1311            Box::new(FileSystemDocumentAuthority::new(Path::new("ion"))),
1312        ]);
1313        let schema_system_authorities = schema_system.authorities();
1314        assert_eq!(2, schema_system_authorities.len());
1315    }
1316
1317    #[test]
1318    fn schema_system_map_authority_with_type_alias_import_test() {
1319        // map with (id, ion content)
1320        let map_authority = [
1321            (
1322                "sample_number.isl",
1323                r#"
1324                    schema_header::{
1325                      imports: [{ id: "sample_decimal.isl", type: my_decimal, as: other_decimal }],
1326                    }
1327                    
1328                    type::{
1329                      name: my_int,
1330                      type: int,
1331                    }
1332                    
1333                    type::{
1334                      name: my_number,
1335                      all_of: [
1336                        my_int,
1337                        other_decimal,
1338                      ],
1339                    }
1340                    
1341                    schema_footer::{
1342                    }
1343                "#,
1344            ),
1345            (
1346                "sample_decimal.isl",
1347                r#"
1348                    schema_header::{
1349                      imports: [],
1350                    }
1351                    
1352                    type::{
1353                      name: my_decimal,
1354                      type: decimal,
1355                    }
1356                    
1357                    type::{
1358                      name: my_string,
1359                      type: string,
1360                    }
1361                    
1362                    schema_footer::{
1363                    }
1364                "#,
1365            ),
1366        ];
1367        let mut schema_system =
1368            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1369        // verify if the schema loads without any errors
1370        let schema = schema_system.load_schema("sample_number.isl");
1371        assert!(schema.is_ok());
1372    }
1373
1374    #[test]
1375    fn schema_system_map_authority_with_type_import_test() {
1376        // map with (id, ion content)
1377        let map_authority = [
1378            (
1379                "sample_number.isl",
1380                r#"
1381                    schema_header::{
1382                      imports: [{ id: "sample_decimal.isl", type: my_decimal }],
1383                    }
1384                    
1385                    type::{
1386                      name: my_int,
1387                      type: int,
1388                    }
1389                    
1390                    type::{
1391                      name: my_number,
1392                      all_of: [
1393                        my_int,
1394                        my_decimal,
1395                      ],
1396                    }
1397                    
1398                    schema_footer::{
1399                    }
1400                "#,
1401            ),
1402            (
1403                "sample_decimal.isl",
1404                r#"
1405                    schema_header::{
1406                      imports: [],
1407                    }
1408                    
1409                    type::{
1410                      name: my_decimal,
1411                      type: decimal,
1412                    }
1413                    
1414                    type::{
1415                      name: my_string,
1416                      type: string,
1417                    }
1418                    
1419                    schema_footer::{
1420                    }
1421                "#,
1422            ),
1423        ];
1424        let mut schema_system =
1425            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1426        // verify if the schema loads without any errors
1427        let schema = schema_system.load_schema("sample_number.isl");
1428        assert!(schema.is_ok());
1429
1430        // Verify that the schema has the imported types and defined types
1431        let isl_imported_type = schema.as_ref().unwrap().get_imported_type("my_decimal");
1432        assert!(isl_imported_type.is_some());
1433        let isl_defined_type = schema
1434            .as_ref()
1435            .unwrap()
1436            .get_built_in_or_defined_type("my_number");
1437        assert!(isl_defined_type.is_some());
1438        let isl_type = schema.as_ref().unwrap().get_type("my_decimal");
1439        assert!(isl_type.is_some());
1440    }
1441
1442    #[test]
1443    fn schema_system_map_authority_with_schema_import_test() {
1444        // map with (id, ion content)
1445        let map_authority = [
1446            (
1447                "sample_import_string.isl",
1448                r#"
1449                    schema_header::{
1450                      imports: [{ id: "sample_string.isl" }],
1451                    }
1452                    
1453                    type::{
1454                      name: import_string,
1455                      type: my_string,
1456                    }
1457                    
1458                    schema_footer::{
1459                    }
1460                "#,
1461            ),
1462            (
1463                "sample_string.isl",
1464                r#"
1465                    schema_header::{
1466                      imports: [],
1467                    }
1468                    
1469                    type::{
1470                      name: my_string,
1471                      type: string,
1472                    }
1473                    
1474                    schema_footer::{
1475                    }
1476                "#,
1477            ),
1478        ];
1479        let mut schema_system =
1480            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1481        // verify if the schema loads without any errors
1482        let schema = schema_system.load_schema("sample_import_string.isl");
1483        assert!(schema.is_ok());
1484    }
1485
1486    #[test]
1487    fn schema_system_map_authority_with_inline_import_type_test() {
1488        // map with (id, ion content)
1489        let map_authority = [
1490            (
1491                "sample_number.isl",
1492                r#"
1493                    schema_header::{
1494                      imports: [],
1495                    }
1496                    
1497                    type::{
1498                      name: my_int,
1499                      type: int,
1500                    }
1501                    
1502                    type::{
1503                      name: my_number,
1504                      all_of: [
1505                        my_int,
1506                        { id: "sample_decimal.isl", type: my_decimal },
1507                      ],
1508                    }
1509                    
1510                    schema_footer::{
1511                    }
1512                "#,
1513            ),
1514            (
1515                "sample_decimal.isl",
1516                r#"
1517                    schema_header::{
1518                      imports: [],
1519                    }
1520                    
1521                    type::{
1522                      name: my_decimal,
1523                      type: decimal,
1524                    }
1525                    
1526                    type::{
1527                      name: my_string,
1528                      type: string,
1529                    }
1530                    
1531                    schema_footer::{
1532                    }
1533                "#,
1534            ),
1535        ];
1536        let mut schema_system =
1537            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1538        // verify if the schema loads without any errors
1539        let schema = schema_system.load_schema("sample_number.isl");
1540        assert!(schema.is_ok());
1541    }
1542
1543    #[test]
1544    fn schema_system_map_authority_with_incorrect_inline_import_type_test() {
1545        // map with (id, ion content)
1546        let map_authority = [
1547            (
1548                "sample_number.isl",
1549                r#"
1550                    $ion_schema_2_0
1551                    schema_header::{
1552                      imports: [],
1553                    }
1554                    
1555                    type::{
1556                      name: my_int,
1557                      type: int,
1558                    }
1559                    
1560                    type::{
1561                      name: my_number,
1562                      all_of: [
1563                        my_int,
1564                        { id: "sample_decimal.isl", type: my_decimal, as: other_decimal},
1565                      ],
1566                    }
1567                    
1568                    schema_footer::{
1569                    }
1570                "#,
1571            ),
1572            (
1573                "sample_decimal.isl",
1574                r#"
1575                    $ion_schema_2_0
1576                    schema_header::{
1577                      imports: [],
1578                    }
1579                    
1580                    type::{
1581                      name: my_decimal,
1582                      type: decimal,
1583                    }
1584                    
1585                    type::{
1586                      name: my_string,
1587                      type: string,
1588                    }
1589                    
1590                    schema_footer::{
1591                    }
1592                "#,
1593            ),
1594        ];
1595        let mut schema_system =
1596            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1597        // verify if the schema loads with specific errors
1598        let schema = schema_system.load_schema("sample_number.isl");
1599        assert!(schema.is_err());
1600        assert!(matches!(schema.unwrap_err(), InvalidSchemaError { .. }));
1601    }
1602
1603    #[test]
1604    fn schema_system_map_authority_with_isl_builtin_derived_types() {
1605        // map with (id, ion content)
1606        let map_authority = [
1607            (
1608                "sample.isl",
1609                r#"
1610                    schema_header::{
1611                      imports: [ { id: "sample_builtin_types.isl" } ],
1612                    }
1613                    
1614                    type::{
1615                        name: my_number,
1616                        type: number,
1617                    }
1618                    
1619                    type::{
1620                      name: my_type,
1621                      one_of: [
1622                        my_number,
1623                        my_text,
1624                        my_lob
1625                      ],
1626                    }
1627                    
1628                    schema_footer::{
1629                    }
1630                "#,
1631            ),
1632            (
1633                "sample_builtin_types.isl",
1634                r#"
1635                    schema_header::{
1636                      imports: [],
1637                    }
1638                    
1639                    type::{
1640                      name: my_text,
1641                      type: text,
1642                    }
1643                    
1644                    type::{
1645                        name: my_lob,
1646                        type: lob,
1647                    }
1648                    
1649                    schema_footer::{
1650                    }
1651                "#,
1652            ),
1653        ];
1654        let mut schema_system =
1655            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1656        // verify if the schema loads without any errors
1657        let schema = schema_system.load_schema("sample.isl");
1658        assert!(schema.is_ok());
1659    }
1660
1661    #[test]
1662    fn schema_system_map_authority_with_isl_builtin_derived_nullable_types() {
1663        // map with (id, ion content)
1664        let map_authority = [
1665            (
1666                "sample.isl",
1667                r#"
1668                    schema_header::{
1669                      imports: [ { id: "sample_builtin_nullable_types.isl" } ],
1670                    }
1671                    
1672                    type::{
1673                        name: my_number,
1674                        type: $number,
1675                    }
1676                    
1677                    type::{
1678                        name: my_any,
1679                        type: $any,
1680                    }
1681                    
1682                    type::{
1683                      name: my_type,
1684                      one_of: [
1685                        my_number,
1686                        my_text,
1687                        my_lob
1688                      ],
1689                    }
1690                    
1691                    schema_footer::{
1692                    }
1693                "#,
1694            ),
1695            (
1696                "sample_builtin_nullable_types.isl",
1697                r#"
1698                    schema_header::{
1699                      imports: [],
1700                    }
1701                    
1702                    type::{
1703                      name: my_text,
1704                      type: $text,
1705                    }
1706                    
1707                    type::{
1708                        name: my_lob,
1709                        type: $lob,
1710                    }
1711                    
1712                    schema_footer::{
1713                    }
1714                "#,
1715            ),
1716        ];
1717        let mut schema_system =
1718            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1719        // verify if the schema loads without any errors
1720        let schema = schema_system.load_schema("sample.isl");
1721        assert!(schema.is_ok());
1722    }
1723
1724    #[test]
1725    fn schema_system_map_authority_with_valid_transitive_type_import_test() {
1726        // map with (id, ion content)
1727        let map_authority = [
1728            (
1729                "sample.isl",
1730                r#"
1731                    schema_header::{
1732                      imports: [ { id: "sample_builtin_nullable_types.isl", type: my_text } ],
1733                    }
1734
1735                    type::{
1736                      name: my_type,
1737                      type: my_text
1738                    }
1739
1740                    schema_footer::{
1741                    }
1742                "#,
1743            ),
1744            (
1745                "sample_builtin_nullable_types.isl",
1746                r#"
1747                    schema_header::{
1748                      imports: [],
1749                    }
1750                    
1751                    type::{
1752                      name: my_text,
1753                      one_of: [
1754                        my_string, 
1755                        symbol,
1756                      ],
1757                    }
1758                    
1759                    type::{
1760                        name: my_string,
1761                        type: string,
1762                    }
1763                    
1764                    schema_footer::{
1765                    }
1766                "#,
1767            ),
1768        ];
1769        let mut schema_system =
1770            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1771        // verify if the schema loads without any error
1772        let schema = schema_system.load_schema("sample.isl");
1773        assert!(schema.is_ok());
1774    }
1775
1776    #[test]
1777    fn schema_system_map_authority_with_invalid_transitive_type_import_test() {
1778        // map with (id, ion content)
1779        let map_authority = [
1780            (
1781                "sample.isl",
1782                r#"
1783                    schema_header::{
1784                      imports: [ { id: "sample_builtin_nullable_types.isl", type: my_text } ],
1785                    }
1786                    
1787                    type::{
1788                      name: my_type,
1789                      type: my_string, // this type reference was not imported by name in sample.isl 
1790                    }
1791                    
1792                    schema_footer::{
1793                    }
1794                "#,
1795            ),
1796            (
1797                "sample_builtin_nullable_types.isl",
1798                r#"
1799                    schema_header::{
1800                      imports: [],
1801                    }
1802                    
1803                    type::{
1804                      name: my_text,
1805                      one_of: [
1806                        my_string, 
1807                        symbol,
1808                      ],
1809                    }
1810                    
1811                    type::{
1812                        name: my_string,
1813                        type: string,
1814                    }
1815                    
1816                    schema_footer::{
1817                    }
1818                "#,
1819            ),
1820        ];
1821        let mut schema_system =
1822            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1823        // verify if the schema loads with an error for invalid transitive type import
1824        let schema = schema_system.load_schema("sample.isl");
1825        assert!(schema.is_err());
1826    }
1827
1828    #[test]
1829    fn schema_system_map_authority_with_multiple_type_definitions() {
1830        // map with (id, ion content)
1831        let map_authority = [(
1832            "sample.isl",
1833            r#"
1834                    schema_header::{
1835                      imports: [],
1836                    }
1837                    
1838                    type::{
1839                      name: my_text,
1840                      one_of: [
1841                        my_string, 
1842                        symbol,
1843                      ],
1844                    }
1845                    
1846                    type::{
1847                        name: my_string,
1848                        type: string,
1849                    }
1850                    
1851                    schema_footer::{
1852                    }
1853                "#,
1854        )];
1855        let mut schema_system =
1856            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1857        // verify if the schema loads without any errors
1858        let schema = schema_system.load_schema("sample.isl");
1859        assert!(schema.is_ok());
1860    }
1861
1862    #[test]
1863    fn schema_system_map_authority_with_multiple_codependent_type_definitions() {
1864        // map with (id, ion content)
1865        let map_authority = [(
1866            "sample.isl",
1867            r#"
1868                    type::{
1869                       name: node_a,
1870                       type: list,
1871                       element: node_b,
1872                    }
1873                    
1874                    type::{
1875                       name: node_b,
1876                       type: sexp,
1877                       element: node_a,
1878                    }
1879                "#,
1880        )];
1881        let mut schema_system =
1882            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1883        // verify if the schema loads without any errors
1884        let schema = schema_system.load_schema("sample.isl");
1885        assert!(schema.is_ok());
1886    }
1887
1888    #[test]
1889    fn schema_system_map_authority_with_self_referencing_type_definition() {
1890        // map with (id, ion content)
1891        let map_authority = [(
1892            "sample.isl",
1893            r#"
1894                    type::{
1895                       name: binary_heap_node,
1896                       type: sexp,
1897                       element: { one_of: [ binary_heap_node, int ] },
1898                       container_length: range::[0, 2],
1899                    }
1900                "#,
1901        )];
1902        let mut schema_system =
1903            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1904        // verify if the schema loads without any errors
1905        let schema = schema_system.load_schema("sample.isl");
1906        assert!(schema.is_ok());
1907    }
1908
1909    #[test]
1910    fn top_level_type_def_without_name_field() {
1911        // map with (id, ion content)
1912        let map_authority = [(
1913            "sample.isl",
1914            r#"
1915                    type::{
1916                        // top level type definition must contain a `name` field
1917                    }
1918                "#,
1919        )];
1920        let mut schema_system =
1921            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1922        // verify if the schema return error for top level type definition without `name` field
1923        let schema = schema_system.load_schema("sample.isl");
1924        assert!(schema.is_err());
1925    }
1926
1927    #[test]
1928    fn top_level_type_def_with_multiple_name_field() {
1929        // map with (id, ion content)
1930        let map_authority = [(
1931            "sample.isl",
1932            r#"
1933                    type::{
1934                        name: my_type,
1935                        name: new_type
1936                    }
1937                "#,
1938        )];
1939        let mut schema_system =
1940            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1941        // verify if the schema return error for top level type definition with multiple `name` field
1942        let schema = schema_system.load_schema("sample.isl");
1943        assert!(schema.is_err());
1944    }
1945
1946    #[test]
1947    fn top_level_type_def_with_non_symbol_name_field() {
1948        // map with (id, ion content)
1949        let map_authority = [(
1950            "sample.isl",
1951            r#"
1952                    type::{
1953                        name: "my_type"
1954                    }
1955                "#,
1956        )];
1957        let mut schema_system =
1958            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1959        // verify if the schema return error for top level type definition with a `name` field of string type
1960        let schema = schema_system.load_schema("sample.isl");
1961        assert!(schema.is_err());
1962    }
1963
1964    #[test]
1965    fn valid_isl_version_marker_test() {
1966        // map with (id, ion content)
1967        let map_authority = [(
1968            "sample.isl",
1969            r#"
1970               $ion_schema_1_0
1971            "#,
1972        )];
1973        let mut schema_system =
1974            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1975        // verify if the schema loads without any errors
1976        let schema = schema_system.load_schema("sample.isl");
1977        assert!(schema.is_ok());
1978    }
1979
1980    #[test]
1981    fn invalid_isl_version_marker_test() {
1982        // map with (id, ion content)
1983        let map_authority = [(
1984            "sample.isl",
1985            r#"
1986                $ion_schema_4_5
1987            "#,
1988        )];
1989        let mut schema_system =
1990            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
1991        // verify if the schema return error for invalid ISL version marker
1992        let schema = schema_system.load_schema("sample.isl");
1993        assert!(schema.is_err());
1994    }
1995
1996    #[test]
1997    fn unsupported_isl_version_marker_test() {
1998        // map with (id, ion content)
1999        let map_authority = [(
2000            "sample.isl",
2001            r#"
2002                $ion_schema_2_1
2003            "#,
2004        )];
2005        let mut schema_system =
2006            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
2007        // verify if the schema return error for unsupported ISL version marker
2008        let schema = schema_system.load_schema("sample.isl");
2009        assert!(schema.is_err());
2010    }
2011
2012    #[test]
2013    fn open_content_test() -> IonSchemaResult<()> {
2014        // map with (id, ion content)
2015        let map_authority = [(
2016            "sample.isl",
2017            r#"
2018                schema_header::{}
2019                
2020                type::{
2021                  name: my_type,
2022                  type: string,
2023                }
2024                
2025                open_content_1::{
2026                    unknown_constraint: "this is an open content struct"
2027                }
2028                
2029                open_content_2::{
2030                    unknown_constraint: "this is an open content struct"
2031                }
2032                
2033                schema_footer::{}
2034            "#,
2035        )];
2036        let mut schema_system =
2037            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
2038        let schema = schema_system.load_isl_schema("sample.isl")?;
2039        let expected_open_content: Vec<_> = Element::read_all(
2040            r#"
2041                open_content_1::{
2042                    unknown_constraint: "this is an open content struct"
2043                }
2044                
2045                open_content_2::{
2046                    unknown_constraint: "this is an open content struct"
2047                }
2048            "#
2049            .as_bytes(),
2050        )?
2051        .into_iter()
2052        .collect();
2053
2054        // verify the open content that is retrieved from the ISL model is same as the expected open content
2055        let open_content: Vec<_> = schema.open_content().map(|x| x.to_owned()).collect();
2056        assert_eq!(open_content.len(), 2);
2057        assert_eq!(open_content, expected_open_content);
2058        Ok(())
2059    }
2060
2061    #[test]
2062    fn unexpected_fields_in_isl_v2_0_header() {
2063        // map with (id, ion content)
2064        let map_authority = [(
2065            "sample.isl",
2066            r#"
2067                $ion_schema_2_0
2068                schema_header::{
2069                    user_reserved_fields: {
2070                        schema_header: [ foo, bar ],
2071                        type: [ baz ]
2072                    },
2073                    baz: "this is an unexpected field in schema header"
2074                }
2075                
2076                type::{
2077                  name: my_type,
2078                  type: string,
2079                }
2080                
2081                schema_footer::{}
2082            "#,
2083        )];
2084        let mut schema_system =
2085            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
2086        let schema = schema_system.load_isl_schema("sample.isl");
2087        assert!(schema.is_err());
2088    }
2089
2090    #[test]
2091    fn unexpected_fields_in_isl_v2_0_footer() {
2092        // map with (id, ion content)
2093        let map_authority = [(
2094            "sample.isl",
2095            r#"
2096                $ion_schema_2_0
2097                schema_header::{
2098                    user_reserved_fields: {
2099                        schema_footer: [ foo, bar ],
2100                        sdhema_header: [ baz ]
2101                    },
2102                }
2103                
2104                type::{
2105                  name: my_type,
2106                  type: string,
2107                }
2108                
2109                schema_footer::{
2110                    baz: "this is an unexpected field in schema header"
2111                }
2112            "#,
2113        )];
2114        let mut schema_system =
2115            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
2116        let schema = schema_system.load_isl_schema("sample.isl");
2117        assert!(schema.is_err());
2118    }
2119
2120    #[test]
2121    fn unexpected_fields_in_isl_v2_0_type() {
2122        // map with (id, ion content)
2123        let map_authority = [(
2124            "sample.isl",
2125            r#"
2126                $ion_schema_2_0
2127                schema_header::{
2128                    user_reserved_fields: {
2129                        schema_header: [ baz ],
2130                        type: [ foo, bar ]
2131                    },
2132                }
2133                
2134                type::{
2135                  name: my_type,
2136                  type: string,
2137                  baz: "this is an unexpected field in schema header"
2138                }
2139                
2140                schema_footer::{}
2141            "#,
2142        )];
2143        let mut schema_system =
2144            SchemaSystem::new(vec![Box::new(MapDocumentAuthority::new(map_authority))]);
2145        let schema = schema_system.load_isl_schema("sample.isl");
2146        assert!(schema.is_err());
2147    }
2148
2149    #[test]
2150    fn load_schema_from_isl_schema_v1_0_test() {
2151        // an ISL 1.0 type definition
2152        let isl_type = isl_type::v_1_0::named_type(
2153            "my_type",
2154            [isl_constraint::v_1_0::element(
2155                isl_type_reference::v_1_0::named_type_ref("int"),
2156            )],
2157        );
2158        let isl = IslSchema::schema_v_1_0("sample.isl", vec![], vec![isl_type], vec![], vec![]);
2159        let mut schema_system = SchemaSystem::new(vec![]);
2160        let schema = schema_system.load_schema_from_isl_schema_v1_0(isl);
2161
2162        // verify the resolved schema generated from the ISL 1.0 model is valid
2163        assert!(schema.is_ok());
2164    }
2165
2166    #[test]
2167    fn load_schema_from_isl_schema_v2_0_test() {
2168        // an ISL 2.0 type definition
2169        let isl_type = isl_type::v_2_0::named_type(
2170            "my_type",
2171            [isl_constraint::v_2_0::type_constraint(
2172                isl_type_reference::v_2_0::named_type_ref("int"),
2173            )],
2174        );
2175        let isl = IslSchema::schema_v_2_0(
2176            "sample.isl",
2177            UserReservedFields::default(),
2178            vec![],
2179            vec![isl_type],
2180            vec![],
2181            vec![],
2182        );
2183        let mut schema_system = SchemaSystem::new(vec![]);
2184        let schema = schema_system.load_schema_from_isl_schema_v2_0(isl);
2185
2186        // verify the resolved schema generated from the ISL 2.0 model is valid
2187        assert!(schema.is_ok());
2188    }
2189
2190    #[test]
2191    fn load_schema_from_isl_schema_v1_0_with_isl_v2_0_constraints_test() {
2192        // an ISL 1.0 type that contains ISL 2.0 related constraints
2193        let isl_type = isl_type::v_1_0::named_type(
2194            "my_type",
2195            [isl_constraint::v_2_0::type_constraint(
2196                isl_type_reference::v_2_0::named_type_ref("int"),
2197            )],
2198        );
2199        let isl = IslSchema::schema_v_1_0("sample.isl", vec![], vec![isl_type], vec![], vec![]);
2200        let mut schema_system = SchemaSystem::new(vec![]);
2201        let schema = schema_system.load_schema_from_isl_schema_v1_0(isl);
2202
2203        // verify the resolved schema generated from the ISL 1.0 model that contains ISL 2.0 constraints is invalid
2204        assert!(schema.is_err());
2205    }
2206
2207    #[test]
2208    fn load_schema_from_isl_schema_v2_0_with_isl_v1_0_constraints_test() {
2209        // an ISL 2.0 type that contains ISL 1.0 related constraints
2210        let isl_type = isl_type::v_2_0::named_type(
2211            "my_type",
2212            [isl_constraint::v_1_0::type_constraint(
2213                isl_type_reference::v_1_0::named_type_ref("int"),
2214            )],
2215        );
2216        let isl = IslSchema::schema_v_2_0(
2217            "sample.isl",
2218            UserReservedFields::default(),
2219            vec![],
2220            vec![isl_type],
2221            vec![],
2222            vec![],
2223        );
2224        let mut schema_system = SchemaSystem::new(vec![]);
2225        let schema = schema_system.load_schema_from_isl_schema_v2_0(isl);
2226
2227        // verify the resolved schema generated from the ISL 2.0 model that contains ISL 1.0 constraints is invalid
2228        assert!(&schema.is_err());
2229    }
2230
2231    #[test]
2232    fn new_schema_test() {
2233        let mut schema_system = SchemaSystem::new(vec![]);
2234        let schema = schema_system.new_schema(
2235            br#"
2236                $ion_schema_2_0
2237                schema_header::{}
2238                
2239                type::{
2240                  name: my_type,
2241                  type: string,
2242                }
2243                
2244                schema_footer::{}
2245            "#,
2246            "sample.isl",
2247        );
2248        assert!(schema.is_ok());
2249    }
2250
2251    #[test]
2252    fn new_schema_invalid_test() {
2253        let mut schema_system = SchemaSystem::new(vec![]);
2254        let schema = schema_system.new_schema(
2255            br#"
2256                $ion_schema_2_0
2257                schema_header::{}
2258                
2259                type::{
2260                  name: my_type,
2261                  type: nullable::string, // `nullable` annotation is not supported in ISL 2.0
2262                }
2263                
2264                schema_footer::{}
2265            "#,
2266            "sample.isl",
2267        );
2268        assert!(schema.is_err());
2269    }
2270
2271    #[test]
2272    fn new_isl_schema_test() {
2273        let mut schema_system = SchemaSystem::new(vec![]);
2274        let isl = schema_system.new_isl_schema(
2275            br#"
2276                $ion_schema_2_0
2277                schema_header::{}
2278                
2279                type::{
2280                  name: my_type,
2281                  type: string,
2282                }
2283                
2284                schema_footer::{}
2285            "#,
2286            "sample.isl",
2287        );
2288        assert!(isl.is_ok());
2289    }
2290
2291    #[test]
2292    fn new_isl_schema_invalid_test() {
2293        let mut schema_system = SchemaSystem::new(vec![]);
2294        let isl = schema_system.new_isl_schema(
2295            br#"
2296                $ion_schema_2_0
2297                schema_header::{}
2298                
2299                type::{
2300                  name: my_type,
2301                  type: nullable::string, // `nullable` annotation is not supported in ISL 2.0
2302                }
2303                
2304                schema_footer::{}
2305            "#,
2306            "sample.isl",
2307        );
2308        assert!(isl.is_err());
2309    }
2310
2311    #[test]
2312    fn test_send_schema() {
2313        fn assert_send<T: Send>() {}
2314        assert_send::<Schema>();
2315    }
2316
2317    #[test]
2318    fn test_sync_schema() {
2319        fn assert_sync<T: Sync>() {}
2320        assert_sync::<Schema>();
2321    }
2322
2323    #[test]
2324    fn test_send_schema_system() {
2325        fn assert_send<T: Send>() {}
2326        assert_send::<SchemaSystem>();
2327    }
2328
2329    #[test]
2330    fn test_sync_schema_system() {
2331        fn assert_sync<T: Sync>() {}
2332        assert_sync::<SchemaSystem>();
2333    }
2334}