use crate::error::{Error, ErrorKind};
use crate::{Meta, Validator};
use tanzim_value::{Value, ValueType};
#[derive(Default)]
pub struct List {
meta: Meta,
min_len: Option<usize>,
max_len: Option<usize>,
unique: bool,
items: Option<Box<dyn Validator>>,
}
impl List {
pub fn with_meta(mut self, meta: Meta) -> Self {
self.meta = meta;
self
}
pub fn new() -> Self {
Self::default()
}
pub fn min_len(mut self, min: usize) -> Self {
self.min_len = Some(min);
self
}
pub fn max_len(mut self, max: usize) -> Self {
self.max_len = Some(max);
self
}
pub fn unique(mut self) -> Self {
self.unique = true;
self
}
pub fn items(mut self, validator: impl Into<Box<dyn Validator>>) -> Self {
self.items = Some(validator.into());
self
}
}
crate::impl_meta_methods!(List);
impl Validator for List {
fn meta(&self) -> &Meta {
&self.meta
}
fn meta_mut(&mut self) -> &mut Meta {
&mut self.meta
}
fn check(&self, value: &mut Value) -> Result<(), Error> {
match value {
Value::List(_) => {}
Value::Map(map) if map.is_empty() => *value = Value::new_list(),
_ => {
return Err(Error::new(ErrorKind::Type {
expected: ValueType::List,
found: value.type_name(),
}));
}
}
let items = match value.list_mut() {
Some(items) => items,
None => unreachable!("value coerced to a list above"),
};
let length = items.len();
if let Some(min) = self.min_len
&& length < min
{
return Err(Error::new(ErrorKind::TooShort { len: length, min }));
}
if let Some(max) = self.max_len
&& length > max
{
return Err(Error::new(ErrorKind::TooLong { len: length, max }));
}
if let Some(validator) = &self.items {
for (index, item) in items.iter_mut().enumerate() {
match validator.validate(item.value_mut()) {
Ok(()) => {}
Err(error) => return Err(error.under_index(index, item.location())),
}
}
}
if self.unique {
let mut seen: Vec<&Value> = Vec::new();
for (index, item) in items.iter().enumerate() {
for previous in &seen {
if **previous == *item.value() {
return Err(Error::new(ErrorKind::Duplicate { index })
.with_location(item.location()));
}
}
seen.push(item.value());
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Integer;
use tanzim_value::{LocatedValue, Location};
fn item(value: Value) -> LocatedValue {
LocatedValue::new(value, Location::at("file", "test", Some(1), Some(1), None))
}
#[test]
fn empty_map_becomes_empty_list() {
let mut value = Value::new_map();
List::new().validate(&mut value).unwrap();
assert_eq!(value, Value::new_list());
}
#[test]
fn enforces_length_bounds() {
let mut value = Value::List(vec![item(Value::Int(1))]);
let error = List::new().min_len(2).validate(&mut value).unwrap_err();
assert!(matches!(error.kind, ErrorKind::TooShort { .. }));
}
#[test]
fn detects_duplicates() {
let mut value = Value::List(vec![item(Value::Int(1)), item(Value::Int(1))]);
let error = List::new().unique().validate(&mut value).unwrap_err();
assert!(matches!(error.kind, ErrorKind::Duplicate { index: 1 }));
}
#[test]
fn item_validator_reports_index_path() {
let mut value = Value::List(vec![item(Value::Int(1)), item(Value::String("x".into()))]);
let error = List::new()
.items(Integer::new())
.validate(&mut value)
.unwrap_err();
assert_eq!(error.path.len(), 1);
assert!(matches!(error.kind, ErrorKind::NotConvertible { .. }));
}
#[test]
fn item_coercion_persists() {
let mut value = Value::List(vec![item(Value::String("5".into()))]);
List::new()
.items(Integer::new())
.validate(&mut value)
.unwrap();
assert_eq!(*value.as_list().unwrap()[0].value(), Value::Int(5));
}
}