1use smallvec;
2use smallvec::SmallVec;
3use std;
4use std::borrow::Cow;
5use yaml_rust;
6use yaml_rust::yaml;
7use yaml_rust::Yaml;
8
9error_chain! {
10 foreign_links {
11 YamlEmit(yaml_rust::EmitError);
12 YamlScan(yaml_rust::ScanError);
13 }
14
15 errors {
16 NoSingleNode(node_qty: usize) {
17 description("wanted a single YAML node but found zero or multiple nodes")
18 display("While parsing YAML: Wanted a single node, but found {} nodes.", node_qty)
19 }
20 RequiredFieldMissing(name: Cow<'static, str>) {
21 description("a YAML object is missing a required field")
22 display("While handling YAML: An object is missing the required field {:?}.", name)
23 }
24 AliasesNotSupported {
25 description("encountered a YAML alias (which is not supported by `yaml_rust`)")
26 display("While handling YAML: Encountered a YAML alias, which is not supported by \
27 `yaml_rust`.")
28 }
29 TypeMismatch(path: String, expected_ty: Kind, actual_ty: Kind) {
30 description("encountered a type error while handling YAML")
31 display("While handling YAML: Expected {path} to be of type {expected_ty:?}, but it \
32 is of type {actual_ty:?}.",
33 path = path,
34 expected_ty = expected_ty,
35 actual_ty = actual_ty)
36 }
37 ExpectedNonEmptyStream {
38 description("expected non-empty YAML stream but found empty stream")
39 display("While handling YAML: Expected a non-empty stream, but found an empty stream.")
40 }
41 ExpectedEmptyStream {
42 description("expected empty YAML stream but found non-empty stream")
43 display("While handling YAML: Expected an empty stream, but found a non-empty stream.")
44 }
45 }
46}
47
48#[derive(Copy, Clone, Debug)]
49pub enum Kind {
50 Scalar,
51 Sequence,
52 Mapping,
53 #[doc(hidden)]
54 __Nonexhaustive,
55}
56
57impl Kind {
58 pub fn of(node: &Yaml) -> Kind {
59 Self::from_aug_ty(&AugmentedTy::of(node))
60 }
61
62 fn from_aug_ty(ty: &AugmentedTy) -> Kind {
63 match ty {
64 &AugmentedTy::Scalar => Kind::Scalar,
65 &AugmentedTy::Sequence => Kind::Sequence,
66 &AugmentedTy::Mapping(_) => Kind::Mapping,
67 &AugmentedTy::Other => Kind::__Nonexhaustive,
68 }
69 }
70}
71
72#[derive(Debug)]
73pub(crate) enum AugmentedTy<'a> {
74 Scalar,
75 Sequence,
76 Mapping(&'a yaml::Hash),
77 Other,
78}
79
80impl<'a> AugmentedTy<'a> {
81 pub(crate) fn of(node: &Yaml) -> AugmentedTy {
82 match node {
83 &Yaml::Real(_)
84 | &Yaml::Integer(_)
85 | &Yaml::String(_)
86 | &Yaml::Boolean(_)
87 | &Yaml::Null => AugmentedTy::Scalar,
88 &Yaml::Array(_) => AugmentedTy::Sequence,
89 &Yaml::Hash(ref data) => AugmentedTy::Mapping(data),
90 &Yaml::Alias(_) | &Yaml::BadValue => AugmentedTy::Other,
91 }
92 }
93}
94
95pub fn any_to_str<'a, 'b, F>(node: &'a Yaml, lt_map: F) -> Cow<'b, str>
101where
102 F: Fn(&'a str) -> Cow<'b, str>,
103{
104 node.as_str()
105 .map(lt_map)
106 .unwrap_or_else(|| Cow::Owned(format!("{:?}", node)))
107}
108
109pub fn scalar_to_str<'a, 'b, F>(
115 node: &'a Yaml,
116 lt_map: F,
117) -> std::result::Result<Cow<'b, str>, Kind>
118where
119 F: Fn(&'a str) -> Cow<'b, str>,
120{
121 match Kind::of(node) {
122 Kind::Scalar => Ok(any_to_str(node, lt_map)),
123 kind => Err(kind),
124 }
125}
126
127pub fn parse_node(src: &str) -> Result<Option<Yaml>> {
134 let mut stream = yaml::YamlLoader::load_from_str(src)?;
135
136 let node = stream.pop();
137
138 match stream.len() {
139 0 => Ok(node),
140 n => {
141 bail!(ErrorKind::NoSingleNode({
142 n + 1
145 }))
146 }
147 }
148}
149
150pub(crate) fn parse_and_check_node<'s, DefaultCtor, S1>(
151 src: &str,
152 expected_syntax: &'s Yaml,
153 subject_label: S1,
154 default: DefaultCtor,
155) -> Result<Yaml>
156where
157 DefaultCtor: Fn() -> Yaml,
158 S1: Into<Cow<'s, str>>,
159{
160 let node = parse_node(src)?.unwrap_or_else(default);
161
162 check_type(expected_syntax, &node, subject_label)?;
163
164 Ok(node)
165}
166
167pub(crate) fn check_type<'s, S1>(expected: &'s Yaml, actual: &Yaml, subject_label: S1) -> Result<()>
175where
176 S1: Into<Cow<'s, str>>,
177{
178 let subject_label = subject_label.into();
179
180 let mut path_buf = SmallVec::<[_; 8]>::new();
181
182 check_type_inner(expected, actual, &mut path_buf, subject_label)?;
183
184 debug_assert!(path_buf.is_empty());
185
186 Ok(())
187}
188
189fn check_type_inner<'s, AS>(
190 expected: &'s Yaml,
191 actual: &Yaml,
192 path_buf: &mut SmallVec<AS>,
193 subject_label: Cow<'s, str>,
194) -> Result<()>
195where
196 AS: smallvec::Array<Item = Cow<'s, str>>,
197{
198 trace!(
199 "Checking YAML object's type and structure. Expected: {expected:?}; actual: {actual:?}.",
200 expected = expected,
201 actual = actual
202 );
203
204 use util::yaml::AugmentedTy as Ty;
205
206 path_buf.push(subject_label);
207
208 let expected_ty = Ty::of(expected);
209 let actual_ty = Ty::of(actual);
210
211 match (&expected_ty, &actual_ty) {
212 (&Ty::Scalar, &Ty::Scalar) | (&Ty::Sequence, &Ty::Sequence) => {
213 }
215 (&Ty::Mapping(expected_fields), &Ty::Mapping(actual_fields)) => {
216 check_field_types(expected_fields, actual_fields, path_buf)?
217 }
218 (&Ty::Scalar, &Ty::Sequence)
219 | (&Ty::Scalar, &Ty::Mapping(_))
220 | (&Ty::Sequence, &Ty::Scalar)
221 | (&Ty::Sequence, &Ty::Mapping(_))
222 | (&Ty::Mapping(_), &Ty::Scalar)
223 | (&Ty::Mapping(_), &Ty::Sequence) => bail!(ErrorKind::TypeMismatch(
224 path_buf.join("."),
225 Kind::from_aug_ty(&expected_ty),
226 Kind::from_aug_ty(&actual_ty),
227 )),
228 (_, &Ty::Other) | (&Ty::Other, _) => bail!(ErrorKind::AliasesNotSupported),
229 }
230
231 path_buf.pop();
232
233 Ok(())
234}
235
236fn check_field_types<'s, AS>(
237 expected_fields: &'s yaml::Hash,
238 actual_fields: &yaml::Hash,
239 path_buf: &mut SmallVec<AS>,
240) -> Result<()>
241where
242 AS: smallvec::Array<Item = Cow<'s, str>>,
243{
244 for (key, expected_value) in expected_fields {
245 match (expected_value, actual_fields.get(key)) {
246 (_, Some(actual_value)) => check_type_inner(
247 expected_value,
248 actual_value,
249 path_buf,
250 any_to_str(key, Cow::Borrowed),
251 )?,
252 (&Yaml::String(ref s), None) if s.starts_with("[") && s.ends_with("]") => {
253 }
255 (&Yaml::Array(_), None) => {
256 }
258 (&Yaml::Hash(_), None) => {
259 check_type_inner(
261 expected_value,
262 &Yaml::Hash(Default::default()),
263 path_buf,
264 any_to_str(key, Cow::Borrowed),
265 )?
266 }
267 (_, None) => bail!(ErrorKind::RequiredFieldMissing(any_to_str(
268 key,
269 |s| s.to_owned().into()
270 ),)),
271 }
272 }
273
274 Ok(())
275}