1use crate::schema::Schema;
2use serde_json::Value;
3use std::fmt::Debug;
4use zod_rs_util::{
5 ValidateResult, ValidationError, ValidationOrigin, ValidationResult, ValidationType,
6};
7
8#[derive(Debug, Clone)]
9pub struct ArraySchema<S, T> {
10 element_schema: S,
11 min_length: Option<usize>,
12 max_length: Option<usize>,
13 _phantom: std::marker::PhantomData<T>,
14}
15
16impl<S, T> ArraySchema<S, T> {
17 pub fn new(element_schema: S) -> Self {
18 Self {
19 element_schema,
20 min_length: None,
21 max_length: None,
22 _phantom: std::marker::PhantomData,
23 }
24 }
25
26 pub fn min(mut self, min: usize) -> Self {
27 self.min_length = Some(min);
28 self
29 }
30
31 pub fn max(mut self, max: usize) -> Self {
32 self.max_length = Some(max);
33 self
34 }
35
36 pub fn length(self, len: usize) -> Self {
37 self.min(len).max(len)
38 }
39}
40
41impl<S, T> Schema<Vec<T>> for ArraySchema<S, T>
42where
43 S: Schema<T>,
44 T: Debug,
45{
46 fn validate(&self, value: &Value) -> ValidateResult<Vec<T>> {
47 let array = match value.as_array() {
48 Some(arr) => arr,
49 None => {
50 return Err(ValidationError::invalid_type(
51 ValidationType::Array,
52 ValidationType::from(value),
53 )
54 .into());
55 }
56 };
57
58 if let Some(min) = self.min_length {
59 if array.len() < min {
60 return Err(ValidationError::too_small(
61 ValidationOrigin::Array,
62 min.to_string(),
63 true,
64 )
65 .into());
66 }
67 }
68
69 if let Some(max) = self.max_length {
70 if array.len() > max {
71 return Err(ValidationError::too_big(
72 ValidationOrigin::Array,
73 max.to_string(),
74 true,
75 )
76 .into());
77 }
78 }
79
80 let mut results = Vec::new();
81 let mut validation_result = ValidationResult::new();
82
83 for (index, item) in array.iter().enumerate() {
84 match self.element_schema.validate(item) {
85 Ok(validated_item) => results.push(validated_item),
86 Err(mut errors) => {
87 errors.prefix_path(index.to_string());
88 validation_result.merge(errors);
89 }
90 }
91 }
92
93 if validation_result.is_empty() {
94 Ok(results)
95 } else {
96 Err(validation_result)
97 }
98 }
99}
100
101pub fn array<S, T>(element_schema: S) -> ArraySchema<S, T> {
102 ArraySchema::new(element_schema)
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::schema::{number, string};
109 use serde_json::json;
110
111 #[test]
112 fn test_array_validation() {
113 let schema = array(string()).min(1).max(3);
114
115 assert!(schema.validate(&json!(["hello", "world"])).is_ok());
116 assert!(schema.validate(&json!([])).is_err());
117 assert!(schema.validate(&json!(["a", "b", "c", "d"])).is_err());
118 assert!(schema.validate(&json!([1, 2, 3])).is_err());
119 }
120
121 #[test]
125 fn test_empty_array_with_min_zero() {
126 let schema = array(string()).min(0);
127 assert!(schema.validate(&json!([])).is_ok());
128 }
129
130 #[test]
131 fn test_empty_array_with_min_one() {
132 let schema = array(string()).min(1);
133 assert!(schema.validate(&json!([])).is_err());
134 }
135
136 #[test]
137 fn test_empty_array_no_constraints() {
138 let schema = array(string());
139 assert!(schema.validate(&json!([])).is_ok());
140 }
141
142 #[test]
144 fn test_exactly_at_min_length() {
145 let schema = array(string()).min(3);
146 assert!(schema.validate(&json!(["a", "b", "c"])).is_ok());
147 assert!(schema.validate(&json!(["a", "b"])).is_err());
148 }
149
150 #[test]
151 fn test_exactly_at_max_length() {
152 let schema = array(string()).max(3);
153 assert!(schema.validate(&json!(["a", "b", "c"])).is_ok());
154 assert!(schema.validate(&json!(["a", "b", "c", "d"])).is_err());
155 }
156
157 #[test]
158 fn test_exact_length() {
159 let schema = array(string()).length(2);
160 assert!(schema.validate(&json!(["a", "b"])).is_ok());
161 assert!(schema.validate(&json!(["a"])).is_err());
162 assert!(schema.validate(&json!(["a", "b", "c"])).is_err());
163 }
164
165 #[test]
167 fn test_error_at_index_zero() {
168 let schema = array(string().min(3));
169 let result = schema.validate(&json!(["hi"])); assert!(result.is_err());
171 let err = result.unwrap_err();
172 assert_eq!(err.issues[0].path, vec!["0"]);
173 }
174
175 #[test]
176 fn test_error_at_middle_index() {
177 let schema = array(string().min(3));
178 let result = schema.validate(&json!(["hello", "hi", "world"]));
179 assert!(result.is_err());
180 let err = result.unwrap_err();
181 assert_eq!(err.issues[0].path, vec!["1"]);
182 }
183
184 #[test]
185 fn test_multiple_errors_across_indices() {
186 let schema = array(string().min(3));
187 let result = schema.validate(&json!(["hi", "ok", "x"]));
188 assert!(result.is_err());
189 let err = result.unwrap_err();
190 assert_eq!(err.issues.len(), 3);
191 let paths: Vec<_> = err.issues.iter().map(|i| i.path[0].as_str()).collect();
193 assert!(paths.contains(&"0"));
194 assert!(paths.contains(&"1"));
195 assert!(paths.contains(&"2"));
196 }
197
198 #[test]
199 fn test_valid_elements_return_correct_values() {
200 let schema = array(string());
201 let result = schema.validate(&json!(["a", "b", "c"]));
202 assert!(result.is_ok());
203 let values = result.unwrap();
204 assert_eq!(values, vec!["a", "b", "c"]);
205 }
206
207 #[test]
209 fn test_nested_array() {
210 let schema = array(array(string()));
211 let result = schema.validate(&json!([["a", "b"], ["c", "d"]]));
212 assert!(result.is_ok());
213 }
214
215 #[test]
216 fn test_nested_array_error_path() {
217 let schema = array(array(string().min(3)));
218 let result = schema.validate(&json!([["hello"], ["hi"]]));
219 assert!(result.is_err());
220 let err = result.unwrap_err();
221 assert_eq!(err.issues[0].path, vec!["1", "0"]);
223 }
224
225 #[test]
226 fn test_deeply_nested_array() {
227 let schema = array(array(array(string())));
228 let result = schema.validate(&json!([[["a", "b"]], [["c", "d"]]]));
229 assert!(result.is_ok());
230 }
231
232 #[test]
234 fn test_mixed_types_fail() {
235 let schema = array(string());
236 assert!(schema.validate(&json!(["hello", 123, "world"])).is_err());
237 }
238
239 #[test]
240 fn test_number_array() {
241 let schema = array(number().positive());
242 assert!(schema.validate(&json!([1, 2, 3])).is_ok());
243 assert!(schema.validate(&json!([1, -2, 3])).is_err());
244 }
245
246 #[test]
248 fn test_impossible_constraint_min_greater_than_max() {
249 let schema = array(string()).min(5).max(2);
250 assert!(schema.validate(&json!(["a", "b", "c"])).is_err());
252 assert!(schema.validate(&json!(["a"])).is_err());
253 assert!(schema.validate(&json!(["a", "b", "c", "d", "e", "f"])).is_err());
254 }
255
256 #[test]
258 fn test_rejects_null() {
259 let schema = array(string());
260 assert!(schema.validate(&json!(null)).is_err());
261 }
262
263 #[test]
264 fn test_rejects_string() {
265 let schema = array(string());
266 assert!(schema.validate(&json!("not an array")).is_err());
267 }
268
269 #[test]
270 fn test_rejects_number() {
271 let schema = array(string());
272 assert!(schema.validate(&json!(123)).is_err());
273 }
274
275 #[test]
276 fn test_rejects_object() {
277 let schema = array(string());
278 assert!(schema.validate(&json!({"key": "value"})).is_err());
279 }
280
281 #[test]
282 fn test_rejects_boolean() {
283 let schema = array(string());
284 assert!(schema.validate(&json!(true)).is_err());
285 }
286
287 #[test]
289 fn test_single_element_valid() {
290 let schema = array(string());
291 assert!(schema.validate(&json!(["single"])).is_ok());
292 }
293
294 #[test]
295 fn test_single_element_invalid() {
296 let schema = array(string().min(10));
297 let result = schema.validate(&json!(["short"]));
298 assert!(result.is_err());
299 let err = result.unwrap_err();
300 assert_eq!(err.issues[0].path, vec!["0"]);
301 }
302
303 #[test]
305 fn test_large_array() {
306 let schema = array(number());
307 let large_array: Vec<i32> = (0..1000).collect();
308 let json_val = serde_json::to_value(large_array).unwrap();
309 assert!(schema.validate(&json_val).is_ok());
310 }
311}