duster/src/bin.rs

777 lines
31 KiB
Rust
Raw Normal View History

extern crate nom;
2020-05-10 23:16:55 +00:00
use crate::renderer::CompareContextElement;
use parser::Filter;
use parser::OwnedLiteral;
use parser::Template;
use renderer::compile_template;
2020-06-13 23:39:31 +00:00
use renderer::Castable;
use renderer::CompileError;
use renderer::ContextElement;
2020-04-11 00:58:55 +00:00
use renderer::DustRenderer;
2020-06-13 21:50:22 +00:00
use renderer::IceResult;
use renderer::IntoContextElement;
use renderer::Loopable;
use renderer::MathNumber;
use renderer::RenderError;
use renderer::Renderable;
use renderer::Truthiness;
2020-05-09 18:18:45 +00:00
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<String> = 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<Vec<(String, Template)>, 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("&lt;"),
'>' => output.push_str("&gt;"),
'"' => output.push_str("&quot;"),
'\'' => output.push_str("&#39;"),
'&' => output.push_str("&amp;"),
_ => output.push(c),
});
output
}
2020-05-23 22:40:09 +00:00
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#"\\"#),
2020-05-23 23:06:09 +00:00
'/' => output.push_str(r#"\/"#),
2020-05-23 22:40:09 +00:00
_ => output.push(c),
});
output
}
2020-05-23 23:06:09 +00:00
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
}
2020-05-23 23:10:02 +00:00
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<serde_json::Value, RenderError> {
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<serde_json::Value, RenderError> {
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<Filter>) -> Result<String, RenderError> {
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<Vec<String>, 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(),
}
}
}
2020-06-13 23:39:31 +00:00
impl Castable for serde_json::Value {
fn cast_to_type<'a>(&'a self, target: &str) -> Option<IceResult<'a>> {
2020-06-13 23:55:40 +00:00
match (self, target) {
(serde_json::Value::String(text), "number") => text
.parse::<u64>()
.map(|num| IceResult::from_owned(OwnedLiteral::LPositiveInteger(num)))
.or_else(|_| {
text.parse::<i64>()
.map(|num| IceResult::from_owned(OwnedLiteral::LNegativeInteger(num)))
})
.or_else(|_| {
text.parse::<f64>()
.map(|num| IceResult::from_owned(OwnedLiteral::LFloat(num)))
})
.ok(),
(serde_json::Value::Number(_), "number") => Some(IceResult::from_borrowed(self)),
(_, _) => panic!("Unimplemented cast"),
}
2020-06-13 23:39:31 +00:00
}
}
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::<Self>() {
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::<OwnedLiteral>() {
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);
}
2020-06-13 20:06:31 +00:00
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<Ordering> {
// 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);
}
2020-06-13 19:31:52 +00:00
let maybe_json_other = other.to_any().downcast_ref::<Self>();
let maybe_literal_other = other.to_any().downcast_ref::<OwnedLiteral>();
// 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."),
}
}
2020-06-13 21:50:22 +00:00
fn math_add<'a>(&self, other: &dyn ContextElement) -> Option<IceResult<'a>> {
let other_json = other.to_any().downcast_ref::<Self>();
let other_literal = other.to_any().downcast_ref::<OwnedLiteral>();
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::<MathNumber>::into(self)
+ std::convert::Into::<MathNumber>::into(other_json_value))
.map(IceResult::from_owned),
// Handle literals
(_, _, Some(other_literal)) => (std::convert::Into::<MathNumber>::into(self)
+ std::convert::Into::<MathNumber>::into(other_literal))
.map(IceResult::from_owned),
}
2020-06-13 21:50:22 +00:00
}
2020-06-14 02:12:00 +00:00
fn math_subtract<'a>(&self, other: &dyn ContextElement) -> Option<IceResult<'a>> {
let other_json = other.to_any().downcast_ref::<Self>();
let other_literal = other.to_any().downcast_ref::<OwnedLiteral>();
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::<MathNumber>::into(self)
- std::convert::Into::<MathNumber>::into(other_json_value))
.map(IceResult::from_owned),
// Handle literals
(_, _, Some(other_literal)) => (std::convert::Into::<MathNumber>::into(self)
- std::convert::Into::<MathNumber>::into(other_literal))
.map(IceResult::from_owned),
}
2020-06-14 02:12:00 +00:00
}
fn math_multiply<'a>(&self, other: &dyn ContextElement) -> Option<IceResult<'a>> {
let other_json = other.to_any().downcast_ref::<Self>();
let other_literal = other.to_any().downcast_ref::<OwnedLiteral>();
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::<MathNumber>::into(self)
* std::convert::Into::<MathNumber>::into(other_json_value))
.map(IceResult::from_owned),
// Handle literals
(_, _, Some(other_literal)) => (std::convert::Into::<MathNumber>::into(self)
* std::convert::Into::<MathNumber>::into(other_literal))
.map(IceResult::from_owned),
}
2020-06-14 02:12:00 +00:00
}
fn math_divide<'a>(&self, other: &dyn ContextElement) -> Option<IceResult<'a>> {
let other_json = other.to_any().downcast_ref::<Self>();
let other_literal = other.to_any().downcast_ref::<OwnedLiteral>();
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::<MathNumber>::into(self)
/ std::convert::Into::<MathNumber>::into(other_json_value))
.map(IceResult::from_owned),
// Handle literals
(_, _, Some(other_literal)) => (std::convert::Into::<MathNumber>::into(self)
/ std::convert::Into::<MathNumber>::into(other_literal))
.map(IceResult::from_owned),
}
2020-06-14 02:12:00 +00:00
}
fn math_modulus<'a>(&self, other: &dyn ContextElement) -> Option<IceResult<'a>> {
let other_json = other.to_any().downcast_ref::<Self>();
let other_literal = other.to_any().downcast_ref::<OwnedLiteral>();
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::<MathNumber>::into(self)
% std::convert::Into::<MathNumber>::into(other_json_value))
.map(IceResult::from_owned),
// Handle literals
(_, _, Some(other_literal)) => (std::convert::Into::<MathNumber>::into(self)
% std::convert::Into::<MathNumber>::into(other_literal))
.map(IceResult::from_owned),
}
2020-06-14 02:12:00 +00:00
}
fn math_abs<'a>(&self) -> Option<IceResult<'a>> {
std::convert::Into::<MathNumber>::into(self)
.math_abs()
.map(IceResult::from_owned)
2020-06-14 02:12:00 +00:00
}
fn math_floor<'a>(&self) -> Option<IceResult<'a>> {
std::convert::Into::<MathNumber>::into(self)
.math_floor()
.map(IceResult::from_owned)
2020-06-14 02:12:00 +00:00
}
fn math_ceil<'a>(&self) -> Option<IceResult<'a>> {
std::convert::Into::<MathNumber>::into(self)
.math_ceil()
.map(IceResult::from_owned)
2020-06-14 02:12:00 +00:00
}
}
2020-05-17 04:27:21 +00:00
#[derive(Debug)]
2020-06-13 23:15:51 +00:00
enum ComparisonNumber {
2020-05-17 04:27:21 +00:00
UnsignedInteger(u64),
SignedInteger(i64),
Decimal(f64),
Failure,
}
2020-06-13 23:15:51 +00:00
impl From<&serde_json::Value> for ComparisonNumber {
2020-06-13 19:31:52 +00:00
/// 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 {
2020-06-13 23:15:51 +00:00
serde_json::Value::Null => ComparisonNumber::UnsignedInteger(0),
2020-06-13 19:31:52 +00:00
serde_json::Value::Bool(boolean) => {
if *boolean {
2020-06-13 23:15:51 +00:00
ComparisonNumber::UnsignedInteger(1)
2020-06-13 19:31:52 +00:00
} else {
2020-06-13 23:15:51 +00:00
ComparisonNumber::UnsignedInteger(0)
2020-06-13 19:31:52 +00:00
}
}
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")
}
}
}
}
2020-06-13 23:15:51 +00:00
impl From<&String> for ComparisonNumber {
2020-05-17 04:27:21 +00:00
fn from(original: &String) -> Self {
match original.parse::<u64>() {
2020-06-13 23:15:51 +00:00
Ok(num) => return ComparisonNumber::UnsignedInteger(num),
2020-05-17 04:27:21 +00:00
Err(_) => (),
};
match original.parse::<i64>() {
2020-06-13 23:15:51 +00:00
Ok(num) => return ComparisonNumber::SignedInteger(num),
2020-05-17 04:27:21 +00:00
Err(_) => (),
};
match original.parse::<f64>() {
2020-06-13 23:15:51 +00:00
Ok(num) => return ComparisonNumber::Decimal(num),
2020-05-17 04:27:21 +00:00
Err(_) => (),
};
2020-06-13 23:15:51 +00:00
ComparisonNumber::Failure
2020-05-17 04:27:21 +00:00
}
}
2020-06-13 23:15:51 +00:00
impl From<&serde_json::Number> for ComparisonNumber {
2020-05-17 04:27:21 +00:00
fn from(original: &serde_json::Number) -> Self {
match original.as_u64() {
2020-06-13 23:15:51 +00:00
Some(num) => return ComparisonNumber::UnsignedInteger(num),
2020-05-17 04:27:21 +00:00
None => (),
};
match original.as_i64() {
2020-06-13 23:15:51 +00:00
Some(num) => return ComparisonNumber::SignedInteger(num),
2020-05-17 04:27:21 +00:00
None => (),
};
match original.as_f64() {
2020-06-13 23:15:51 +00:00
Some(num) => return ComparisonNumber::Decimal(num),
2020-05-17 04:27:21 +00:00
None => (),
};
2020-06-13 23:15:51 +00:00
ComparisonNumber::Failure
2020-05-17 04:27:21 +00:00
}
}
2020-06-13 23:15:51 +00:00
impl From<&OwnedLiteral> for ComparisonNumber {
2020-05-17 04:27:21 +00:00
fn from(original: &OwnedLiteral) -> Self {
match original {
2020-06-13 23:15:51 +00:00
OwnedLiteral::LPositiveInteger(num) => ComparisonNumber::UnsignedInteger(*num),
OwnedLiteral::LNegativeInteger(num) => ComparisonNumber::SignedInteger(*num),
2020-05-17 04:27:21 +00:00
OwnedLiteral::LString(text) => text.into(),
2020-06-13 18:55:27 +00:00
OwnedLiteral::LFloat(num) => {
if num.is_nan() {
2020-06-13 23:15:51 +00:00
ComparisonNumber::Failure
2020-06-13 18:55:27 +00:00
} else {
2020-06-13 23:15:51 +00:00
ComparisonNumber::Decimal(*num)
2020-06-13 18:55:27 +00:00
}
}
2020-05-17 04:27:21 +00:00
}
}
}
/// 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<S, O>(self_input: S, other_input: O) -> Option<Ordering>
where
2020-06-13 23:15:51 +00:00
S: Into<ComparisonNumber>,
O: Into<ComparisonNumber>,
2020-05-17 04:27:21 +00:00
{
2020-06-13 23:15:51 +00:00
let self_number: ComparisonNumber = self_input.into();
let other_number: ComparisonNumber = other_input.into();
2020-05-17 04:27:21 +00:00
match (self_number, other_number) {
2020-06-13 23:15:51 +00:00
(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);
}
}
2020-06-13 23:15:51 +00:00
(ComparisonNumber::UnsignedInteger(self_num), ComparisonNumber::Decimal(other_num)) => {
return (self_num as f64).partial_cmp(&other_num)
2020-05-17 04:27:21 +00:00
}
2020-06-13 23:15:51 +00:00
(
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);
}
2020-05-17 04:27:21 +00:00
}
2020-06-13 23:15:51 +00:00
(ComparisonNumber::SignedInteger(self_num), ComparisonNumber::SignedInteger(other_num)) => {
2020-05-17 04:27:21 +00:00
return self_num.partial_cmp(&other_num)
}
2020-06-13 23:15:51 +00:00
(ComparisonNumber::SignedInteger(self_num), ComparisonNumber::Decimal(other_num)) => {
return (self_num as f64).partial_cmp(&other_num)
}
2020-05-17 04:27:21 +00:00
2020-06-13 23:15:51 +00:00
(ComparisonNumber::Decimal(self_num), ComparisonNumber::UnsignedInteger(other_num)) => {
return self_num.partial_cmp(&(other_num as f64))
}
2020-06-13 23:15:51 +00:00
(ComparisonNumber::Decimal(self_num), ComparisonNumber::SignedInteger(other_num)) => {
return self_num.partial_cmp(&(other_num as f64))
}
2020-06-13 23:15:51 +00:00
(ComparisonNumber::Decimal(self_num), ComparisonNumber::Decimal(other_num)) => {
2020-05-17 04:27:21 +00:00
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]
2020-05-17 04:40:55 +00:00
fn test_nested_array_render() {
let x: serde_json::Value =
2020-05-17 04:40:55 +00:00
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("<>&\"'"), "&lt;&gt;&amp;&quot;&#39;".to_owned())
}
}