1use std::rc::Rc;
2
3use hashlink::LinkedHashMap;
4
5pub mod engine;
6#[macro_use]
7pub mod error;
8pub mod loader;
9pub mod reference;
10pub mod schemas;
11pub mod validation;
12
13pub use engine::Engine;
14pub use error::Error;
15pub use reference::Reference;
16pub use schemas::AnyOfSchema;
17pub use schemas::ArraySchema;
18pub use schemas::BoolOrTypedSchema;
19pub use schemas::ConstSchema;
20pub use schemas::EnumSchema;
21pub use schemas::IntegerSchema;
22pub use schemas::NotSchema;
23pub use schemas::NumberSchema;
24pub use schemas::ObjectSchema;
25pub use schemas::OneOfSchema;
26pub use schemas::StringSchema;
27pub use validation::Context;
28pub use validation::Validator;
29
30use schemas::TypedSchema;
31
32pub fn version() -> String {
34 clap::crate_version!().to_string()
35}
36
37pub type Result<T> = std::result::Result<T, Error>;
39
40#[derive(Debug, Default)]
42pub struct RootSchema {
43 pub id: Option<String>,
44 pub meta_schema: Option<String>,
45 pub defs: Option<LinkedHashMap<String, YamlSchema>>,
46 pub schema: Rc<YamlSchema>,
47}
48
49impl RootSchema {
50 pub fn new(schema: YamlSchema) -> RootSchema {
52 RootSchema {
53 id: None,
54 meta_schema: None,
55 defs: None,
56 schema: Rc::new(schema),
57 }
58 }
59
60 pub fn new_with_schema(schema: Schema) -> RootSchema {
62 RootSchema::new(YamlSchema::from(schema))
63 }
64
65 pub fn load_file(path: &str) -> Result<RootSchema> {
67 loader::load_file(path)
68 }
69
70 pub fn load_from_str(schema: &str) -> Result<RootSchema> {
71 let docs = saphyr::Yaml::load_from_str(schema)?;
72 if docs.is_empty() {
73 return Ok(RootSchema::new(YamlSchema::empty())); }
75 loader::load_from_doc(docs.first().unwrap())
76 }
77
78 pub fn validate(&self, context: &Context, value: &saphyr::MarkedYaml) -> Result<()> {
79 self.schema.validate(context, value)?;
80 Ok(())
81 }
82
83 pub fn get_def(&self, name: &str) -> Option<&YamlSchema> {
84 if let Some(defs) = &self.defs {
85 return defs.get(&name.to_owned());
86 }
87 None
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq)]
93pub enum Number {
94 Integer(i64),
95 Float(f64),
96}
97
98impl Number {
99 pub fn integer(value: i64) -> Number {
101 Number::Integer(value)
102 }
103
104 pub fn float(value: f64) -> Number {
106 Number::Float(value)
107 }
108}
109
110impl std::fmt::Display for Number {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 match self {
113 Number::Integer(v) => write!(f, "{}", v),
114 Number::Float(v) => write!(f, "{}", v),
115 }
116 }
117}
118
119#[derive(Debug, PartialEq)]
120pub enum ConstValue {
121 Boolean(bool),
122 Null,
123 Number(Number),
124 String(String),
125}
126
127impl ConstValue {
128 pub fn boolean(value: bool) -> ConstValue {
129 ConstValue::Boolean(value)
130 }
131 pub fn integer(value: i64) -> ConstValue {
132 ConstValue::Number(Number::integer(value))
133 }
134 pub fn float(value: f64) -> ConstValue {
135 ConstValue::Number(Number::float(value))
136 }
137 pub fn null() -> ConstValue {
138 ConstValue::Null
139 }
140 pub fn string<V: Into<String>>(value: V) -> ConstValue {
141 ConstValue::String(value.into())
142 }
143 pub fn from_saphyr_yaml(value: &saphyr::Yaml) -> ConstValue {
144 match value {
145 saphyr::Yaml::Boolean(b) => ConstValue::Boolean(*b),
146 saphyr::Yaml::Integer(i) => ConstValue::Number(Number::integer(*i)),
147 saphyr::Yaml::Real(s) => ConstValue::Number(Number::float(s.parse::<f64>().unwrap())),
148 saphyr::Yaml::String(s) => ConstValue::String(s.clone()),
149 saphyr::Yaml::Null => ConstValue::Null,
150 _ => panic!("Expected a constant value, but got: {:?}", value),
151 }
152 }
153}
154
155impl TryFrom<&saphyr::YamlData<saphyr::MarkedYaml>> for ConstValue {
156 type Error = crate::Error;
157
158 fn try_from(value: &saphyr::YamlData<saphyr::MarkedYaml>) -> Result<Self> {
159 match value {
160 saphyr::YamlData::String(s) => Ok(ConstValue::String(s.clone())),
161 saphyr::YamlData::Integer(i) => Ok(ConstValue::Number(Number::integer(*i))),
162 saphyr::YamlData::Real(f) => {
163 let f = f.parse::<f64>()?;
164 Ok(ConstValue::Number(Number::float(f)))
165 }
166 saphyr::YamlData::Boolean(b) => Ok(ConstValue::Boolean(*b)),
167 saphyr::YamlData::Null => Ok(ConstValue::Null),
168 v => Err(unsupported_type!(
169 "Expected a constant value, but got: {:?}",
170 v
171 )),
172 }
173 }
174}
175
176impl TryFrom<saphyr::Yaml> for ConstValue {
177 type Error = crate::Error;
178
179 fn try_from(value: saphyr::Yaml) -> Result<Self> {
180 match value {
181 saphyr::Yaml::Boolean(b) => Ok(ConstValue::Boolean(b)),
182 saphyr::Yaml::Integer(i) => Ok(ConstValue::Number(Number::integer(i))),
183 saphyr::Yaml::Real(s) => {
184 let f = s.parse::<f64>()?;
185 Ok(ConstValue::Number(Number::float(f)))
186 }
187 saphyr::Yaml::String(s) => Ok(ConstValue::String(s.clone())),
188 saphyr::Yaml::Null => Ok(ConstValue::Null),
189 v => Err(unsupported_type!(
190 "Expected a constant value, but got: {:?}",
191 v
192 )),
193 }
194 }
195}
196
197impl std::fmt::Display for ConstValue {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 match self {
200 ConstValue::Boolean(b) => write!(f, "{} (bool)", b),
201 ConstValue::Null => write!(f, "null"),
202 ConstValue::Number(n) => write!(f, "{} (number)", n),
203 ConstValue::String(s) => write!(f, "\"{}\"", s),
204 }
205 }
206}
207
208#[derive(Debug, Default, PartialEq)]
210pub struct YamlSchema {
211 pub metadata: Option<LinkedHashMap<String, String>>,
212 pub r#ref: Option<Reference>,
213 pub schema: Option<Schema>,
214}
215
216impl From<Schema> for YamlSchema {
217 fn from(schema: Schema) -> Self {
218 YamlSchema {
219 schema: Some(schema),
220 ..Default::default()
221 }
222 }
223}
224
225impl YamlSchema {
226 pub fn empty() -> YamlSchema {
227 YamlSchema {
228 schema: Some(Schema::Empty),
229 ..Default::default()
230 }
231 }
232
233 pub fn null() -> YamlSchema {
234 YamlSchema {
235 schema: Some(Schema::TypeNull),
236 ..Default::default()
237 }
238 }
239
240 pub fn boolean_literal(value: bool) -> YamlSchema {
241 YamlSchema {
242 schema: Some(Schema::BooleanLiteral(value)),
243 ..Default::default()
244 }
245 }
246
247 pub fn reference(reference: Reference) -> YamlSchema {
248 YamlSchema {
249 r#ref: Some(reference),
250 ..Default::default()
251 }
252 }
253}
254
255#[derive(Debug, Default, PartialEq)]
256pub enum Schema {
257 #[default]
258 Empty, BooleanLiteral(bool), Const(ConstSchema), TypeNull, Array(ArraySchema), BooleanSchema, Integer(IntegerSchema), Number(NumberSchema), Object(ObjectSchema), String(StringSchema), Enum(EnumSchema), AnyOf(AnyOfSchema), OneOf(OneOfSchema), Not(NotSchema), }
273
274impl std::fmt::Display for Schema {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 match &self {
277 Schema::Empty => write!(f, "<empty schema>"),
278 Schema::TypeNull => write!(f, "type: null"),
279 Schema::BooleanLiteral(b) => write!(f, "{}", b),
280 Schema::BooleanSchema => write!(f, "type: boolean"),
281 Schema::Const(c) => write!(f, "{}", c),
282 Schema::Enum(e) => write!(f, "{}", e),
283 Schema::Integer(i) => write!(f, "{}", i),
284 Schema::AnyOf(any_of_schema) => {
285 write!(f, "{}", any_of_schema)
286 }
287 Schema::OneOf(one_of_schema) => {
288 write!(f, "{}", one_of_schema)
289 }
290 Schema::Not(not_schema) => {
291 write!(f, "{}", not_schema)
292 }
293 Schema::String(s) => write!(f, "{}", s),
294 Schema::Number(n) => write!(f, "{}", n),
295 Schema::Object(o) => write!(f, "{}", o),
296 Schema::Array(a) => write!(f, "{}", a),
297 }
298 }
299}
300
301impl std::fmt::Display for YamlSchema {
302 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303 write!(f, "{{")?;
304 if let Some(metadata) = &self.metadata {
305 write!(f, "metadata: {:?}, ", metadata)?;
306 }
307 if let Some(r#ref) = &self.r#ref {
308 r#ref.fmt(f)?;
309 }
310 if let Some(schema) = &self.schema {
311 write!(f, "schema: {}", schema)?;
312 }
313 write!(f, "}}")
314 }
315}
316
317impl From<TypedSchema> for Schema {
320 fn from(schema: TypedSchema) -> Self {
321 match schema {
322 TypedSchema::Array(array_schema) => Schema::Array(array_schema),
323 TypedSchema::BooleanSchema => Schema::BooleanSchema,
324 TypedSchema::Null => Schema::TypeNull,
325 TypedSchema::Integer(integer_schema) => Schema::Integer(integer_schema),
326 TypedSchema::Number(number_schema) => Schema::Number(number_schema),
327 TypedSchema::Object(object_schema) => Schema::Object(object_schema),
328 TypedSchema::String(string_schema) => Schema::String(string_schema),
329 }
330 }
331}
332
333fn format_vec<V>(vec: &[V]) -> String
335where
336 V: std::fmt::Display,
337{
338 let items: Vec<String> = vec.iter().map(|v| format!("{}", v)).collect();
339 format!("[{}]", items.join(", "))
340}
341
342fn format_yaml_data(data: &saphyr::YamlData<saphyr::MarkedYaml>) -> String {
344 match data {
345 saphyr::YamlData::Null => "null".to_string(),
346 saphyr::YamlData::Boolean(b) => b.to_string(),
347 saphyr::YamlData::Integer(i) => i.to_string(),
348 saphyr::YamlData::Real(s) => s.clone(),
349 saphyr::YamlData::String(s) => format!("\"{}\"", s),
350 saphyr::YamlData::Array(array) => {
351 let items: Vec<String> = array.iter().map(|v| format_yaml_data(&v.data)).collect();
352 format!("[{}]", items.join(", "))
353 }
354 saphyr::YamlData::Hash(hash) => {
355 let items: Vec<String> = hash
356 .iter()
357 .map(|(k, v)| {
358 format!(
359 "{}: {}",
360 format_yaml_data(&k.data),
361 format_yaml_data(&v.data)
362 )
363 })
364 .collect();
365 format!("[{}]", items.join(", "))
366 }
367 _ => format!("<unsupported type: {:?}>", data),
368 }
369}
370
371fn format_marker(marker: &saphyr::Marker) -> String {
372 format!("[{}, {}]", marker.line(), marker.col())
373}
374
375#[cfg(test)]
377#[ctor::ctor]
378fn init() {
379 env_logger::builder()
380 .filter_level(log::LevelFilter::Trace)
381 .format_target(false)
382 .format_timestamp_secs()
383 .target(env_logger::Target::Stdout)
384 .init();
385}
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390
391 #[test]
392 fn test_const_equality() {
393 let i1 = ConstValue::integer(42);
394 let i2 = ConstValue::integer(42);
395 assert_eq!(i1, i2);
396
397 let s1 = ConstValue::string("NW");
398 let s2 = ConstValue::string("NW");
399 assert_eq!(s1, s2);
400 }
401}