json_utils/schema_coercion/
coercion.rs

1
2use crate::json::JsValue;
3
4use super::CoercionError;
5
6type CoercionResult = Result<JsValue, CoercionError>;
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub enum Coercion {
10    #[serde(rename = "identity")]
11    Identity,
12
13    #[serde(rename = "replace_with_literal")]
14    ReplaceWithLiteral(JsValue),
15
16    #[serde(rename = "number_to_string")]
17    NumberToString,
18
19    #[serde(rename = "array")]
20    Array(Box<Coercion>),
21
22    #[serde(rename = "object")]
23    Object(Vec<(String, Coercion)>),
24}
25
26impl Coercion {
27    pub fn coerce(self, value: JsValue) -> CoercionResult {
28        match self {
29            Coercion::Identity => ok_value(value),
30            Coercion::ReplaceWithLiteral(literal_value) => ok_value(literal_value),
31            Coercion::NumberToString => number_to_string(self, value),
32            Coercion::Array(item_coercion) => array_coercion(item_coercion, value),
33            Coercion::Object(prop_coercions) => object_coercion(prop_coercions, value),
34        }
35    }
36
37    pub fn is_subtyping_only(&self) -> bool {
38        match *self {
39            Coercion::Identity => true,
40            Coercion::NumberToString => false,
41            Coercion::ReplaceWithLiteral(_) => false,
42            Coercion::Array(ref items) => items.is_subtyping_only(),
43            Coercion::Object(ref props) => props.iter().all(|(_, prop)| prop.is_subtyping_only()),
44        }
45    }
46}
47
48fn number_to_string(coercion: Coercion, value: JsValue) -> CoercionResult {
49    if let JsValue::Number(js_number) = value {
50        js_number
51            .as_f64()
52            .map(|f| format!("{}", f))
53            .map(JsValue::String)
54            .ok_or(CoercionError::JsNumberError)
55    } else {
56        err_unexpectd_input(coercion, value)
57    }
58}
59
60fn array_coercion(item_coercion: Box<Coercion>, value: JsValue) -> CoercionResult {
61    if let JsValue::Array(items) = value {
62        let item_coercion = *item_coercion;
63        let items = items
64            .into_iter()
65            .map(move |item| item_coercion.clone().coerce(item))
66            .collect::<Result<Vec<_>, _>>()?;
67        ok_value(JsValue::Array(items))
68    } else {
69        err_unexpectd_input(Coercion::Array(item_coercion), value)
70    }
71}
72
73fn object_coercion(prop_coercions: Vec<(String, Coercion)>, value: JsValue) -> CoercionResult {
74    if let JsValue::Object(mut input_props) = value {
75        let props_count = prop_coercions.len();
76        let mut output_props = Vec::<(String, JsValue)>::with_capacity(props_count);
77
78        for (prop_name, prop_coercion) in prop_coercions {
79            let input_prop_value =
80                input_props
81                    .remove(&prop_name)
82                    .ok_or(CoercionError::ObjectFieldsMissing(hashset![
83                        prop_name.clone()
84                    ]))?;
85            let output_prop_value = prop_coercion.coerce(input_prop_value)?;
86
87            output_props.push((prop_name, output_prop_value));
88        }
89
90        ok_value(JsValue::Object(output_props.into_iter().collect()))
91    } else {
92        err_unexpectd_input(Coercion::Object(prop_coercions), value)
93    }
94}
95
96fn ok_value(js_value: JsValue) -> CoercionResult {
97    Ok(js_value)
98}
99
100fn err_unexpectd_input(coercion: Coercion, input: JsValue) -> CoercionResult {
101    Err(CoercionError::UnexpectedInput { coercion, input })
102}