Skip to main content

valico/json_dsl/
coercers.rs

1use serde_json::{to_string, to_value, Value};
2
3use super::errors;
4
5#[allow(dead_code)]
6#[derive(Copy, Clone)]
7pub enum PrimitiveType {
8    String,
9    I64,
10    U64,
11    F64,
12    Boolean,
13    Null,
14    Array,
15    Object,
16    // Reserved for future use in Rustless
17    File,
18}
19
20pub type CoercerResult<T> = Result<T, super::super::ValicoErrors>;
21
22pub trait Coercer: Send + Sync {
23    fn get_primitive_type(&self) -> PrimitiveType;
24    fn coerce(&self, _: &mut Value, _: &str) -> CoercerResult<Option<Value>>;
25}
26
27#[derive(Copy, Clone)]
28pub struct StringCoercer;
29
30impl Coercer for StringCoercer {
31    fn get_primitive_type(&self) -> PrimitiveType {
32        PrimitiveType::String
33    }
34    fn coerce(&self, val: &mut Value, path: &str) -> CoercerResult<Option<Value>> {
35        if val.is_string() {
36            Ok(None)
37        } else if val.is_number() {
38            Ok(Some(to_value(to_string(&val).unwrap()).unwrap()))
39        } else {
40            Err(vec![Box::new(errors::WrongType {
41                path: path.to_string(),
42                detail: "Can't coerce value to string".to_string(),
43            })])
44        }
45    }
46}
47
48#[derive(Copy, Clone)]
49pub struct I64Coercer;
50
51impl Coercer for I64Coercer {
52    fn get_primitive_type(&self) -> PrimitiveType {
53        PrimitiveType::I64
54    }
55    fn coerce(&self, val: &mut Value, path: &str) -> CoercerResult<Option<Value>> {
56        if val.is_i64() {
57            Ok(None)
58        } else if val.is_u64() {
59            let val = val.as_u64().unwrap();
60            Ok(Some(to_value(val as i64).unwrap()))
61        } else if val.is_f64() {
62            let val = val.as_f64().unwrap();
63            Ok(Some(to_value(val as i64).unwrap()))
64        } else if val.is_string() {
65            let val = val.as_str().unwrap();
66            let converted: Option<i64> = val.parse().ok();
67            match converted {
68                Some(num) => Ok(Some(to_value(num).unwrap())),
69                None => Err(vec![Box::new(errors::WrongType {
70                    path: path.to_string(),
71                    detail: "Can't coerce string value to i64".to_string(),
72                })]),
73            }
74        } else {
75            Err(vec![Box::new(errors::WrongType {
76                path: path.to_string(),
77                detail: "Can't coerce object value to i64".to_string(),
78            })])
79        }
80    }
81}
82
83#[derive(Copy, Clone)]
84pub struct U64Coercer;
85
86impl Coercer for U64Coercer {
87    fn get_primitive_type(&self) -> PrimitiveType {
88        PrimitiveType::U64
89    }
90    fn coerce(&self, val: &mut Value, path: &str) -> CoercerResult<Option<Value>> {
91        if val.is_u64() {
92            Ok(None)
93        } else if val.is_i64() {
94            let val = val.as_i64().unwrap();
95            Ok(Some(to_value(val as u64).unwrap()))
96        } else if val.is_f64() {
97            let val = val.as_f64().unwrap();
98            Ok(Some(to_value(val as u64).unwrap()))
99        } else if val.is_string() {
100            let val = val.as_str().unwrap();
101            let converted: Option<u64> = val.parse().ok();
102            match converted {
103                Some(num) => Ok(Some(to_value(num).unwrap())),
104                None => Err(vec![Box::new(errors::WrongType {
105                    path: path.to_string(),
106                    detail: "Can't coerce string value to u64".to_string(),
107                })]),
108            }
109        } else {
110            Err(vec![Box::new(errors::WrongType {
111                path: path.to_string(),
112                detail: "Can't coerce object value to u64".to_string(),
113            })])
114        }
115    }
116}
117
118#[derive(Copy, Clone)]
119pub struct F64Coercer;
120
121impl Coercer for F64Coercer {
122    fn get_primitive_type(&self) -> PrimitiveType {
123        PrimitiveType::F64
124    }
125    fn coerce(&self, val: &mut Value, path: &str) -> CoercerResult<Option<Value>> {
126        if val.is_f64() {
127            Ok(None)
128        } else if val.is_i64() {
129            let val = val.as_i64().unwrap();
130            Ok(Some(to_value(val as f64).unwrap()))
131        } else if val.is_u64() {
132            let val = val.as_u64().unwrap();
133            Ok(Some(to_value(val as f64).unwrap()))
134        } else if val.is_string() {
135            let val = val.as_str().unwrap();
136            let converted: Option<f64> = val.parse().ok();
137            match converted {
138                Some(num) => Ok(Some(to_value(num).unwrap())),
139                None => Err(vec![Box::new(errors::WrongType {
140                    path: path.to_string(),
141                    detail: "Can't coerce string value to f64".to_string(),
142                })]),
143            }
144        } else {
145            Err(vec![Box::new(errors::WrongType {
146                path: path.to_string(),
147                detail: "Can't coerce object value to f64".to_string(),
148            })])
149        }
150    }
151}
152
153#[derive(Copy, Clone)]
154pub struct BooleanCoercer;
155
156impl Coercer for BooleanCoercer {
157    fn get_primitive_type(&self) -> PrimitiveType {
158        PrimitiveType::Boolean
159    }
160    fn coerce(&self, val: &mut Value, path: &str) -> CoercerResult<Option<Value>> {
161        if val.is_boolean() {
162            Ok(None)
163        } else if val.is_string() {
164            let val = val.as_str().unwrap();
165            if val == "true" {
166                Ok(Some(json!(true)))
167            } else if val == "false" {
168                Ok(Some(json!(false)))
169            } else {
170                Err(vec![
171                    Box::new(errors::WrongType {
172                        path: path.to_string(),
173                        detail: "Can't coerce this string value to boolean. Correct values are 'true' and 'false'".to_string()
174                    })
175                ])
176            }
177        } else {
178            Err(vec![Box::new(errors::WrongType {
179                path: path.to_string(),
180                detail: "Can't coerce object to boolean".to_string(),
181            })])
182        }
183    }
184}
185
186#[derive(Copy, Clone)]
187pub struct NullCoercer;
188
189impl Coercer for NullCoercer {
190    fn get_primitive_type(&self) -> PrimitiveType {
191        PrimitiveType::Null
192    }
193    fn coerce(&self, val: &mut Value, path: &str) -> CoercerResult<Option<Value>> {
194        if val.is_null() {
195            Ok(None)
196        } else if val.is_string() {
197            let val = val.as_str().unwrap();
198            if val.is_empty() {
199                Ok(Some(json!(null)))
200            } else {
201                Err(vec![Box::new(errors::WrongType {
202                    path: path.to_string(),
203                    detail:
204                        "Can't coerce this string value to null. Correct value is only empty string"
205                            .to_string(),
206                })])
207            }
208        } else {
209            Err(vec![Box::new(errors::WrongType {
210                path: path.to_string(),
211                detail: "Can't coerce object to null".to_string(),
212            })])
213        }
214    }
215}
216
217pub struct ArrayCoercer {
218    sub_coercer: Option<Box<dyn Coercer + Send + Sync>>,
219    separator: Option<String>,
220}
221
222impl ArrayCoercer {
223    pub fn new() -> ArrayCoercer {
224        ArrayCoercer {
225            sub_coercer: None,
226            separator: None,
227        }
228    }
229
230    pub fn encoded(separator: String) -> ArrayCoercer {
231        ArrayCoercer {
232            separator: Some(separator),
233            sub_coercer: None,
234        }
235    }
236
237    pub fn encoded_of(
238        separator: String,
239        sub_coercer: Box<dyn Coercer + Send + Sync>,
240    ) -> ArrayCoercer {
241        ArrayCoercer {
242            separator: Some(separator),
243            sub_coercer: Some(sub_coercer),
244        }
245    }
246
247    pub fn of_type(sub_coercer: Box<dyn Coercer + Send + Sync>) -> ArrayCoercer {
248        ArrayCoercer {
249            separator: None,
250            sub_coercer: Some(sub_coercer),
251        }
252    }
253
254    fn coerce_array(&self, val: &mut Value, path: &str) -> CoercerResult<Option<Value>> {
255        let array = val.as_array_mut().unwrap();
256        if self.sub_coercer.is_some() {
257            let sub_coercer = self.sub_coercer.as_ref().unwrap();
258            let mut errors = vec![];
259            for i in 0..array.len() {
260                let item_path = [path, i.to_string().as_ref()].join("/");
261                match sub_coercer.coerce(&mut array[i], item_path.as_ref()) {
262                    Ok(Some(value)) => {
263                        array.remove(i);
264                        array.insert(i, value);
265                    }
266                    Ok(None) => (),
267                    Err(err) => {
268                        errors.extend(err);
269                    }
270                }
271            }
272
273            if errors.is_empty() {
274                Ok(None)
275            } else {
276                Err(errors)
277            }
278        } else {
279            Ok(None)
280        }
281    }
282}
283
284impl Coercer for ArrayCoercer {
285    fn get_primitive_type(&self) -> PrimitiveType {
286        PrimitiveType::Array
287    }
288
289    fn coerce(&self, val: &mut Value, path: &str) -> CoercerResult<Option<Value>> {
290        if val.is_array() {
291            self.coerce_array(val, path)
292        } else if val.is_string() && self.separator.is_some() {
293            let separator = self.separator.as_ref().unwrap();
294            let string = val.as_str().unwrap();
295            let mut array = Value::Array(
296                string
297                    .split(&separator[..])
298                    .map(|s| Value::String(s.to_string()))
299                    .collect::<Vec<Value>>(),
300            );
301            self.coerce_array(&mut array, path)?;
302            Ok(Some(array))
303        } else {
304            Err(vec![Box::new(errors::WrongType {
305                path: path.to_string(),
306                detail: "Can't coerce object to array".to_string(),
307            })])
308        }
309    }
310}
311
312#[derive(Copy, Clone)]
313pub struct ObjectCoercer;
314
315impl Coercer for ObjectCoercer {
316    fn get_primitive_type(&self) -> PrimitiveType {
317        PrimitiveType::Object
318    }
319    fn coerce(&self, val: &mut Value, path: &str) -> CoercerResult<Option<Value>> {
320        if val.is_object() {
321            Ok(None)
322        } else {
323            Err(vec![Box::new(errors::WrongType {
324                path: path.to_string(),
325                detail: "Can't coerce non-object value to the object type".to_string(),
326            })])
327        }
328    }
329}