1#![forbid(unsafe_code)]
2
3use std::collections::BTreeMap;
4use std::convert::TryFrom;
5pub use yaml_rust;
6use yaml_rust::Yaml;
7
8mod breadcrumb;
9mod errors;
10mod modifiers;
11mod types;
12mod utils;
13use modifiers::*;
14use types::*;
15
16pub use errors::schema::{SchemaError, SchemaErrorKind};
17use errors::ValidationError;
18
19use crate::types::bool::SchemaBool;
20use utils::{CondenseErrors, OptionalLookup, YamlUtils};
21
22pub trait Validate<'yaml, 'schema: 'yaml> {
24 fn validate(
25 &self,
26 ctx: &'schema Context<'schema>,
27 yaml: &'yaml Yaml,
28 ) -> Result<(), ValidationError<'yaml>>;
29}
30
31#[derive(Debug, Default)]
33pub struct Context<'schema> {
34 schemas: BTreeMap<&'schema str, Schema<'schema>>,
35}
36
37impl<'schema> Context<'schema> {
38 pub fn get_schema(&self, uri: &str) -> Option<&Schema<'schema>> {
62 self.schemas.get(uri)
63 }
64}
65
66impl<'schema> TryFrom<&'schema [Yaml]> for Context<'schema> {
68 type Error = SchemaError<'schema>;
69 fn try_from(documents: &'schema [Yaml]) -> Result<Self, Self::Error> {
70 let schemas = SchemaError::condense_errors(&mut documents.iter().map(Schema::try_from))?;
71
72 Ok(Context {
73 schemas: schemas
74 .into_iter()
75 .map(|schema| (schema.uri, schema))
76 .collect(),
77 })
78 }
79}
80
81#[derive(Debug)]
82enum PropertyType<'schema> {
83 Object(SchemaObject<'schema>),
84 Array(SchemaArray<'schema>),
85 Hash(SchemaHash<'schema>),
86 String(SchemaString),
87 Integer(SchemaInteger),
88 Real(SchemaReal),
89 Bool(SchemaBool),
90 Reference(SchemaReference<'schema>),
91 Not(SchemaNot<'schema>),
92 OneOf(SchemaOneOf<'schema>),
93 AllOf(SchemaAllOf<'schema>),
94 AnyOf(SchemaAnyOf<'schema>),
95}
96
97impl<'schema> TryFrom<&'schema Yaml> for PropertyType<'schema> {
98 type Error = SchemaError<'schema>;
99 fn try_from(yaml: &'schema Yaml) -> Result<Self, Self::Error> {
100 if yaml.as_hash().is_none() {
101 return Err(SchemaErrorKind::WrongType {
102 expected: "hash",
103 actual: yaml.type_to_str(),
104 }
105 .into());
106 }
107
108 if let Some(uri) = yaml
109 .lookup("$ref", "string", Yaml::as_str)
110 .into_optional()
111 .map_err(SchemaError::from)?
112 {
113 return Ok(PropertyType::Reference(SchemaReference { uri }));
114 }
115
116 if yaml
117 .lookup("not", "hash", Option::from)
118 .into_optional()
119 .map_err(SchemaError::from)?
120 .is_some()
121 {
122 return Ok(PropertyType::Not(SchemaNot::try_from(yaml)?));
123 }
124
125 if yaml
126 .lookup("oneOf", "hash", Option::from)
127 .into_optional()
128 .map_err(SchemaError::from)?
129 .is_some()
130 {
131 return Ok(PropertyType::OneOf(SchemaOneOf::try_from(yaml)?));
132 }
133
134 if yaml
135 .lookup("allOf", "hash", Option::from)
136 .into_optional()
137 .map_err(SchemaError::from)?
138 .is_some()
139 {
140 return Ok(PropertyType::AllOf(SchemaAllOf::try_from(yaml)?));
141 }
142
143 if yaml
144 .lookup("anyOf", "hash", Option::from)
145 .into_optional()
146 .map_err(SchemaError::from)?
147 .is_some()
148 {
149 return Ok(PropertyType::AnyOf(SchemaAnyOf::try_from(yaml)?));
150 }
151
152 let typename = yaml.lookup("type", "string", Yaml::as_str)?;
153
154 match typename {
155 "object" => Ok(PropertyType::Object(SchemaObject::try_from(yaml)?)),
156 "string" => Ok(PropertyType::String(SchemaString::try_from(yaml)?)),
157 "integer" => Ok(PropertyType::Integer(SchemaInteger::try_from(yaml)?)),
158 "real" => Ok(PropertyType::Real(SchemaReal::try_from(yaml)?)),
159 "array" => Ok(PropertyType::Array(SchemaArray::try_from(yaml)?)),
160 "hash" => Ok(PropertyType::Hash(SchemaHash::try_from(yaml)?)),
161 "boolean" => Ok(PropertyType::Bool(SchemaBool::try_from(yaml)?)),
162 unknown_type => Err(SchemaErrorKind::UnknownType { unknown_type }.into()),
163 }
164 }
165}
166
167impl<'yaml, 'schema: 'yaml> Validate<'yaml, 'schema> for PropertyType<'schema> {
168 fn validate(
169 &self,
170 ctx: &'schema Context<'schema>,
171 yaml: &'yaml Yaml,
172 ) -> Result<(), ValidationError<'yaml>> {
173 match self {
174 PropertyType::Integer(p) => p.validate(ctx, yaml),
175 PropertyType::Real(p) => p.validate(ctx, yaml),
176 PropertyType::String(p) => p.validate(ctx, yaml),
177 PropertyType::Object(p) => p.validate(ctx, yaml),
178 PropertyType::Array(p) => p.validate(ctx, yaml),
179 PropertyType::Hash(p) => p.validate(ctx, yaml),
180 PropertyType::Reference(p) => p.validate(ctx, yaml),
181 PropertyType::Not(p) => p.validate(ctx, yaml),
182 PropertyType::OneOf(p) => p.validate(ctx, yaml),
183 PropertyType::AllOf(p) => p.validate(ctx, yaml),
184 PropertyType::AnyOf(p) => p.validate(ctx, yaml),
185 PropertyType::Bool(p) => p.validate(ctx, yaml),
186 }
187 }
188}
189
190#[derive(Debug)]
192pub struct Schema<'schema> {
193 uri: &'schema str,
194 schema: PropertyType<'schema>,
195}
196
197impl<'schema> TryFrom<&'schema Yaml> for Schema<'schema> {
198 type Error = SchemaError<'schema>;
199 fn try_from(yaml: &'schema Yaml) -> Result<Self, Self::Error> {
200 yaml.strict_contents(&["uri", "schema"], &[])?;
201
202 let uri = yaml.lookup("uri", "string", Yaml::as_str)?;
203 let schema = PropertyType::try_from(yaml.lookup("schema", "yaml", Option::from)?)
204 .map_err(SchemaError::add_path_name(uri))?;
205
206 Ok(Schema { uri, schema })
207 }
208}
209
210impl<'yaml, 'schema: 'yaml> Validate<'yaml, 'schema> for Schema<'schema> {
211 fn validate(
212 &self,
213 ctx: &'schema Context<'schema>,
214 yaml: &'yaml Yaml,
215 ) -> Result<(), ValidationError<'yaml>> {
216 self.schema.validate(ctx, yaml)
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223 use crate::utils::load_simple;
224 use crate::Context;
225 use yaml_rust::YamlLoader;
226
227 #[test]
228 fn from_yaml() {
229 let yaml = YamlLoader::load_from_str(
230 r#"---
231uri: test
232schema:
233 type: integer
234---
235uri: another
236schema:
237 $ref: test
238"#,
239 )
240 .unwrap();
241
242 let context = Context::try_from(&yaml[..]).unwrap();
243 let schema = context.get_schema("another").unwrap();
244 dbg!(&context);
245 dbg!(&schema);
246 schema.validate(&context, &load_simple("20")).unwrap();
247 }
248}