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