use crate::error::{Error, ReasonCode, Result};
use crate::value::Value;
use super::helpers::{choose_multiline_form, string_needs_multiline};
pub(crate) fn check_representable(value: &Value) -> Result<()> {
match value {
Value::Object(_) | Value::Array(_) => check_node(value),
_ => Err(Error::Unrepresentable(ReasonCode::ScalarRoot)),
}
}
fn check_node(value: &Value) -> Result<()> {
match value {
Value::Object(pairs) => {
for (k, v) in pairs {
if k.is_empty() {
return Err(Error::Unrepresentable(ReasonCode::EmptyKeyName));
}
check_node(v)?;
}
Ok(())
}
Value::Array(items) => {
for item in items {
check_node(item)?;
}
Ok(())
}
Value::Float(s) => match s.parse::<f64>() {
Ok(v) if v.is_nan() || v.is_infinite() => {
Err(Error::Unrepresentable(ReasonCode::NonFiniteFloat))
}
_ => Ok(()),
},
Value::String(s) => {
if s.contains('\r') {
return Err(Error::Unrepresentable(ReasonCode::CRByte));
}
if string_needs_multiline(s) {
choose_multiline_form(s, false).map(|_| ())
} else {
Ok(())
}
}
Value::Null | Value::Bool(_) | Value::Integer(_) => Ok(()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::ReasonCode;
use crate::value::{ObjectMap, Scalar};
fn obj(pairs: &[(&str, Value)]) -> Value {
let mut map = ObjectMap::default();
for (k, v) in pairs {
map.insert(Scalar::from(*k), v.clone());
}
Value::Object(map)
}
fn check_code(value: &Value) -> ReasonCode {
check_representable(value)
.unwrap_err()
.reason_code()
.unwrap()
}
#[test]
fn choose_multiline_form_direct() {
use super::super::helpers::choose_multiline_form;
use crate::render::helpers::MultilineForm;
for prefer in [false, true] {
assert!(matches!(
choose_multiline_form("a\nb", prefer),
Ok(MultilineForm::Verbatim | MultilineForm::Stripped)
));
assert!(matches!(
choose_multiline_form("a\n))", prefer),
Ok(MultilineForm::Stripped)
));
assert!(matches!(
choose_multiline_form("\talpha\n ))", prefer),
Ok(MultilineForm::Stripped)
));
assert!(matches!(
choose_multiline_form("\u{2000}alpha\n\u{2001}))", prefer),
Ok(MultilineForm::Stripped)
));
let code = choose_multiline_form("))\n)", prefer)
.unwrap_err()
.reason_code()
.unwrap();
assert_eq!(code, ReasonCode::BothFormsRequired);
let code = choose_multiline_form("))\nx ", prefer)
.unwrap_err()
.reason_code()
.unwrap();
assert_eq!(code, ReasonCode::TrailingWhitespaceCollision);
let code = choose_multiline_form(" ))\n x", prefer)
.unwrap_err()
.reason_code()
.unwrap();
assert_eq!(code, ReasonCode::LeadingWhitespaceCollision);
let code = choose_multiline_form("))\n \nx", prefer)
.unwrap_err()
.reason_code()
.unwrap();
assert_eq!(code, ReasonCode::TrailingWhitespaceCollision);
}
}
#[test]
fn scalar_root() {
assert_eq!(check_code(&Value::Null), ReasonCode::ScalarRoot);
}
#[test]
fn empty_key_name() {
let v = obj(&[("", Value::Null)]);
assert_eq!(check_code(&v), ReasonCode::EmptyKeyName);
}
#[test]
fn non_finite_floats() {
let in_obj = obj(&[("k", Value::Float("NaN".into()))]);
assert_eq!(check_code(&in_obj), ReasonCode::NonFiniteFloat);
let in_arr = Value::Array(vec![Value::Float("inf".into())]);
assert_eq!(check_code(&in_arr), ReasonCode::NonFiniteFloat);
let neg = obj(&[("k", Value::Float("-Infinity".into()))]);
assert_eq!(check_code(&neg), ReasonCode::NonFiniteFloat);
}
#[test]
fn node_check_runs_after_root_passes() {
let v = obj(&[("k", Value::Float("NaN".into()))]);
assert_eq!(check_code(&v), ReasonCode::NonFiniteFloat);
}
#[test]
fn finite_floats_ok() {
assert!(check_representable(&obj(&[("a", Value::Float("1.5".into()))])).is_ok());
assert!(check_representable(&obj(&[("a", Value::Float("-0.0".into()))])).is_ok());
}
#[test]
fn cr_byte() {
let in_obj = obj(&[("k", Value::String("a\rb".into()))]);
assert_eq!(check_code(&in_obj), ReasonCode::CRByte);
let in_arr = Value::Array(vec![Value::String("a\rb".into())]);
assert_eq!(check_code(&in_arr), ReasonCode::CRByte);
}
#[test]
fn empty_roots_ok() {
assert!(check_representable(&Value::Object(ObjectMap::default())).is_ok());
assert!(check_representable(&Value::Array(Vec::new())).is_ok());
}
}