1use std::rc::Rc;
2
3use hashlink::LinkedHashMap;
4use saphyr::LoadableYamlNode;
5use saphyr::MarkedYaml;
6use saphyr::Scalar;
7use saphyr::YamlData;
8
9pub mod engine;
10#[macro_use]
11pub mod error;
12pub mod loader;
13pub mod reference;
14pub mod schemas;
15pub mod utils;
16pub mod validation;
17
18pub use engine::Engine;
19pub use error::Error;
20pub use reference::Reference;
21pub use schemas::AnyOfSchema;
22pub use schemas::ArraySchema;
23pub use schemas::BoolOrTypedSchema;
24pub use schemas::ConstSchema;
25pub use schemas::EnumSchema;
26pub use schemas::IntegerSchema;
27pub use schemas::NotSchema;
28pub use schemas::NumberSchema;
29pub use schemas::ObjectSchema;
30pub use schemas::OneOfSchema;
31pub use schemas::Schema;
32pub use schemas::StringSchema;
33pub use schemas::TypedSchema;
34pub use schemas::YamlSchema;
35pub use validation::Context;
36pub use validation::Validator;
37
38use crate::utils::format_marker;
39
40pub fn version() -> String {
42 clap::crate_version!().to_string()
43}
44
45pub type Result<T> = std::result::Result<T, Error>;
47
48#[derive(Debug, Default, PartialEq)]
51pub struct RootSchema {
52 pub id: Option<String>,
53 pub meta_schema: Option<String>,
54 pub defs: Option<LinkedHashMap<String, YamlSchema>>,
55 pub schema: Rc<YamlSchema>,
56}
57
58impl RootSchema {
59 pub fn new(schema: YamlSchema) -> RootSchema {
61 RootSchema {
62 id: None,
63 meta_schema: None,
64 defs: None,
65 schema: Rc::new(schema),
66 }
67 }
68
69 pub fn builder() -> RootSchemaBuilder {
71 RootSchemaBuilder::new()
72 }
73
74 pub fn new_with_schema(schema: Schema) -> RootSchema {
76 RootSchema::new(YamlSchema::from(schema))
77 }
78
79 pub fn load_file(path: &str) -> Result<RootSchema> {
81 loader::load_file(path)
82 }
83
84 pub fn load_from_str(schema: &str) -> Result<RootSchema> {
85 let docs = MarkedYaml::load_from_str(schema)?;
86 if docs.is_empty() {
87 return Ok(RootSchema::new(YamlSchema::empty())); }
89 loader::load_from_doc(docs.first().unwrap())
90 }
91
92 pub fn validate(&self, context: &Context, value: &MarkedYaml) -> Result<()> {
93 self.schema.validate(context, value)?;
94 Ok(())
95 }
96
97 pub fn get_def(&self, name: &str) -> Option<&YamlSchema> {
98 if let Some(defs) = &self.defs {
99 return defs.get(&name.to_owned());
100 }
101 None
102 }
103}
104
105pub struct RootSchemaBuilder(RootSchema);
106
107impl Default for RootSchemaBuilder {
108 fn default() -> Self {
109 Self::new()
110 }
111}
112
113impl RootSchemaBuilder {
114 pub fn new() -> Self {
116 Self(RootSchema::default())
117 }
118
119 pub fn build(&mut self) -> RootSchema {
120 std::mem::take(&mut self.0)
121 }
122
123 pub fn id<S: Into<String>>(&mut self, id: S) -> &mut Self {
124 self.0.id = Some(id.into());
125 self
126 }
127
128 pub fn meta_schema<S: Into<String>>(&mut self, meta_schema: S) -> &mut Self {
129 self.0.meta_schema = Some(meta_schema.into());
130 self
131 }
132
133 pub fn defs(&mut self, defs: LinkedHashMap<String, YamlSchema>) -> &mut Self {
134 self.0.defs = Some(defs);
135 self
136 }
137
138 pub fn schema(&mut self, schema: YamlSchema) -> &mut Self {
139 self.0.schema = Rc::new(schema);
140 self
141 }
142}
143
144#[derive(Debug, Clone, Copy, PartialEq)]
146pub enum Number {
147 Integer(i64),
148 Float(f64),
149}
150
151impl Number {
152 pub fn integer(value: i64) -> Number {
154 Number::Integer(value)
155 }
156
157 pub fn float(value: f64) -> Number {
159 Number::Float(value)
160 }
161}
162
163impl TryFrom<&MarkedYaml<'_>> for Number {
164 type Error = Error;
165 fn try_from(value: &MarkedYaml) -> Result<Number> {
166 if let YamlData::Value(scalar) = &value.data {
167 match scalar {
168 Scalar::Integer(i) => Ok(Number::integer(*i)),
169 Scalar::FloatingPoint(o) => Ok(Number::float(o.into_inner())),
170 _ => Err(generic_error!(
171 "{} Expected type: integer or float, but got: {:?}",
172 format_marker(&value.span.start),
173 value
174 )),
175 }
176 } else {
177 Err(generic_error!(
178 "{} Expected scalar, but got: {:?}",
179 format_marker(&value.span.start),
180 value
181 ))
182 }
183 }
184}
185
186impl std::fmt::Display for Number {
187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188 match self {
189 Number::Integer(v) => write!(f, "{v}"),
190 Number::Float(v) => write!(f, "{v}"),
191 }
192 }
193}
194
195#[derive(Debug, PartialEq)]
199pub enum ConstValue {
200 Null,
201 Boolean(bool),
202 Number(Number),
203 String(String),
204}
205
206impl ConstValue {
207 pub fn null() -> ConstValue {
208 ConstValue::Null
209 }
210 pub fn boolean(value: bool) -> ConstValue {
211 ConstValue::Boolean(value)
212 }
213 pub fn integer(value: i64) -> ConstValue {
214 ConstValue::Number(Number::integer(value))
215 }
216 pub fn float(value: f64) -> ConstValue {
217 ConstValue::Number(Number::float(value))
218 }
219 pub fn string<V: Into<String>>(value: V) -> ConstValue {
220 ConstValue::String(value.into())
221 }
222}
223
224impl TryFrom<&Scalar<'_>> for ConstValue {
225 type Error = crate::Error;
226
227 fn try_from(scalar: &Scalar) -> std::result::Result<ConstValue, Self::Error> {
228 match scalar {
229 Scalar::Null => Ok(ConstValue::Null),
230 Scalar::Boolean(b) => Ok(ConstValue::Boolean(*b)),
231 Scalar::Integer(i) => Ok(ConstValue::Number(Number::integer(*i))),
232 Scalar::FloatingPoint(o) => Ok(ConstValue::Number(Number::float(o.into_inner()))),
233 Scalar::String(s) => Ok(ConstValue::String(s.to_string())),
234 }
235 }
236}
237
238impl<'a> TryFrom<&YamlData<'a, MarkedYaml<'a>>> for ConstValue {
239 type Error = crate::Error;
240
241 fn try_from(value: &YamlData<'a, MarkedYaml<'a>>) -> Result<Self> {
242 match value {
243 YamlData::Value(scalar) => scalar.try_into(),
244 v => Err(generic_error!("Expected a scalar value, but got: {:?}", v)),
245 }
246 }
247}
248
249impl<'a> TryFrom<&MarkedYaml<'a>> for ConstValue {
250 type Error = crate::Error;
251 fn try_from(value: &MarkedYaml<'a>) -> Result<ConstValue> {
252 match (&value.data).try_into() {
253 Ok(r) => Ok(r),
254 _ => Err(generic_error!(
255 "{} Expected a scalar value, but got: {:?}",
256 format_marker(&value.span.start),
257 value
258 )),
259 }
260 }
261}
262
263impl std::fmt::Display for ConstValue {
264 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
265 match self {
266 ConstValue::Boolean(b) => write!(f, "{b} (bool)"),
267 ConstValue::Null => write!(f, "null"),
268 ConstValue::Number(n) => write!(f, "{n} (number)"),
269 ConstValue::String(s) => write!(f, "\"{s}\""),
270 }
271 }
272}
273
274#[cfg(test)]
276#[ctor::ctor]
277fn init() {
278 env_logger::builder()
279 .filter_level(log::LevelFilter::Trace)
280 .format_target(false)
281 .format_timestamp_secs()
282 .target(env_logger::Target::Stdout)
283 .init();
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289 use ordered_float::OrderedFloat;
290
291 #[test]
292 fn test_const_equality() {
293 let i1 = ConstValue::integer(42);
294 let i2 = ConstValue::integer(42);
295 assert_eq!(i1, i2);
296
297 let s1 = ConstValue::string("NW");
298 let s2 = ConstValue::string("NW");
299 assert_eq!(s1, s2);
300 }
301
302 #[test]
303 fn test_scalar_to_constvalue() -> Result<()> {
304 let scalars = [
305 Scalar::Null,
306 Scalar::Boolean(true),
307 Scalar::Boolean(false),
308 Scalar::Integer(42),
309 Scalar::Integer(-1),
310 Scalar::FloatingPoint(OrderedFloat::from(3.14)),
311 Scalar::String("foo".into()),
312 ];
313
314 let expected = [
315 ConstValue::Null,
316 ConstValue::Boolean(true),
317 ConstValue::Boolean(false),
318 ConstValue::Number(Number::Integer(42)),
319 ConstValue::Number(Number::Integer(-1)),
320 ConstValue::Number(Number::Float(3.14)),
321 ConstValue::String("foo".to_string()),
322 ];
323
324 for (scalar, expected) in scalars.iter().zip(expected.iter()) {
325 let actual: ConstValue = scalar.try_into()?;
326 assert_eq!(*expected, actual);
327 }
328
329 Ok(())
330 }
331}