1#![allow(dead_code, unused_variables)]
3
4use crate::isl::isl_constraint::IslConstraintValue;
5use crate::isl::isl_type::IslType;
6use crate::result::{invalid_schema_error, invalid_schema_error_raw, IonSchemaResult};
7use ion_rs::{Element, IonResult, Struct, StructWriter, Symbol, ValueWriter, WriteAsIon};
8use regex::Regex;
9use std::sync::OnceLock;
10
11macro_rules! try_to {
16 ($getter:expr) => {
17 match $getter {
18 Some(value) => value,
19 None => invalid_schema_error(format!("Missing a value: {}", stringify!($getter)))?,
20 }
21 };
22}
23
24pub mod authority;
26mod constraint;
27mod import;
28pub(crate) mod ion_extension;
29mod ion_path;
30mod ion_schema_element;
31pub mod isl;
32mod ordered_elements_nfa;
33pub mod result;
34pub mod schema;
35pub mod system;
36mod type_reference;
37pub mod types;
38pub mod violation;
39
40pub use ion_schema_element::*;
41
42static ISL_VERSION_MARKER_REGEX: OnceLock<Regex> = OnceLock::new();
43static RESERVED_WORD_REGEX: OnceLock<Regex> = OnceLock::new();
44
45fn is_isl_version_marker(text: &str) -> bool {
47 ISL_VERSION_MARKER_REGEX
48 .get_or_init(|| Regex::new(r"^\$ion_schema_\d.*$").unwrap())
49 .is_match(text)
50}
51
52fn is_reserved_word(text: &str) -> bool {
54 RESERVED_WORD_REGEX
55 .get_or_init(|| Regex::new(r"^(\$ion_schema(_.*)?|[a-z][a-z0-9]*(_[a-z0-9]+)*)$").unwrap())
56 .is_match(text)
57}
58
59const ISL_2_0_KEYWORDS: [&str; 28] = [
60 "all_of",
61 "annotations",
62 "any_of",
63 "as",
64 "byte_length",
65 "codepoint_length",
66 "container_length",
67 "contains",
68 "element",
69 "exponent",
70 "field_names",
71 "fields",
72 "id",
73 "imports",
74 "name",
75 "not",
76 "occurs",
77 "one_of",
78 "ordered_elements",
79 "precision",
80 "regex",
81 "schema_footer",
82 "schema_header",
83 "timestamp_precision",
84 "type",
85 "user_reserved_fields",
86 "utf8_byte_length",
87 "valid_values",
88];
89
90fn load(text: &str) -> Vec<Element> {
92 Element::read_all(text.as_bytes())
93 .expect("parsing failed unexpectedly")
94 .into_iter()
95 .collect()
96}
97
98#[derive(Debug, Clone, Default, PartialEq)]
100pub struct UserReservedFields {
101 schema_header_fields: Vec<String>,
102 schema_footer_fields: Vec<String>,
103 type_fields: Vec<String>,
104}
105
106impl UserReservedFields {
107 pub(crate) fn is_empty(&self) -> bool {
108 self.type_fields.is_empty()
109 && self.schema_header_fields.is_empty()
110 && self.schema_footer_fields.is_empty()
111 }
112
113 pub(crate) fn from_ion_elements(user_reserved_fields: &Struct) -> IonSchemaResult<Self> {
115 if user_reserved_fields.fields().any(|(f, v)| {
116 f.text() != Some("schema_header")
117 && f.text() != Some("schema_footer")
118 && f.text() != Some("type")
119 }) {
120 return invalid_schema_error(
121 "User reserved fields can only have schema_header, schema_footer or type as the field names",
122 );
123 }
124 Ok(Self {
125 schema_header_fields: UserReservedFields::field_names_from_ion_elements(
126 "schema_header",
127 user_reserved_fields,
128 )?,
129 schema_footer_fields: UserReservedFields::field_names_from_ion_elements(
130 "schema_footer",
131 user_reserved_fields,
132 )?,
133 type_fields: UserReservedFields::field_names_from_ion_elements(
134 "type",
135 user_reserved_fields,
136 )?,
137 })
138 }
139
140 fn field_names_from_ion_elements(
141 user_reserved_fields_type: &str,
142 user_reserved_fields: &Struct,
143 ) -> IonSchemaResult<Vec<String>> {
144 let user_reserved_elements: Vec<&Element> = user_reserved_fields
145 .get(user_reserved_fields_type)
146 .and_then(|it| it.as_sequence().map(|s| s.elements().collect()))
147 .ok_or(invalid_schema_error_raw(
148 "User reserved fields mut be non null",
149 ))?;
150
151 let user_reserved_fields = user_reserved_elements
152 .iter()
153 .filter(|e| e.annotations().is_empty() && !e.is_null())
154 .map(|e| e.as_text().map(|s| s.to_owned()))
155 .collect::<Option<Vec<String>>>()
156 .unwrap_or(vec![]);
157
158 if user_reserved_fields.len() != user_reserved_elements.len() {
159 return invalid_schema_error("User reserved fields mut be unannotated");
160 }
161
162 if user_reserved_fields
163 .iter()
164 .any(|f| is_reserved_word(f) || ISL_2_0_KEYWORDS.binary_search(&f.as_str()).is_ok())
165 {
166 return invalid_schema_error(
167 "ISl 2.0 keywords may not be declared as user reserved fields",
168 );
169 }
170
171 Ok(user_reserved_fields)
172 }
173
174 pub(crate) fn validate_field_names_in_header(
175 &self,
176 schema_header: &Struct,
177 ) -> IonSchemaResult<()> {
178 let unexpected_fields: Vec<(&Symbol, &Element)> = schema_header
179 .fields()
180 .filter(|(f, v)| {
181 !self
182 .schema_header_fields
183 .contains(&f.text().unwrap().to_owned())
184 && f.text().unwrap() != "user_reserved_fields"
185 && f.text().unwrap() != "imports"
186 })
187 .collect();
188
189 if !unexpected_fields.is_empty() {
190 return invalid_schema_error(format!(
192 "schema header contains unexpected fields: {unexpected_fields:?}"
193 ));
194 }
195
196 Ok(())
197 }
198
199 pub(crate) fn validate_field_names_in_type(&self, isl_type: &IslType) -> IonSchemaResult<()> {
200 let unexpected_fields: &Vec<&String> = &isl_type
201 .constraints()
202 .iter()
203 .filter(|c| matches!(c.constraint_value, IslConstraintValue::Unknown(_, _)))
204 .map(|c| match &c.constraint_value {
205 IslConstraintValue::Unknown(f, v) => f,
206 _ => {
207 unreachable!("we have already filtered all other constraints")
208 }
209 })
210 .filter(|f| !self.type_fields.contains(f))
211 .collect();
212
213 if !unexpected_fields.is_empty() {
214 return invalid_schema_error(format!(
216 "schema type contains unexpected fields: {unexpected_fields:?}"
217 ));
218 }
219
220 Ok(())
221 }
222
223 pub(crate) fn validate_field_names_in_footer(
224 &self,
225 schema_footer: &Struct,
226 ) -> IonSchemaResult<()> {
227 let unexpected_fields: Vec<(&Symbol, &Element)> = schema_footer
228 .fields()
229 .filter(|(f, v)| {
230 !self
231 .schema_footer_fields
232 .contains(&f.text().unwrap().to_owned())
233 })
234 .collect();
235
236 if !unexpected_fields.is_empty() {
237 return invalid_schema_error(format!(
239 "schema footer contains unexpected fields: {unexpected_fields:?}"
240 ));
241 }
242 Ok(())
243 }
244}
245
246impl WriteAsIon for UserReservedFields {
247 fn write_as_ion<V: ValueWriter>(&self, writer: V) -> IonResult<()> {
248 let mut struct_writer = writer.struct_writer()?;
249 struct_writer
250 .field_writer("schema_header")
251 .write(&self.schema_header_fields)?;
252 struct_writer
253 .field_writer("type")
254 .write(&self.type_fields)?;
255 struct_writer
256 .field_writer("schema_footer")
257 .write(&self.schema_footer_fields)?;
258 struct_writer.close()
259 }
260}