vld/collections/
record.rs1use serde_json::Value;
2use std::collections::HashMap;
3
4use crate::error::{value_type_name, IssueCode, PathSegment, VldError};
5use crate::schema::VldSchema;
6
7pub struct ZRecord<V: VldSchema> {
21 value_schema: V,
22 min_keys: Option<usize>,
23 max_keys: Option<usize>,
24}
25
26impl<V: VldSchema> ZRecord<V> {
27 pub fn new(value_schema: V) -> Self {
28 Self {
29 value_schema,
30 min_keys: None,
31 max_keys: None,
32 }
33 }
34
35 pub fn min_keys(mut self, n: usize) -> Self {
37 self.min_keys = Some(n);
38 self
39 }
40
41 pub fn max_keys(mut self, n: usize) -> Self {
43 self.max_keys = Some(n);
44 self
45 }
46
47 #[allow(dead_code)]
48 pub(crate) fn value_schema_ref(&self) -> &V {
49 &self.value_schema
50 }
51
52 #[cfg(feature = "openapi")]
56 pub fn to_json_schema_inner(&self) -> serde_json::Value
57 where
58 V: crate::json_schema::JsonSchema,
59 {
60 serde_json::json!({
61 "type": "object",
62 "additionalProperties": self.value_schema.json_schema(),
63 })
64 }
65}
66
67impl<V: VldSchema> VldSchema for ZRecord<V> {
68 type Output = HashMap<String, V::Output>;
69
70 fn parse_value(&self, value: &Value) -> Result<HashMap<String, V::Output>, VldError> {
71 let obj = value.as_object().ok_or_else(|| {
72 VldError::single(
73 IssueCode::InvalidType {
74 expected: "object".to_string(),
75 received: value_type_name(value),
76 },
77 format!("Expected object, received {}", value_type_name(value)),
78 )
79 })?;
80
81 let mut errors = VldError::new();
82
83 if let Some(min) = self.min_keys {
84 if obj.len() < min {
85 errors.push(
86 IssueCode::TooSmall {
87 minimum: min as f64,
88 inclusive: true,
89 },
90 format!("Record must have at least {} keys", min),
91 );
92 }
93 }
94
95 if let Some(max) = self.max_keys {
96 if obj.len() > max {
97 errors.push(
98 IssueCode::TooBig {
99 maximum: max as f64,
100 inclusive: true,
101 },
102 format!("Record must have at most {} keys", max),
103 );
104 }
105 }
106
107 let mut result = HashMap::new();
108
109 for (key, val) in obj {
110 match self.value_schema.parse_value(val) {
111 Ok(v) => {
112 result.insert(key.clone(), v);
113 }
114 Err(e) => {
115 errors = errors.merge(e.with_prefix(PathSegment::Field(key.clone())));
116 }
117 }
118 }
119
120 if errors.is_empty() {
121 Ok(result)
122 } else {
123 Err(errors)
124 }
125 }
126}