use crate::error::{Error, Result};
use crate::prelude::*;
use crate::value::{Number, Value};
pub fn validate_yaml_failsafe_schema(value: &Value) -> Result<()> {
validate_failsafe_recursive(value, &mut String::from("root"))
}
fn validate_failsafe_recursive(value: &Value, path: &mut String) -> Result<()> {
use core::fmt::Write;
match value {
Value::String(_) => Ok(()),
Value::Sequence(seq) => {
let base_len = path.len();
for (i, item) in seq.iter().enumerate() {
let _ = write!(path, "[{i}]");
validate_failsafe_recursive(item, path)?;
path.truncate(base_len);
}
Ok(())
}
Value::Mapping(map) => {
let base_len = path.len();
for (key, val) in map.iter() {
let _ = write!(path, ".{key}");
validate_failsafe_recursive(val, path)?;
path.truncate(base_len);
}
Ok(())
}
Value::Null => Err(Error::Invalid(format!(
"failsafe schema: null not allowed at {path}"
))),
Value::Bool(_) => Err(Error::Invalid(format!(
"failsafe schema: boolean not allowed at {path}"
))),
Value::Number(_) => Err(Error::Invalid(format!(
"failsafe schema: number not allowed at {path}"
))),
Value::Tagged(_) => Err(Error::Invalid(format!(
"failsafe schema: tagged values not allowed at {path}"
))),
}
}
pub fn validate_yaml_json_schema(value: &Value) -> Result<()> {
validate_json_recursive(value, &mut String::from("root"))
}
fn validate_json_recursive(value: &Value, path: &mut String) -> Result<()> {
use core::fmt::Write;
match value {
Value::Null | Value::Bool(_) | Value::String(_) => Ok(()),
Value::Number(n) => {
if let Number::Float(f) = n {
if f.is_nan() || f.is_infinite() {
return Err(Error::Invalid(format!(
"JSON schema: NaN/Infinity not allowed at {path}"
)));
}
}
Ok(())
}
Value::Sequence(seq) => {
let base_len = path.len();
for (i, item) in seq.iter().enumerate() {
let _ = write!(path, "[{i}]");
validate_json_recursive(item, path)?;
path.truncate(base_len);
}
Ok(())
}
Value::Mapping(map) => {
let base_len = path.len();
for (key, val) in map.iter() {
let _ = write!(path, ".{key}");
validate_json_recursive(val, path)?;
path.truncate(base_len);
}
Ok(())
}
Value::Tagged(_) => Err(Error::Invalid(format!(
"JSON schema: tagged values not allowed at {path}"
))),
}
}
pub fn validate_yaml_core_schema(value: &Value) -> Result<()> {
validate_core_recursive(value, &mut String::from("root"))
}
fn validate_core_recursive(value: &Value, path: &mut String) -> Result<()> {
use core::fmt::Write;
match value {
Value::Null | Value::Bool(_) | Value::String(_) | Value::Number(_) => Ok(()),
Value::Sequence(seq) => {
let base_len = path.len();
for (i, item) in seq.iter().enumerate() {
let _ = write!(path, "[{i}]");
validate_core_recursive(item, path)?;
path.truncate(base_len);
}
Ok(())
}
Value::Mapping(map) => {
let base_len = path.len();
for (key, val) in map.iter() {
let _ = write!(path, ".{key}");
validate_core_recursive(val, path)?;
path.truncate(base_len);
}
Ok(())
}
Value::Tagged(tagged) => {
let base_len = path.len();
let _ = write!(path, "!{}", tagged.tag());
let result = validate_core_recursive(tagged.value(), path);
path.truncate(base_len);
result
}
}
}
#[must_use]
pub fn is_yaml_json_compatible(value: &Value) -> bool {
validate_yaml_json_schema(value).is_ok()
}
#[must_use]
pub fn is_yaml_failsafe_compatible(value: &Value) -> bool {
validate_yaml_failsafe_schema(value).is_ok()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::Mapping;
#[test]
fn test_failsafe_valid() {
assert!(validate_yaml_failsafe_schema(&Value::String("test".to_string())).is_ok());
assert!(validate_yaml_failsafe_schema(&Value::Sequence(vec![])).is_ok());
assert!(validate_yaml_failsafe_schema(&Value::Mapping(Mapping::new())).is_ok());
}
#[test]
fn test_failsafe_invalid() {
assert!(validate_yaml_failsafe_schema(&Value::Null).is_err());
assert!(validate_yaml_failsafe_schema(&Value::Bool(true)).is_err());
assert!(validate_yaml_failsafe_schema(&Value::from(42)).is_err());
}
#[test]
fn test_json_valid() {
assert!(validate_yaml_json_schema(&Value::Null).is_ok());
assert!(validate_yaml_json_schema(&Value::Bool(true)).is_ok());
assert!(validate_yaml_json_schema(&Value::from(42)).is_ok());
assert!(validate_yaml_json_schema(&Value::from(3.125)).is_ok());
assert!(validate_yaml_json_schema(&Value::String("test".to_string())).is_ok());
}
#[test]
fn test_json_invalid_nan() {
let nan = Value::from(f64::NAN);
assert!(validate_yaml_json_schema(&nan).is_err());
}
#[test]
fn test_json_invalid_infinity() {
let inf = Value::from(f64::INFINITY);
assert!(validate_yaml_json_schema(&inf).is_err());
}
#[test]
fn test_core_accepts_all() {
assert!(validate_yaml_core_schema(&Value::Null).is_ok());
assert!(validate_yaml_core_schema(&Value::Bool(true)).is_ok());
assert!(validate_yaml_core_schema(&Value::from(42)).is_ok());
assert!(validate_yaml_core_schema(&Value::from(f64::NAN)).is_ok());
assert!(validate_yaml_core_schema(&Value::from(f64::INFINITY)).is_ok());
}
#[test]
fn test_is_yaml_json_compatible() {
assert!(is_yaml_json_compatible(&Value::from(42)));
assert!(!is_yaml_json_compatible(&Value::from(f64::NAN)));
}
#[test]
fn test_is_yaml_failsafe_compatible() {
assert!(is_yaml_failsafe_compatible(&Value::String(
"test".to_string()
)));
assert!(!is_yaml_failsafe_compatible(&Value::from(42)));
}
}