use alloc::boxed::Box;
use alloc::vec::Vec;
use serde_json::Value;
use crate::error::{ErrorIterator, ValidationError, ValidationErrorBuilder, ValidationErrorKind};
use crate::paths::{LazyLocation, Location};
use super::{Validate, ValidationContext};
pub struct EnumValidator {
options: Vec<Value>,
schema_path: Location,
}
impl EnumValidator {
pub fn new(options: Vec<Value>, schema_path: Location) -> Self {
Self {
options,
schema_path,
}
}
}
impl Validate for EnumValidator {
fn is_valid(&self, instance: &Value, _ctx: &mut ValidationContext) -> bool {
self.options.iter().any(|opt| values_equal(instance, opt))
}
fn validate(
&self,
instance: &Value,
instance_path: &LazyLocation<'_>,
ctx: &mut ValidationContext,
) -> Result<(), ValidationError> {
if self.is_valid(instance, ctx) {
Ok(())
} else {
Err(
ValidationErrorBuilder::new(instance_path.materialize(), self.schema_path.clone())
.build(ValidationErrorKind::InvalidEnum {
options: self.options.clone(),
}),
)
}
}
fn iter_errors(
&self,
instance: &Value,
instance_path: &LazyLocation<'_>,
ctx: &mut ValidationContext,
) -> ErrorIterator {
match self.validate(instance, instance_path, ctx) {
Ok(()) => Box::new(core::iter::empty()),
Err(e) => Box::new(core::iter::once(e)),
}
}
}
fn values_equal(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Number(a), Value::Number(b)) => numbers_equal(a, b),
(Value::Array(a), Value::Array(b)) => {
a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y))
}
(Value::Object(a), Value::Object(b)) => {
a.len() == b.len()
&& a.iter()
.all(|(k, v)| b.get(k).is_some_and(|bv| values_equal(v, bv)))
}
_ => a == b,
}
}
fn numbers_equal(a: &serde_json::Number, b: &serde_json::Number) -> bool {
if a.as_i64().is_some() && b.as_i64().is_some() {
return a.as_i64() == b.as_i64();
}
if a.as_u64().is_some() && b.as_u64().is_some() {
return a.as_u64() == b.as_u64();
}
if a.as_f64().is_some()
&& b.as_f64().is_some()
&& a.as_i64().is_none()
&& a.as_u64().is_none()
&& b.as_i64().is_none()
&& b.as_u64().is_none()
{
return a.as_f64() == b.as_f64();
}
a.as_f64() == b.as_f64()
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn ctx() -> ValidationContext {
ValidationContext::new()
}
#[test]
fn matches() {
let v = EnumValidator::new(vec![json!(1), json!("a"), json!(null)], Location::new());
assert!(v.is_valid(&json!(1), &mut ctx()));
assert!(v.is_valid(&json!("a"), &mut ctx()));
assert!(v.is_valid(&json!(null), &mut ctx()));
}
#[test]
fn no_match() {
let v = EnumValidator::new(vec![json!(1), json!(2)], Location::new());
assert!(!v.is_valid(&json!(3), &mut ctx()));
}
#[test]
fn number_coercion() {
let v = EnumValidator::new(vec![json!(1)], Location::new());
assert!(v.is_valid(&json!(1.0), &mut ctx()));
}
}