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