Skip to main content

ion_schema/
authority.rs

1//! Provides a way to construct [`DocumentAuthority`].
2//!
3//! A [`DocumentAuthority`] is responsible for resolving a particular class of
4//! schema identifiers as per *[Ion Schema spec]*.
5//!
6//! There are two types of a [`DocumentAuthority`] as defined below.
7//! * [`FileSystemDocumentAuthority`] : Attempts to resolve schema ids to files relative to a basePath.
8//! * [`MapDocumentAuthority`] : Attempts to resolve schema ids to ion elements using the map of (id, ion content).
9//!
10//! [Ion Schema spec]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#schema-authorities
11//!
12//! ## Example usage of `authority` module to create a [`DocumentAuthority`]:
13//! ```
14//! use ion_schema::authority::{MapDocumentAuthority, FileSystemDocumentAuthority};
15//! use std::path::Path;
16//!
17//! // map with (id, ion content) to represent `sample_number` schema
18//! let map_authority = [
19//!     (
20//!         "sample_number.isl",
21//!         r#"
22//!             schema_header::{
23//!                 imports: [{ id: "sample_decimal.isl", type: my_decimal, as: other_decimal }],
24//!             }
25//!             type::{
26//!                 name: my_int,
27//!                 type: int,
28//!             }
29//!             type::{
30//!                 name: my_number,
31//!                 all_of: [
32//!                     my_int,
33//!                     other_decimal,
34//!                  ],
35//!             }
36//!             schema_footer::{
37//!            }
38//!         "#,
39//!   ),
40//!   (
41//!         "sample_decimal.isl",
42//!         r#"
43//!             schema_header::{
44//!                 imports: [],
45//!             }
46//!             type::{
47//!                 name: my_decimal,
48//!                 type: decimal,
49//!              }
50//!              schema_footer::{
51//!              }
52//!         "#,
53//!    ),
54//! ];
55//!
56//! let map_document_authority = MapDocumentAuthority::new(map_authority);
57//!
58//! let file_system_document_authority = FileSystemDocumentAuthority::new(Path::new(
59//!     "sample_schemas",
60//! ));
61//! ```
62
63use crate::result::{unresolvable_schema_error_raw, IonSchemaResult};
64use ion_rs::Element;
65use std::collections::HashMap;
66use std::fmt::Debug;
67use std::fs;
68use std::path::{Path, PathBuf};
69
70/// An [`DocumentAuthority`] is responsible for resolving a particular class of
71/// schema identifiers.
72///
73/// The structure of a schema identifier string is defined by the
74/// Authority responsible for the schema/type(s) being imported.
75///
76/// [`DocumentAuthority`] is *[Send and Sync]* i.e. it is safe to send it to another thread and to be shared between threads.
77/// For cases when one does need thread-safe interior mutability, they can use the explicit locking via [`std::sync::Mutex`] and [`std::sync::RwLock`].
78///
79/// [Send and Sync]: https://doc.rust-lang.org/nomicon/send-and-sync.html
80pub trait DocumentAuthority: Debug + Send + Sync {
81    fn elements(&self, id: &str) -> IonSchemaResult<Vec<Element>>;
82}
83
84/// An [`DocumentAuthority`] implementation that attempts to resolve schema ids to files
85/// relative to a basePath.
86#[derive(Debug, Clone)]
87pub struct FileSystemDocumentAuthority {
88    base_path: PathBuf,
89}
90
91impl FileSystemDocumentAuthority {
92    pub fn new(base_path: &Path) -> Self {
93        Self {
94            base_path: base_path.to_path_buf(),
95        }
96    }
97
98    /// Returns the base path for this [`FileSystemDocumentAuthority`]
99    pub fn base_path(&self) -> &Path {
100        self.base_path.as_path()
101    }
102}
103
104impl DocumentAuthority for FileSystemDocumentAuthority {
105    /// Returns a vector of [`Element`]s based on given schema id
106    fn elements(&self, id: &str) -> IonSchemaResult<Vec<Element>> {
107        let absolute_path = self.base_path().join(id);
108        // if absolute_path exists for the given id then load schema with file contents
109        let ion_content = fs::read(absolute_path)?;
110        let schema_content = Element::read_all(ion_content)?;
111        Ok(schema_content.into_iter().collect())
112    }
113}
114
115/// An [`DocumentAuthority`] implementation that attempts to resolve schema ids to ion elements using the map.
116#[derive(Debug, Clone)]
117pub struct MapDocumentAuthority {
118    ion_content_by_id: HashMap<String, String>, // This map represents (id, ion content) which can used to resolve schema ids to Vec<Element>
119}
120
121impl MapDocumentAuthority {
122    pub fn new<'a, I: IntoIterator<Item = (&'a str, &'a str)>>(ion_content_by_id: I) -> Self {
123        Self {
124            ion_content_by_id: ion_content_by_id
125                .into_iter()
126                .map(|(id, ion)| (id.to_owned(), ion.to_owned()))
127                .collect(),
128        }
129    }
130}
131
132impl DocumentAuthority for MapDocumentAuthority {
133    /// Returns a vector of [`Element`]s based on given schema id using ion_content_by_id map
134    fn elements(&self, id: &str) -> IonSchemaResult<Vec<Element>> {
135        // if ion content exists for the given id  in the map then return ion content as Elements
136        let ion_content = self.ion_content_by_id.get(id).ok_or_else(|| {
137            unresolvable_schema_error_raw(format!(
138                "MapDocumentAuthority does not contain schema with id: {id}"
139            ))
140        })?;
141        let schema_content = Element::read_all(ion_content.as_bytes())?;
142        Ok(schema_content.into_iter().collect())
143    }
144}