siderite_validation/types/
vec.rs1use serde::de::Error as _;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use serde_json::Value;
6
7use crate::context::ValidationContext;
8use crate::dump::{Dump, DumpError, DumpOptions};
9use crate::error::FieldError;
10use crate::schema::{Schema, SchemaObject, SchemaRegistry};
11use crate::validate::Validate;
12
13#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct ConstrainedVec<T, const MIN: usize, const MAX: usize>(Vec<T>);
20
21fn check_len<const MIN: usize, const MAX: usize>(len: usize) -> Result<(), FieldError> {
22 const { assert!(MIN <= MAX, "ConstrainedVec MIN must be <= MAX") };
23 if len < MIN {
24 Err(FieldError::new(
25 "too_short",
26 format!("ensure this value has at least {MIN} items"),
27 ))
28 } else if len > MAX {
29 Err(FieldError::new(
30 "too_long",
31 format!("ensure this value has at most {MAX} items"),
32 ))
33 } else {
34 Ok(())
35 }
36}
37
38impl<T, const MIN: usize, const MAX: usize> ConstrainedVec<T, MIN, MAX> {
39 pub fn new(value: Vec<T>) -> Result<Self, FieldError> {
41 check_len::<MIN, MAX>(value.len())?;
42 Ok(Self(value))
43 }
44}
45
46impl<T, const MIN: usize, const MAX: usize> ConstrainedVec<T, MIN, MAX> {
47 pub fn into_inner(self) -> Vec<T> {
49 self.0
50 }
51}
52
53impl<T, const MIN: usize, const MAX: usize> AsRef<[T]> for ConstrainedVec<T, MIN, MAX> {
54 fn as_ref(&self) -> &[T] {
55 &self.0
56 }
57}
58
59impl<T, const MIN: usize, const MAX: usize> AsRef<Vec<T>> for ConstrainedVec<T, MIN, MAX> {
60 fn as_ref(&self) -> &Vec<T> {
61 &self.0
62 }
63}
64
65impl<T, const MIN: usize, const MAX: usize> std::ops::Deref for ConstrainedVec<T, MIN, MAX> {
66 type Target = [T];
67
68 fn deref(&self) -> &Self::Target {
69 &self.0
70 }
71}
72
73impl<T, const MIN: usize, const MAX: usize> TryFrom<Vec<T>> for ConstrainedVec<T, MIN, MAX> {
74 type Error = FieldError;
75
76 fn try_from(value: Vec<T>) -> Result<Self, Self::Error> {
77 Self::new(value)
78 }
79}
80
81impl<T, const MIN: usize, const MAX: usize> From<ConstrainedVec<T, MIN, MAX>> for Vec<T> {
82 fn from(value: ConstrainedVec<T, MIN, MAX>) -> Self {
83 value.0
84 }
85}
86
87impl<T: Serialize, const MIN: usize, const MAX: usize> Serialize for ConstrainedVec<T, MIN, MAX> {
88 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
89 where
90 S: Serializer,
91 {
92 self.0.serialize(serializer)
93 }
94}
95
96impl<'de, T: Deserialize<'de>, const MIN: usize, const MAX: usize> Deserialize<'de>
97 for ConstrainedVec<T, MIN, MAX>
98{
99 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
100 where
101 D: Deserializer<'de>,
102 {
103 let value = Vec::<T>::deserialize(deserializer)?;
104 Self::new(value).map_err(D::Error::custom)
105 }
106}
107
108impl<T: Validate, const MIN: usize, const MAX: usize> Validate for ConstrainedVec<T, MIN, MAX> {
109 fn prepare(input: &mut Value, ctx: &mut ValidationContext) {
110 Vec::<T>::prepare(input, ctx);
111 if let Some(items) = input.as_array() {
112 ctx.check(check_len::<MIN, MAX>(items.len()));
113 }
114 }
115
116 fn validate(&self, ctx: &mut ValidationContext) {
117 self.0.validate(ctx);
118 }
119}
120
121impl<T: Schema + 'static, const MIN: usize, const MAX: usize> Schema
122 for ConstrainedVec<T, MIN, MAX>
123{
124 fn schema(registry: &mut SchemaRegistry) -> SchemaObject {
125 SchemaObject::of_type("array")
126 .with("items", registry.subschema::<T>())
127 .with("minItems", MIN as u64)
128 .with("maxItems", MAX as u64)
129 }
130}
131
132impl<T: Dump, const MIN: usize, const MAX: usize> Dump for ConstrainedVec<T, MIN, MAX> {
133 fn dump(&self, opts: &DumpOptions) -> Result<Value, DumpError> {
134 self.0.dump(opts)
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 #![allow(clippy::unwrap_used)]
141
142 use super::*;
143 use crate::context::ValidationContext;
144 use crate::dump::{Dump, DumpOptions};
145 use crate::schema::schema_for;
146 use crate::types::{SecretString, prepare_codes};
147 use serde_json::json;
148
149 type Pair = ConstrainedVec<i32, 2, 3>;
150
151 #[test]
152 fn constrained_vec_construction() {
153 assert_eq!(Pair::new(vec![1, 2]).unwrap().into_inner(), vec![1, 2]);
154 assert!(Pair::new(vec![1, 2, 3]).is_ok());
155 assert_eq!(Pair::new(vec![1]).unwrap_err().code, "too_short");
156 assert_eq!(Pair::new(vec![1, 2, 3, 4]).unwrap_err().code, "too_long");
157 assert_eq!(Pair::new(vec![1, 2]).unwrap().len(), 2);
158 }
159
160 #[test]
161 fn constrained_vec_serde_round_trip_and_reject() {
162 let value = Pair::new(vec![1, 2]).unwrap();
163 let encoded = serde_json::to_string(&value).unwrap();
164 assert_eq!(encoded, "[1,2]");
165 let round: Pair = serde_json::from_str(&encoded).unwrap();
166 assert_eq!(&*round, &[1, 2]);
167 assert!(serde_json::from_str::<Pair>("[1]").is_err());
168 assert!(serde_json::from_str::<Pair>("[1,2,3,4]").is_err());
169 }
170
171 #[test]
172 fn constrained_vec_prepare_lax_and_strict() {
173 let (out, codes) = prepare_codes::<Pair>(json!([1, 2]), ValidationContext::new());
174 assert!(codes.is_empty());
175 assert_eq!(out, json!([1, 2]));
176 assert_eq!(
177 prepare_codes::<Pair>(json!([1]), ValidationContext::new()).1,
178 ["too_short"]
179 );
180 assert_eq!(
181 prepare_codes::<Pair>(json!(1), ValidationContext::new()).1,
182 ["list_type"]
183 );
184 assert_eq!(
185 prepare_codes::<Pair>(json!(1), ValidationContext::new().with_strict(true)).1,
186 ["list_type"]
187 );
188 let (out, codes) =
190 prepare_codes::<Pair>(json!("3"), crate::context::ValidationContext::for_text());
191 assert_eq!(out, json!([3]));
192 assert_eq!(codes, ["too_short"]);
193 }
194
195 #[test]
196 fn constrained_vec_schema_dump_and_parse_value() {
197 assert_eq!(
198 schema_for::<Pair>().0.into_value(),
199 json!({"type": "array", "items": {"type": "integer", "format": "int32"}, "minItems": 2, "maxItems": 3})
200 );
201 let value = Pair::new(vec![1, 2, 3]).unwrap();
202 assert_eq!(value.dump(&DumpOptions::new()).unwrap(), json!([1, 2, 3]));
203
204 type Secrets = ConstrainedVec<SecretString, 1, 2>;
205 let secrets = Secrets::new(vec![SecretString::new("hunter2").unwrap()]).unwrap();
206 assert_eq!(
207 secrets.dump(&DumpOptions::new()).unwrap(),
208 json!(["**********"])
209 );
210
211 let parsed = crate::parse_value::<Pair>(json!([9, 8]), ValidationContext::new()).unwrap();
212 assert_eq!(&*parsed, &[9, 8]);
213 let err = crate::parse_value::<Pair>(json!([]), ValidationContext::new()).unwrap_err();
214 assert_eq!(err.errors[0].code, "too_short");
215 }
216
217 #[test]
218 fn constrained_vec_prepare_checks_elements_and_length() {
219 type Names = ConstrainedVec<String, 1, 2>;
220 let (out, codes) = prepare_codes::<Names>(json!([1, "ok", "x"]), ValidationContext::new());
221 assert_eq!(out, json!([1, "ok", "x"]));
222 assert_eq!(codes, ["string_type", "too_long"]);
223 }
224}