extern crate nom; use crate::renderer::CompareContextElement; use parser::Filter; use parser::OwnedLiteral; use parser::Template; use renderer::compile_template; use renderer::Castable; use renderer::CompileError; use renderer::ContextElement; use renderer::DustRenderer; use renderer::IceResult; use renderer::IntoContextElement; use renderer::Loopable; use renderer::MathNumber; use renderer::RenderError; use renderer::Renderable; use renderer::Truthiness; use renderer::WalkError; use renderer::Walkable; use std::cmp::Ordering; use std::convert::TryInto; use std::env; use std::fs; use std::io::{self, Read}; use std::path::Path; mod parser; mod renderer; fn main() { let context = read_context_from_stdin(); let argv: Vec = env::args().collect(); if argv.len() < 2 { panic!("Need to pass templates"); } let template_paths = &argv[1..]; let template_contents: Vec<(String, String)> = template_paths .iter() .map(|p| { let template_content = fs::read_to_string(&p).unwrap(); (p.to_string(), template_content) }) .collect(); let compiled_templates_result: Result, CompileError> = template_contents .iter() .map(|(p, contents)| template_from_file(p, contents)) .collect(); let compiled_templates = compiled_templates_result.unwrap(); let main_template_name = &compiled_templates .first() .expect("There should be more than 1 template") .0; let mut dust_renderer = DustRenderer::new(); compiled_templates.iter().for_each(|(name, template)| { dust_renderer.load_source(template, name.to_owned()); }); println!( "{}", dust_renderer .render(main_template_name, Some(&context)) .expect("Failed to render") ); } fn template_from_file<'a>( file_path: &str, file_contents: &'a str, ) -> Result<(String, Template<'a>), CompileError> { let path: &Path = Path::new(file_path); let name = path.file_stem().ok_or(CompileError { message: format!("Failed to get file stem on {}", file_path), })?; Ok(( name.to_string_lossy().to_string(), compile_template(file_contents)?, )) } fn read_context_from_stdin() -> serde_json::Value { let mut buffer = String::new(); io::stdin() .read_to_string(&mut buffer) .expect("Failed to read stdin"); serde_json::from_str(&buffer).expect("Failed to parse json") } fn html_escape(inp: &str) -> String { // Adding 10% space from the original to avoid re-allocations by // leaving room for escaped sequences. let mut output = String::with_capacity(((inp.len() as f64) * 1.1) as usize); inp.chars().for_each(|c| match c { '<' => output.push_str("<"), '>' => output.push_str(">"), '"' => output.push_str("""), '\'' => output.push_str("'"), '&' => output.push_str("&"), _ => output.push(c), }); output } fn javascript_escape(inp: &str) -> String { // Adding 10% space from the original to avoid re-allocations by // leaving room for escaped sequences. let mut output = String::with_capacity(((inp.len() as f64) * 1.1) as usize); inp.chars().for_each(|c| match c { '"' => output.push_str(r#"\""#), '\'' => output.push_str(r#"\'"#), '\t' => output.push_str(r#"\t"#), '\x0C' => output.push_str(r#"\f"#), '\n' => output.push_str(r#"\n"#), '\r' => output.push_str(r#"\r"#), '\\' => output.push_str(r#"\\"#), '/' => output.push_str(r#"\/"#), _ => output.push(c), }); output } fn get_utf8_hex(inp: char) -> String { let num_bytes = inp.len_utf8(); let mut byte_buffer = [0; 4]; // UTF-8 supports up to 4 bytes per codepoint let mut output = String::with_capacity(num_bytes * 2); inp.encode_utf8(&mut byte_buffer); for b in &byte_buffer[..num_bytes] { output.push_str(&format!("{:02X}", b)); } output } fn encode_uri(inp: &str) -> String { // Adding 10% space from the original to avoid re-allocations by // leaving room for escaped sequences. let mut output = String::with_capacity(((inp.len() as f64) * 1.1) as usize); inp.chars().for_each(|c| match c { '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' | ';' | ',' | '/' | '?' | ':' | '@' | '&' | '=' | '+' | '$' | '-' | '_' | '.' | '!' | '~' | '*' | '\'' | '(' | ')' | '#' => output.push(c), _ => { output.push('%'); output.push_str(&get_utf8_hex(c)); } }); output } fn encode_uri_component(inp: &str) -> String { // Adding 10% space from the original to avoid re-allocations by // leaving room for escaped sequences. let mut output = String::with_capacity(((inp.len() as f64) * 1.1) as usize); inp.chars().for_each(|c| match c { '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' | '-' | '_' | '.' | '!' | '~' | '*' | '\'' | '(' | ')' => { output.push(c) } _ => { output.push('%'); output.push_str(&get_utf8_hex(c)); } }); output } fn apply_filter( json_value: &serde_json::Value, filter: &Filter, ) -> Result { match (json_value, filter) { // Html escape filter (serde_json::Value::String(string), Filter::HtmlEncode) => { Ok(serde_json::Value::String(html_escape(string))) } (_, Filter::HtmlEncode) => Ok(serde_json::Value::String(html_escape( &json_value.render(&Vec::new())?, ))), // Disable html escape filter (_, Filter::DisableHtmlEncode) => panic!("The |s filter is automatically removed by the renderer since it is a no-op during rendering."), // Parse JSON filter (serde_json::Value::String(string), Filter::JsonParse) => { serde_json::from_str(&string).or(Err(RenderError::InvalidJson(string.to_owned()))) } (_, Filter::JsonParse) => { let rendered_value = json_value.render(&Vec::new())?; serde_json::from_str(&rendered_value).or(Err(RenderError::InvalidJson(rendered_value))) } // Json Stringify filter (_, Filter::JsonStringify) => { Ok(serde_json::Value::String(json_value.to_string())) } // Javascript escape filter (serde_json::Value::String(string), Filter::JavascriptStringEncode) => { Ok(serde_json::Value::String(javascript_escape(string))) } (serde_json::Value::Bool(boolean), Filter::JavascriptStringEncode) => { Ok(serde_json::Value::Bool(*boolean)) } (serde_json::Value::Number(number), Filter::JavascriptStringEncode) => { Ok(serde_json::Value::Number(number.clone())) } (serde_json::Value::Array(arr), Filter::JavascriptStringEncode) => { Ok(serde_json::Value::Array(arr.clone())) } (serde_json::Value::Object(obj), Filter::JavascriptStringEncode) => { Ok(serde_json::Value::Object(obj.clone())) } (_, Filter::JavascriptStringEncode) => Ok(serde_json::Value::String(javascript_escape( &json_value.render(&Vec::new())?, ))), // EncodeURI filter (serde_json::Value::String(string), Filter::EncodeUri) => { Ok(serde_json::Value::String(encode_uri(string))) } (_, Filter::EncodeUri) => Ok(serde_json::Value::String(encode_uri( &json_value.render(&Vec::new())?, ))), // EncodeURIComponent filter (serde_json::Value::String(string), Filter::EncodeUriComponent) => { Ok(serde_json::Value::String(encode_uri_component(string))) } (_, Filter::EncodeUriComponent) => Ok(serde_json::Value::String(encode_uri_component( &json_value.render(&Vec::new())?, ))), } } fn apply_filters( json_value: &serde_json::Value, filters: &[Filter], ) -> Result { let mut final_value: serde_json::Value = apply_filter(json_value, &filters[0])?; for filter in &filters[1..] { final_value = apply_filter(&final_value, filter)?; } Ok(final_value) } impl ContextElement for serde_json::Value {} impl Truthiness for serde_json::Value { fn is_truthy(&self) -> bool { match self { serde_json::Value::Null => false, serde_json::Value::Bool(boolean) => *boolean, serde_json::Value::Number(_num) => true, serde_json::Value::String(string_value) => !string_value.is_empty(), serde_json::Value::Array(array_value) => !array_value.is_empty(), serde_json::Value::Object(_obj) => true, } } } impl Renderable for serde_json::Value { fn render(&self, _filters: &Vec) -> Result { let after_apply = if _filters.is_empty() { None } else { Some(apply_filters(self, _filters)?) }; match after_apply.as_ref().unwrap_or(self) { serde_json::Value::Null => Ok("".to_owned()), serde_json::Value::Bool(boolean) => Ok(boolean.to_string()), serde_json::Value::Number(num) => Ok(num.to_string()), serde_json::Value::String(string) => Ok(string.to_string()), serde_json::Value::Array(arr) => { let rendered: Result, RenderError> = arr.iter().map(|val| val.render(&Vec::new())).collect(); let rendered_slice: &[String] = &rendered?; Ok(rendered_slice.join(",")) } serde_json::Value::Object(_obj) => Ok("[object Object]".to_owned()), } } } impl Walkable for serde_json::Value { fn walk(&self, segment: &str) -> Result<&dyn IntoContextElement, WalkError> { match self { serde_json::Value::Null => Err(WalkError::CantWalk), serde_json::Value::Bool(_boolean) => Err(WalkError::CantWalk), serde_json::Value::Number(_num) => Err(WalkError::CantWalk), serde_json::Value::String(_string) => Err(WalkError::CantWalk), serde_json::Value::Array(_arr) => Err(WalkError::CantWalk), serde_json::Value::Object(obj) => obj .get(segment) .map(|val| val as _) .ok_or(WalkError::CantWalk), } } } impl Loopable for serde_json::Value { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { match self { serde_json::Value::Array(array_value) => array_value.iter().map(|x| x as _).collect(), _ => Vec::new(), } } } impl Castable for serde_json::Value { fn cast_to_type<'a>(&'a self, target: &str) -> Option> { match (self, target) { (serde_json::Value::String(text), "number") => text .parse::() .map(|num| IceResult::from_owned(OwnedLiteral::LPositiveInteger(num))) .or_else(|_| { text.parse::() .map(|num| IceResult::from_owned(OwnedLiteral::LNegativeInteger(num))) }) .or_else(|_| { text.parse::() .map(|num| IceResult::from_owned(OwnedLiteral::LFloat(num))) }) .ok(), (serde_json::Value::Number(_), "number") => Some(IceResult::from_borrowed(self)), (_, _) => panic!("Unimplemented cast"), } } } impl CompareContextElement for serde_json::Value { fn equals(&self, other: &dyn ContextElement) -> bool { // println!("Json equality check {:?} == {:?}", self, other); // Handle other serde_json::Value match other.to_any().downcast_ref::() { None => (), Some(other_json_value) => match (self, other_json_value) { // Non-scalar values not caught in the renderer by the // identical-path shortcut are always not equal. (serde_json::Value::Array(_), _) | (_, serde_json::Value::Array(_)) | (serde_json::Value::Object(_), _) | (_, serde_json::Value::Object(_)) => return false, _ => return self == other_json_value, }, } // Handle literals match other.to_any().downcast_ref::() { None => (), Some(OwnedLiteral::LString(other_string)) => { return self.as_str().map_or(false, |s| s == other_string) } Some(OwnedLiteral::LPositiveInteger(other_num)) => { let other_json_num: serde_json::Number = std::convert::From::from(*other_num); return self .equals(&serde_json::Value::Number(other_json_num) as &dyn ContextElement); } Some(OwnedLiteral::LNegativeInteger(other_num)) => { let other_json_num: serde_json::Number = std::convert::From::from(*other_num); return self .equals(&serde_json::Value::Number(other_json_num) as &dyn ContextElement); } Some(OwnedLiteral::LFloat(other_num)) => match self.as_f64() { None => return false, Some(self_float) => return self_float == *other_num, }, } false } fn partial_compare(&self, other: &dyn ContextElement) -> Option { // Handle type coerced objects // When doing a greater than or less than comparison, // javascript coerces objects into "[object Object]". if let serde_json::Value::Object(_) = self { return OwnedLiteral::LString(self.render(&Vec::new()).unwrap_or("".to_owned())) .partial_compare(other); } // When doing a greater than or less than comparison // javascript turns arrays into strings. if let serde_json::Value::Array(_) = self { return OwnedLiteral::LString(self.render(&Vec::new()).unwrap_or("".to_owned())) .partial_compare(other); } let maybe_json_other = other.to_any().downcast_ref::(); let maybe_literal_other = other.to_any().downcast_ref::(); // If they're both strings, compare them directly match (self, maybe_json_other, maybe_literal_other) { // If they're both strings, compare them directly ( serde_json::Value::String(self_string), Some(serde_json::Value::String(other_string)), _, ) => return self_string.partial_cmp(&other_string), ( serde_json::Value::String(self_string), _, Some(OwnedLiteral::LString(other_string)), ) => return self_string.partial_cmp(&other_string), // Otherwise, convert to numbers are compare them that way (_, Some(json_other), _) => return compare_json_numbers(self, json_other), (_, _, Some(literal_other)) => return compare_json_numbers(self, literal_other), _ => panic!("Unimplemented comparison type."), } } fn math_add<'a>(&self, other: &dyn ContextElement) -> Option> { let other_json = other.to_any().downcast_ref::(); let other_literal = other.to_any().downcast_ref::(); match (self, other_json, other_literal) { // If its neither of those types, then it is unimplemented (_, None, None) => panic!("Math operation on unimplemented type"), // Since this is specifically for the math helper, non-primitives are not supported (serde_json::Value::Array(_), _, _) | (serde_json::Value::Object(_), _, _) | (_, Some(serde_json::Value::Array(_)), _) | (_, Some(serde_json::Value::Object(_)), _) => None, // Strings also are ignored because this is specifically a math function (serde_json::Value::String(_), _, _) | (_, Some(serde_json::Value::String(_)), _) | (_, _, Some(OwnedLiteral::LString(_))) => None, // Handle other serde_json::Value (_, Some(other_json_value), _) => (std::convert::Into::::into(self) + std::convert::Into::::into(other_json_value)) .map(IceResult::from_owned), // Handle literals (_, _, Some(other_literal)) => (std::convert::Into::::into(self) + std::convert::Into::::into(other_literal)) .map(IceResult::from_owned), } } fn math_subtract<'a>(&self, other: &dyn ContextElement) -> Option> { let other_json = other.to_any().downcast_ref::(); let other_literal = other.to_any().downcast_ref::(); match (self, other_json, other_literal) { // If its neither of those types, then it is unimplemented (_, None, None) => panic!("Math operation on unimplemented type"), // Since this is specifically for the math helper, non-primitives are not supported (serde_json::Value::Array(_), _, _) | (serde_json::Value::Object(_), _, _) | (_, Some(serde_json::Value::Array(_)), _) | (_, Some(serde_json::Value::Object(_)), _) => None, // Strings also are ignored because this is specifically a math function (serde_json::Value::String(_), _, _) | (_, Some(serde_json::Value::String(_)), _) | (_, _, Some(OwnedLiteral::LString(_))) => None, // Handle other serde_json::Value (_, Some(other_json_value), _) => (std::convert::Into::::into(self) - std::convert::Into::::into(other_json_value)) .map(IceResult::from_owned), // Handle literals (_, _, Some(other_literal)) => (std::convert::Into::::into(self) - std::convert::Into::::into(other_literal)) .map(IceResult::from_owned), } } fn math_multiply<'a>(&self, other: &dyn ContextElement) -> Option> { let other_json = other.to_any().downcast_ref::(); let other_literal = other.to_any().downcast_ref::(); match (self, other_json, other_literal) { // If its neither of those types, then it is unimplemented (_, None, None) => panic!("Math operation on unimplemented type"), // Since this is specifically for the math helper, non-primitives are not supported (serde_json::Value::Array(_), _, _) | (serde_json::Value::Object(_), _, _) | (_, Some(serde_json::Value::Array(_)), _) | (_, Some(serde_json::Value::Object(_)), _) => None, // Strings also are ignored because this is specifically a math function (serde_json::Value::String(_), _, _) | (_, Some(serde_json::Value::String(_)), _) | (_, _, Some(OwnedLiteral::LString(_))) => None, // Handle other serde_json::Value (_, Some(other_json_value), _) => (std::convert::Into::::into(self) * std::convert::Into::::into(other_json_value)) .map(IceResult::from_owned), // Handle literals (_, _, Some(other_literal)) => (std::convert::Into::::into(self) * std::convert::Into::::into(other_literal)) .map(IceResult::from_owned), } } fn math_divide<'a>(&self, other: &dyn ContextElement) -> Option> { let other_json = other.to_any().downcast_ref::(); let other_literal = other.to_any().downcast_ref::(); match (self, other_json, other_literal) { // If its neither of those types, then it is unimplemented (_, None, None) => panic!("Math operation on unimplemented type"), // Since this is specifically for the math helper, non-primitives are not supported (serde_json::Value::Array(_), _, _) | (serde_json::Value::Object(_), _, _) | (_, Some(serde_json::Value::Array(_)), _) | (_, Some(serde_json::Value::Object(_)), _) => None, // Strings also are ignored because this is specifically a math function (serde_json::Value::String(_), _, _) | (_, Some(serde_json::Value::String(_)), _) | (_, _, Some(OwnedLiteral::LString(_))) => None, // Handle other serde_json::Value (_, Some(other_json_value), _) => (std::convert::Into::::into(self) / std::convert::Into::::into(other_json_value)) .map(IceResult::from_owned), // Handle literals (_, _, Some(other_literal)) => (std::convert::Into::::into(self) / std::convert::Into::::into(other_literal)) .map(IceResult::from_owned), } } fn math_modulus<'a>(&self, other: &dyn ContextElement) -> Option> { let other_json = other.to_any().downcast_ref::(); let other_literal = other.to_any().downcast_ref::(); match (self, other_json, other_literal) { // If its neither of those types, then it is unimplemented (_, None, None) => panic!("Math operation on unimplemented type"), // Since this is specifically for the math helper, non-primitives are not supported (serde_json::Value::Array(_), _, _) | (serde_json::Value::Object(_), _, _) | (_, Some(serde_json::Value::Array(_)), _) | (_, Some(serde_json::Value::Object(_)), _) => None, // Strings also are ignored because this is specifically a math function (serde_json::Value::String(_), _, _) | (_, Some(serde_json::Value::String(_)), _) | (_, _, Some(OwnedLiteral::LString(_))) => None, // Handle other serde_json::Value (_, Some(other_json_value), _) => (std::convert::Into::::into(self) % std::convert::Into::::into(other_json_value)) .map(IceResult::from_owned), // Handle literals (_, _, Some(other_literal)) => (std::convert::Into::::into(self) % std::convert::Into::::into(other_literal)) .map(IceResult::from_owned), } } fn math_abs<'a>(&self) -> Option> { std::convert::Into::::into(self) .math_abs() .map(IceResult::from_owned) } fn math_floor<'a>(&self) -> Option> { std::convert::Into::::into(self) .math_floor() .map(IceResult::from_owned) } fn math_ceil<'a>(&self) -> Option> { std::convert::Into::::into(self) .math_ceil() .map(IceResult::from_owned) } } #[derive(Debug)] enum ComparisonNumber { UnsignedInteger(u64), SignedInteger(i64), Decimal(f64), Failure, } impl From<&serde_json::Value> for ComparisonNumber { /// Convert from a JSON value to a number for comparison based on /// the logic described at /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Greater_than fn from(original: &serde_json::Value) -> Self { match original { serde_json::Value::Null => ComparisonNumber::UnsignedInteger(0), serde_json::Value::Bool(boolean) => { if *boolean { ComparisonNumber::UnsignedInteger(1) } else { ComparisonNumber::UnsignedInteger(0) } } serde_json::Value::Number(num) => num.into(), serde_json::Value::String(text) => text.into(), serde_json::Value::Array(_) => { panic!("Only primitives should be cast to numbers for comparisons") } serde_json::Value::Object(_) => { panic!("Only primitives should be cast to numbers for comparisons") } } } } impl From<&String> for ComparisonNumber { fn from(original: &String) -> Self { match original.parse::() { Ok(num) => return ComparisonNumber::UnsignedInteger(num), Err(_) => (), }; match original.parse::() { Ok(num) => return ComparisonNumber::SignedInteger(num), Err(_) => (), }; match original.parse::() { Ok(num) => return ComparisonNumber::Decimal(num), Err(_) => (), }; ComparisonNumber::Failure } } impl From<&serde_json::Number> for ComparisonNumber { fn from(original: &serde_json::Number) -> Self { match original.as_u64() { Some(num) => return ComparisonNumber::UnsignedInteger(num), None => (), }; match original.as_i64() { Some(num) => return ComparisonNumber::SignedInteger(num), None => (), }; match original.as_f64() { Some(num) => return ComparisonNumber::Decimal(num), None => (), }; ComparisonNumber::Failure } } impl From<&OwnedLiteral> for ComparisonNumber { fn from(original: &OwnedLiteral) -> Self { match original { OwnedLiteral::LPositiveInteger(num) => ComparisonNumber::UnsignedInteger(*num), OwnedLiteral::LNegativeInteger(num) => ComparisonNumber::SignedInteger(*num), OwnedLiteral::LString(text) => text.into(), OwnedLiteral::LFloat(num) => { if num.is_nan() { ComparisonNumber::Failure } else { ComparisonNumber::Decimal(*num) } } } } } /// Compare json numbers /// /// While this function can be called with two strings, it would not /// make sense because javascript does not use numeric comparisons for /// strings fn compare_json_numbers(self_input: S, other_input: O) -> Option where S: Into, O: Into, { let self_number: ComparisonNumber = self_input.into(); let other_number: ComparisonNumber = other_input.into(); match (self_number, other_number) { (ComparisonNumber::Failure, _) => return None, (_, ComparisonNumber::Failure) => return None, ( ComparisonNumber::UnsignedInteger(self_num), ComparisonNumber::UnsignedInteger(other_num), ) => return self_num.partial_cmp(&other_num), ( ComparisonNumber::UnsignedInteger(self_num), ComparisonNumber::SignedInteger(other_num), ) => { if self_num < std::i64::MAX as u64 { return (self_num as i64).partial_cmp(&other_num); } else { return Some(Ordering::Greater); } } (ComparisonNumber::UnsignedInteger(self_num), ComparisonNumber::Decimal(other_num)) => { return (self_num as f64).partial_cmp(&other_num) } ( ComparisonNumber::SignedInteger(self_num), ComparisonNumber::UnsignedInteger(other_num), ) => { if other_num < std::i64::MAX as u64 { return self_num.partial_cmp(&(other_num as i64)); } else { return Some(Ordering::Less); } } (ComparisonNumber::SignedInteger(self_num), ComparisonNumber::SignedInteger(other_num)) => { return self_num.partial_cmp(&other_num) } (ComparisonNumber::SignedInteger(self_num), ComparisonNumber::Decimal(other_num)) => { return (self_num as f64).partial_cmp(&other_num) } (ComparisonNumber::Decimal(self_num), ComparisonNumber::UnsignedInteger(other_num)) => { return self_num.partial_cmp(&(other_num as f64)) } (ComparisonNumber::Decimal(self_num), ComparisonNumber::SignedInteger(other_num)) => { return self_num.partial_cmp(&(other_num as f64)) } (ComparisonNumber::Decimal(self_num), ComparisonNumber::Decimal(other_num)) => { return self_num.partial_cmp(&other_num) } } } impl From<&serde_json::Value> for MathNumber { fn from(original: &serde_json::Value) -> Self { match original { serde_json::Value::Null => MathNumber::Integer(0), serde_json::Value::Bool(boolean) => { if *boolean { MathNumber::Integer(1) } else { MathNumber::Integer(0) } } serde_json::Value::Number(num) => num.into(), serde_json::Value::String(_) => { panic!("Strings should not be cast to numbers for math") } serde_json::Value::Array(_) => { panic!("Only primitives should be cast to numbers for comparisons") } serde_json::Value::Object(_) => { panic!("Only primitives should be cast to numbers for comparisons") } } } } impl From<&serde_json::Number> for MathNumber { fn from(original: &serde_json::Number) -> Self { match original.as_u64() { Some(num) => return MathNumber::Integer(num.try_into().unwrap()), None => (), }; match original.as_i64() { Some(num) => return MathNumber::Integer(num.into()), None => (), }; match original.as_f64() { Some(num) => return MathNumber::Decimal(num), None => (), }; MathNumber::Failure } } #[cfg(test)] mod tests { use super::*; #[test] fn test_nested_array_render() { let x: serde_json::Value = serde_json::from_str(r#"[3,5,[7,9]]"#).expect("Failed to parse json"); assert_eq!( x.render(&Vec::new()), Ok::<_, RenderError>("3,5,7,9".to_owned()) ); } #[test] fn test_html_escape() { assert_eq!(html_escape("<>&\"'"), "<>&"'".to_owned()) } }