1use crate::schema::Schema;
2use serde_json::Value;
3use std::{fmt::Debug, sync::Arc};
4use zod_rs_util::{ValidateResult, ValidationError, ValidationResult, ValidationType};
5
6#[derive(Debug, Clone)]
7pub struct TupleSchema {
8 elements: Vec<Arc<dyn TupleElementValidator>>,
9}
10
11impl TupleSchema {
12 pub fn new() -> Self {
13 Self {
14 elements: Vec::new(),
15 }
16 }
17
18 pub fn element<S, T>(mut self, schema: S) -> Self
19 where
20 S: Schema<T> + Send + Sync + 'static,
21 T: serde::Serialize + Send + Sync + Debug + 'static,
22 {
23 self.elements
24 .push(Arc::new(TupleElementValidatorImpl::new(schema)));
25 self
26 }
27}
28
29impl Default for TupleSchema {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35trait TupleElementValidator: Send + Sync + Debug {
36 fn validate_element(&self, value: &Value) -> ValidateResult<Value>;
37}
38
39#[derive(Debug)]
40struct TupleElementValidatorImpl<S, T> {
41 schema: S,
42 _phantom: std::marker::PhantomData<T>,
43}
44
45impl<S, T> TupleElementValidatorImpl<S, T> {
46 fn new(schema: S) -> Self {
47 Self {
48 schema,
49 _phantom: std::marker::PhantomData,
50 }
51 }
52}
53
54impl<S, T> TupleElementValidator for TupleElementValidatorImpl<S, T>
55where
56 S: Schema<T> + Send + Sync + Debug,
57 T: serde::Serialize + Send + Sync + Debug,
58{
59 fn validate_element(&self, value: &Value) -> ValidateResult<Value> {
60 let validated = self.schema.validate(value)?;
61 serde_json::to_value(validated).map_err(|e| {
62 ValidationError::custom(format!("Failed to serialize validated value: {}", e)).into()
63 })
64 }
65}
66
67impl Schema<Value> for TupleSchema {
68 fn validate(&self, value: &Value) -> ValidateResult<Value> {
69 let arr = value.as_array().ok_or_else(|| {
70 ValidationResult::from(ValidationError::invalid_type(
71 ValidationType::Array,
72 ValidationType::from(value),
73 ))
74 })?;
75
76 if arr.len() != self.elements.len() {
77 return Err(ValidationError::custom(format!(
78 "Expected tuple of {} elements, got {}",
79 self.elements.len(),
80 arr.len()
81 ))
82 .into());
83 }
84
85 let mut result = Vec::with_capacity(arr.len());
86 let mut validation_result = ValidationResult::new();
87
88 for (i, (element, schema)) in arr.iter().zip(&self.elements).enumerate() {
89 match schema.validate_element(element) {
90 Ok(validated) => result.push(validated),
91 Err(mut errors) => {
92 errors.prefix_path(i.to_string());
93 validation_result.merge(errors);
94 }
95 }
96 }
97
98 if validation_result.is_empty() {
99 Ok(Value::Array(result))
100 } else {
101 Err(validation_result)
102 }
103 }
104}
105
106pub fn tuple() -> TupleSchema {
107 TupleSchema::new()
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use crate::schema::{boolean, number, string};
114 use serde_json::json;
115
116 #[test]
117 fn test_empty_tuple() {
118 let schema = tuple();
119 assert!(schema.validate(&json!([])).is_ok());
120 assert!(schema.validate(&json!([1])).is_err());
121 }
122
123 #[test]
124 fn test_single_element_tuple() {
125 let schema = tuple().element(string());
126 assert!(schema.validate(&json!(["hello"])).is_ok());
127 assert!(schema.validate(&json!([123])).is_err());
128 assert!(schema.validate(&json!([])).is_err());
129 assert!(schema.validate(&json!(["a", "b"])).is_err());
130 }
131
132 #[test]
133 fn test_two_element_tuple() {
134 let schema = tuple().element(string()).element(number());
135 assert!(schema.validate(&json!(["hello", 42])).is_ok());
136 assert!(schema.validate(&json!([42, "hello"])).is_err());
137 assert!(schema.validate(&json!(["hello"])).is_err());
138 assert!(schema.validate(&json!(["hello", 42, true])).is_err());
139 }
140
141 #[test]
142 fn test_three_element_tuple() {
143 let schema = tuple()
144 .element(string())
145 .element(number())
146 .element(boolean());
147
148 assert!(schema.validate(&json!(["test", 123, true])).is_ok());
149 assert!(schema.validate(&json!(["test", 123, false])).is_ok());
150 assert!(schema.validate(&json!(["test", 123])).is_err());
151 }
152
153 #[test]
154 fn test_tuple_with_constraints() {
155 let schema = tuple()
156 .element(string().min(3))
157 .element(number().positive());
158
159 assert!(schema.validate(&json!(["abc", 1])).is_ok());
160 assert!(schema.validate(&json!(["ab", 1])).is_err()); assert!(schema.validate(&json!(["abc", -1])).is_err()); }
163
164 #[test]
165 fn test_tuple_returns_array_value() {
166 let schema = tuple().element(string()).element(number());
167 let result = schema.validate(&json!(["hello", 42]));
168 assert!(result.is_ok());
169 assert_eq!(result.unwrap(), json!(["hello", 42.0]));
170 }
171
172 #[test]
173 fn test_tuple_rejects_non_array() {
174 let schema = tuple().element(string());
175 assert!(schema.validate(&json!("hello")).is_err());
176 assert!(schema.validate(&json!(123)).is_err());
177 assert!(schema.validate(&json!(null)).is_err());
178 assert!(schema.validate(&json!({})).is_err());
179 }
180
181 #[test]
182 fn test_tuple_error_includes_index() {
183 let schema = tuple().element(string()).element(number());
184 let result = schema.validate(&json!(["hello", "not a number"]));
185 assert!(result.is_err());
186 let err = result.unwrap_err();
187 assert!(!err.issues.is_empty());
189 }
190
191 #[test]
192 fn test_homogeneous_tuple() {
193 let schema = tuple()
194 .element(number())
195 .element(number())
196 .element(number());
197
198 assert!(schema.validate(&json!([1, 2, 3])).is_ok());
199 assert!(schema.validate(&json!([1, 2])).is_err());
200 assert!(schema.validate(&json!([1, 2, "3"])).is_err());
201 }
202}