use smallvec;
use smallvec::SmallVec;
use std;
use std::borrow::Cow;
use yaml_rust;
use yaml_rust::yaml;
use yaml_rust::Yaml;
error_chain! {
foreign_links {
YamlEmit(yaml_rust::EmitError);
YamlScan(yaml_rust::ScanError);
}
errors {
NoSingleNode(node_qty: usize) {
description("wanted a single YAML node but found zero or multiple nodes")
display("While parsing YAML: Wanted a single node, but found {} nodes.", node_qty)
}
RequiredFieldMissing(name: Cow<'static, str>) {
description("a YAML object is missing a required field")
display("While handling YAML: An object is missing the required field {:?}.", name)
}
AliasesNotSupported {
description("encountered a YAML alias (which is not supported by `yaml_rust`)")
display("While handling YAML: Encountered a YAML alias, which is not supported by \
`yaml_rust`.")
}
TypeMismatch(path: String, expected_ty: Kind, actual_ty: Kind) {
description("encountered a type error while handling YAML")
display("While handling YAML: Expected {path} to be of type {expected_ty:?}, but it \
is of type {actual_ty:?}.",
path = path,
expected_ty = expected_ty,
actual_ty = actual_ty)
}
ExpectedNonEmptyStream {
description("expected non-empty YAML stream but found empty stream")
display("While handling YAML: Expected a non-empty stream, but found an empty stream.")
}
ExpectedEmptyStream {
description("expected empty YAML stream but found non-empty stream")
display("While handling YAML: Expected an empty stream, but found a non-empty stream.")
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum Kind {
Scalar,
Sequence,
Mapping,
#[doc(hidden)]
__Nonexhaustive,
}
impl Kind {
pub fn of(node: &Yaml) -> Kind {
Self::from_aug_ty(&AugmentedTy::of(node))
}
fn from_aug_ty(ty: &AugmentedTy) -> Kind {
match ty {
&AugmentedTy::Scalar => Kind::Scalar,
&AugmentedTy::Sequence => Kind::Sequence,
&AugmentedTy::Mapping(_) => Kind::Mapping,
&AugmentedTy::Other => Kind::__Nonexhaustive,
}
}
}
#[derive(Debug)]
pub(crate) enum AugmentedTy<'a> {
Scalar,
Sequence,
Mapping(&'a yaml::Hash),
Other,
}
impl<'a> AugmentedTy<'a> {
pub(crate) fn of(node: &Yaml) -> AugmentedTy {
match node {
&Yaml::Real(_)
| &Yaml::Integer(_)
| &Yaml::String(_)
| &Yaml::Boolean(_)
| &Yaml::Null => AugmentedTy::Scalar,
&Yaml::Array(_) => AugmentedTy::Sequence,
&Yaml::Hash(ref data) => AugmentedTy::Mapping(data),
&Yaml::Alias(_) | &Yaml::BadValue => AugmentedTy::Other,
}
}
}
pub fn any_to_str<'a, 'b, F>(node: &'a Yaml, lt_map: F) -> Cow<'b, str>
where
F: Fn(&'a str) -> Cow<'b, str>,
{
node.as_str()
.map(lt_map)
.unwrap_or_else(|| Cow::Owned(format!("{:?}", node)))
}
pub fn scalar_to_str<'a, 'b, F>(
node: &'a Yaml,
lt_map: F,
) -> std::result::Result<Cow<'b, str>, Kind>
where
F: Fn(&'a str) -> Cow<'b, str>,
{
match Kind::of(node) {
Kind::Scalar => Ok(any_to_str(node, lt_map)),
kind => Err(kind),
}
}
pub fn parse_node(src: &str) -> Result<Option<Yaml>> {
let mut stream = yaml::YamlLoader::load_from_str(src)?;
let node = stream.pop();
match stream.len() {
0 => Ok(node),
n => {
bail!(ErrorKind::NoSingleNode({
n + 1
}))
}
}
}
pub(crate) fn parse_and_check_node<'s, DefaultCtor, S1>(
src: &str,
expected_syntax: &'s Yaml,
subject_label: S1,
default: DefaultCtor,
) -> Result<Yaml>
where
DefaultCtor: Fn() -> Yaml,
S1: Into<Cow<'s, str>>,
{
let node = parse_node(src)?.unwrap_or_else(default);
check_type(expected_syntax, &node, subject_label)?;
Ok(node)
}
pub(crate) fn check_type<'s, S1>(expected: &'s Yaml, actual: &Yaml, subject_label: S1) -> Result<()>
where
S1: Into<Cow<'s, str>>,
{
let subject_label = subject_label.into();
let mut path_buf = SmallVec::<[_; 8]>::new();
check_type_inner(expected, actual, &mut path_buf, subject_label)?;
debug_assert!(path_buf.is_empty());
Ok(())
}
fn check_type_inner<'s, AS>(
expected: &'s Yaml,
actual: &Yaml,
path_buf: &mut SmallVec<AS>,
subject_label: Cow<'s, str>,
) -> Result<()>
where
AS: smallvec::Array<Item = Cow<'s, str>>,
{
trace!(
"Checking YAML object's type and structure. Expected: {expected:?}; actual: {actual:?}.",
expected = expected,
actual = actual
);
use util::yaml::AugmentedTy as Ty;
path_buf.push(subject_label);
let expected_ty = Ty::of(expected);
let actual_ty = Ty::of(actual);
match (&expected_ty, &actual_ty) {
(&Ty::Scalar, &Ty::Scalar) | (&Ty::Sequence, &Ty::Sequence) => {
}
(&Ty::Mapping(expected_fields), &Ty::Mapping(actual_fields)) => {
check_field_types(expected_fields, actual_fields, path_buf)?
}
(&Ty::Scalar, &Ty::Sequence)
| (&Ty::Scalar, &Ty::Mapping(_))
| (&Ty::Sequence, &Ty::Scalar)
| (&Ty::Sequence, &Ty::Mapping(_))
| (&Ty::Mapping(_), &Ty::Scalar)
| (&Ty::Mapping(_), &Ty::Sequence) => bail!(ErrorKind::TypeMismatch(
path_buf.join("."),
Kind::from_aug_ty(&expected_ty),
Kind::from_aug_ty(&actual_ty),
)),
(_, &Ty::Other) | (&Ty::Other, _) => bail!(ErrorKind::AliasesNotSupported),
}
path_buf.pop();
Ok(())
}
fn check_field_types<'s, AS>(
expected_fields: &'s yaml::Hash,
actual_fields: &yaml::Hash,
path_buf: &mut SmallVec<AS>,
) -> Result<()>
where
AS: smallvec::Array<Item = Cow<'s, str>>,
{
for (key, expected_value) in expected_fields {
match (expected_value, actual_fields.get(key)) {
(_, Some(actual_value)) => check_type_inner(
expected_value,
actual_value,
path_buf,
any_to_str(key, Cow::Borrowed),
)?,
(&Yaml::String(ref s), None) if s.starts_with("[") && s.ends_with("]") => {
}
(&Yaml::Array(_), None) => {
}
(&Yaml::Hash(_), None) => {
check_type_inner(
expected_value,
&Yaml::Hash(Default::default()),
path_buf,
any_to_str(key, Cow::Borrowed),
)?
}
(_, None) => bail!(ErrorKind::RequiredFieldMissing(any_to_str(
key,
|s| s.to_owned().into()
),)),
}
}
Ok(())
}