1use serde_json::{Map, Value};
2
3use crate::error::{value_type_name, IssueCode, PathSegment, VldError};
4use crate::schema::VldSchema;
5
6pub trait DynSchema {
10 fn dyn_parse(&self, value: &Value) -> Result<Value, VldError>;
11 #[cfg(feature = "openapi")]
15 fn dyn_json_schema(&self) -> Value {
16 serde_json::json!({})
17 }
18}
19
20impl<T> DynSchema for T
23where
24 T: VldSchema,
25 T::Output: serde::Serialize,
26{
27 fn dyn_parse(&self, value: &Value) -> Result<Value, VldError> {
28 let result = self.parse_value(value)?;
29 serde_json::to_value(&result).map_err(|e| {
30 VldError::single(
31 IssueCode::Custom {
32 code: "serialize".to_string(),
33 },
34 format!("Failed to serialize validated value: {}", e),
35 )
36 })
37 }
38}
39
40#[cfg(feature = "openapi")]
41pub(crate) struct JsonSchemaField<T> {
45 inner: T,
46}
47
48#[cfg(feature = "openapi")]
49impl<T> DynSchema for JsonSchemaField<T>
50where
51 T: VldSchema + crate::json_schema::JsonSchema,
52 T::Output: serde::Serialize,
53{
54 fn dyn_parse(&self, value: &Value) -> Result<Value, VldError> {
55 let result = self.inner.parse_value(value)?;
56 serde_json::to_value(&result).map_err(|e| {
57 VldError::single(
58 IssueCode::Custom {
59 code: "serialize".to_string(),
60 },
61 format!("Failed to serialize validated value: {}", e),
62 )
63 })
64 }
65
66 fn dyn_json_schema(&self) -> Value {
67 self.inner.json_schema()
68 }
69}
70
71struct ObjectField {
72 name: String,
73 schema: Box<dyn DynSchema>,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78enum UnknownFieldMode {
79 Strip,
81 Strict,
83 Passthrough,
85}
86
87struct ConditionalRule {
108 condition_field: String,
109 condition_value: serde_json::Value,
110 target_field: String,
111 schema: Box<dyn DynSchema>,
112}
113
114pub struct ZObject {
115 fields: Vec<ObjectField>,
116 unknown_mode: UnknownFieldMode,
117 catchall_schema: Option<Box<dyn DynSchema>>,
118 conditional_rules: Vec<ConditionalRule>,
119}
120
121impl ZObject {
122 pub fn new() -> Self {
123 Self {
124 fields: vec![],
125 unknown_mode: UnknownFieldMode::Strip,
126 catchall_schema: None,
127 conditional_rules: vec![],
128 }
129 }
130
131 pub fn field<S: DynSchema + 'static>(mut self, name: impl Into<String>, schema: S) -> Self {
133 self.fields.push(ObjectField {
134 name: name.into(),
135 schema: Box::new(schema),
136 });
137 self
138 }
139
140 #[cfg(feature = "openapi")]
148 pub fn field_schema<S>(mut self, name: impl Into<String>, schema: S) -> Self
149 where
150 S: VldSchema + crate::json_schema::JsonSchema + 'static,
151 S::Output: serde::Serialize,
152 {
153 self.fields.push(ObjectField {
154 name: name.into(),
155 schema: Box::new(JsonSchemaField { inner: schema }),
156 });
157 self
158 }
159
160 pub fn field_optional<S: DynSchema + 'static>(
177 mut self,
178 name: impl Into<String>,
179 schema: S,
180 ) -> Self {
181 self.fields.push(ObjectField {
182 name: name.into(),
183 schema: Box::new(OptionalDynSchema(Box::new(schema))),
184 });
185 self
186 }
187
188 pub fn strict(mut self) -> Self {
190 self.unknown_mode = UnknownFieldMode::Strict;
191 self
192 }
193
194 pub fn strip(mut self) -> Self {
196 self.unknown_mode = UnknownFieldMode::Strip;
197 self
198 }
199
200 pub fn passthrough(mut self) -> Self {
202 self.unknown_mode = UnknownFieldMode::Passthrough;
203 self
204 }
205
206 pub fn omit(mut self, name: &str) -> Self {
210 self.fields.retain(|f| f.name != name);
211 self
212 }
213
214 pub fn pick(mut self, names: &[&str]) -> Self {
216 self.fields.retain(|f| names.contains(&f.name.as_str()));
217 self
218 }
219
220 pub fn extend(mut self, other: ZObject) -> Self {
224 for field in other.fields {
225 self.fields.retain(|f| f.name != field.name);
226 self.fields.push(field);
227 }
228 self
229 }
230
231 pub fn merge(self, other: ZObject) -> Self {
233 self.extend(other)
234 }
235
236 pub fn partial(mut self) -> Self {
241 self.fields = self
242 .fields
243 .into_iter()
244 .map(|f| ObjectField {
245 name: f.name,
246 schema: Box::new(OptionalDynSchema(f.schema)),
247 })
248 .collect();
249 self
250 }
251
252 pub fn required(mut self) -> Self {
255 self.fields = self
256 .fields
257 .into_iter()
258 .map(|f| ObjectField {
259 name: f.name,
260 schema: Box::new(RequiredDynSchema(f.schema)),
261 })
262 .collect();
263 self
264 }
265
266 pub fn catchall<S: DynSchema + 'static>(mut self, schema: S) -> Self {
271 self.catchall_schema = Some(Box::new(schema));
272 self
273 }
274
275 pub fn when<S: DynSchema + 'static>(
298 mut self,
299 condition_field: impl Into<String>,
300 condition_value: impl Into<serde_json::Value>,
301 target_field: impl Into<String>,
302 schema: S,
303 ) -> Self {
304 self.conditional_rules.push(ConditionalRule {
305 condition_field: condition_field.into(),
306 condition_value: condition_value.into(),
307 target_field: target_field.into(),
308 schema: Box::new(schema),
309 });
310 self
311 }
312
313 pub fn deep_partial(self) -> Self {
318 self.partial()
319 }
320
321 pub fn keyof(&self) -> Vec<String> {
323 self.fields.iter().map(|f| f.name.clone()).collect()
324 }
325
326 #[cfg(feature = "openapi")]
334 pub fn to_json_schema(&self) -> serde_json::Value {
335 let required: Vec<String> = self.fields.iter().map(|f| f.name.clone()).collect();
336 let mut props = serde_json::Map::new();
337 for f in &self.fields {
338 props.insert(f.name.clone(), f.schema.dyn_json_schema());
339 }
340 let mut schema = serde_json::json!({
341 "type": "object",
342 "required": required,
343 "properties": Value::Object(props),
344 "additionalProperties": self.unknown_mode != UnknownFieldMode::Strict,
345 });
346 if let Some(ref catchall) = self.catchall_schema {
347 schema["additionalProperties"] = catchall.dyn_json_schema();
348 }
349 schema
350 }
351}
352
353impl Default for ZObject {
354 fn default() -> Self {
355 Self::new()
356 }
357}
358
359impl VldSchema for ZObject {
360 type Output = Map<String, Value>;
361
362 fn parse_value(&self, value: &Value) -> Result<Map<String, Value>, VldError> {
363 let obj = value.as_object().ok_or_else(|| {
364 VldError::single(
365 IssueCode::InvalidType {
366 expected: "object".to_string(),
367 received: value_type_name(value),
368 },
369 format!("Expected object, received {}", value_type_name(value)),
370 )
371 })?;
372
373 let mut result = Map::new();
374 let mut errors = VldError::new();
375
376 for field in &self.fields {
378 let field_value = obj.get(&field.name).unwrap_or(&Value::Null);
379 match field.schema.dyn_parse(field_value) {
380 Ok(v) => {
381 result.insert(field.name.clone(), v);
382 }
383 Err(e) => {
384 errors = errors.merge(e.with_prefix(PathSegment::Field(field.name.clone())));
385 }
386 }
387 }
388
389 let known_keys: Vec<&str> = self.fields.iter().map(|f| f.name.as_str()).collect();
391 let unknown_keys: Vec<&String> = obj
392 .keys()
393 .filter(|k| !known_keys.contains(&k.as_str()))
394 .collect();
395
396 if let Some(catchall) = &self.catchall_schema {
397 for key in &unknown_keys {
398 let val = &obj[key.as_str()];
399 match catchall.dyn_parse(val) {
400 Ok(v) => {
401 result.insert((*key).clone(), v);
402 }
403 Err(e) => {
404 errors = errors.merge(e.with_prefix(PathSegment::Field((*key).clone())));
405 }
406 }
407 }
408 } else {
409 match self.unknown_mode {
410 UnknownFieldMode::Strip => {}
411 UnknownFieldMode::Strict => {
412 for key in &unknown_keys {
413 let mut issue_err = VldError::single(
414 IssueCode::UnrecognizedField,
415 format!("Unrecognized field: \"{}\"", key),
416 );
417 issue_err = issue_err.with_prefix(PathSegment::Field((*key).clone()));
418 errors = errors.merge(issue_err);
419 }
420 }
421 UnknownFieldMode::Passthrough => {
422 for key in &unknown_keys {
423 result.insert((*key).clone(), obj[key.as_str()].clone());
424 }
425 }
426 }
427 }
428
429 for rule in &self.conditional_rules {
431 let cond_val = obj.get(&rule.condition_field).unwrap_or(&Value::Null);
432 if *cond_val == rule.condition_value {
433 let target_val = obj.get(&rule.target_field).unwrap_or(&Value::Null);
434 if let Err(e) = rule.schema.dyn_parse(target_val) {
435 errors =
436 errors.merge(e.with_prefix(PathSegment::Field(rule.target_field.clone())));
437 }
438 }
439 }
440
441 if errors.is_empty() {
442 Ok(result)
443 } else {
444 Err(errors)
445 }
446 }
447}
448
449struct OptionalDynSchema(Box<dyn DynSchema>);
451
452impl DynSchema for OptionalDynSchema {
453 fn dyn_parse(&self, value: &Value) -> Result<Value, VldError> {
454 if value.is_null() {
455 return Ok(Value::Null);
456 }
457 self.0.dyn_parse(value)
458 }
459}
460
461struct RequiredDynSchema(Box<dyn DynSchema>);
463
464impl DynSchema for RequiredDynSchema {
465 fn dyn_parse(&self, value: &Value) -> Result<Value, VldError> {
466 if value.is_null() {
467 return Err(VldError::single(
468 IssueCode::MissingField,
469 "Required field is missing or null",
470 ));
471 }
472 self.0.dyn_parse(value)
473 }
474}