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}