use serde_json::Value;
use crate::error::{value_type_name, IssueCode, PathSegment, VldError};
use crate::schema::VldSchema;
pub struct ZArray<T: VldSchema> {
element: T,
min_len: Option<usize>,
max_len: Option<usize>,
exact_len: Option<usize>,
contains: Option<serde_json::Value>,
min_contains: Option<usize>,
max_contains: Option<usize>,
unique: bool,
}
impl<T: VldSchema> ZArray<T> {
pub fn new(element: T) -> Self {
Self {
element,
min_len: None,
max_len: None,
exact_len: None,
contains: None,
min_contains: None,
max_contains: None,
unique: false,
}
}
pub fn min_len(mut self, len: usize) -> Self {
self.min_len = Some(len);
self
}
pub fn max_len(mut self, len: usize) -> Self {
self.max_len = Some(len);
self
}
pub fn len(mut self, len: usize) -> Self {
self.exact_len = Some(len);
self
}
pub fn non_empty(self) -> Self {
self.min_len(1)
}
pub fn contains(mut self, value: impl Into<serde_json::Value>) -> Self {
self.contains = Some(value.into());
self
}
pub fn min_contains(mut self, n: usize) -> Self {
self.min_contains = Some(n);
self
}
pub fn max_contains(mut self, n: usize) -> Self {
self.max_contains = Some(n);
self
}
pub fn unique(mut self) -> Self {
self.unique = true;
self
}
#[allow(dead_code)]
pub(crate) fn element_schema(&self) -> &T {
&self.element
}
#[cfg(feature = "openapi")]
pub fn to_json_schema_inner(&self) -> serde_json::Value
where
T: crate::json_schema::JsonSchema,
{
let mut schema = serde_json::json!({
"type": "array",
"items": self.element.json_schema(),
});
if let Some(min) = self.min_len {
schema["minItems"] = serde_json::json!(min);
}
if let Some(max) = self.max_len {
schema["maxItems"] = serde_json::json!(max);
}
if let Some(exact) = self.exact_len {
schema["minItems"] = serde_json::json!(exact);
schema["maxItems"] = serde_json::json!(exact);
}
if let Some(ref contains) = self.contains {
schema["contains"] = contains.clone();
}
if let Some(min_contains) = self.min_contains {
schema["minContains"] = serde_json::json!(min_contains);
}
if let Some(max_contains) = self.max_contains {
schema["maxContains"] = serde_json::json!(max_contains);
}
if self.unique {
schema["uniqueItems"] = serde_json::json!(true);
}
schema
}
}
impl<T: VldSchema> VldSchema for ZArray<T> {
type Output = Vec<T::Output>;
fn parse_value(&self, value: &Value) -> Result<Vec<T::Output>, VldError> {
let arr = value.as_array().ok_or_else(|| {
VldError::single(
IssueCode::InvalidType {
expected: "array".to_string(),
received: value_type_name(value),
},
format!("Expected array, received {}", value_type_name(value)),
)
})?;
let mut errors = VldError::new();
if let Some(min) = self.min_len {
if arr.len() < min {
errors.push(
IssueCode::TooSmall {
minimum: min as f64,
inclusive: true,
},
format!("Array must have at least {} elements", min),
);
}
}
if let Some(max) = self.max_len {
if arr.len() > max {
errors.push(
IssueCode::TooBig {
maximum: max as f64,
inclusive: true,
},
format!("Array must have at most {} elements", max),
);
}
}
if let Some(exact) = self.exact_len {
if arr.len() != exact {
errors.push(
IssueCode::Custom {
code: "invalid_length".to_string(),
},
format!("Array must have exactly {} elements", exact),
);
}
}
if self.unique {
for i in 0..arr.len() {
for j in (i + 1)..arr.len() {
if arr[i] == arr[j] {
errors.push(
IssueCode::Custom {
code: "not_unique".to_string(),
},
"Array items must be unique",
);
break;
}
}
}
}
if let Some(ref contains) = self.contains {
let count = arr.iter().filter(|v| *v == contains).count();
if count == 0 {
errors.push(
IssueCode::Custom {
code: "missing_contains".to_string(),
},
"Array must contain required value",
);
}
if let Some(min_contains) = self.min_contains {
if count < min_contains {
errors.push(
IssueCode::TooSmall {
minimum: min_contains as f64,
inclusive: true,
},
format!(
"Array must contain required value at least {} time(s)",
min_contains
),
);
}
}
if let Some(max_contains) = self.max_contains {
if count > max_contains {
errors.push(
IssueCode::TooBig {
maximum: max_contains as f64,
inclusive: true,
},
format!(
"Array must contain required value at most {} time(s)",
max_contains
),
);
}
}
}
let mut results = Vec::with_capacity(arr.len());
for (i, item) in arr.iter().enumerate() {
match self.element.parse_value(item) {
Ok(v) => results.push(v),
Err(e) => {
errors = errors.merge(e.with_prefix(PathSegment::Index(i)));
}
}
}
if errors.is_empty() {
Ok(results)
} else {
Err(errors)
}
}
}