json_utils/schema_coercion/
coercion.rs1
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}