Move the json integration to its own file to keep the serde stuff separate.
This commit is contained in:
parent
a9a83d1b4a
commit
900d929869
@ -5,7 +5,7 @@ authors = ["Tom Alexander <tom@fizz.buzz>"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["json-integration"]
|
||||
json-integration = ["serde", "serde_json"]
|
||||
|
||||
[lib]
|
||||
|
697
src/bin.rs
697
src/bin.rs
@ -1,33 +1,15 @@
|
||||
extern crate nom;
|
||||
|
||||
use crate::renderer::CompareContextElement;
|
||||
use parser::Filter;
|
||||
use parser::OwnedLiteral;
|
||||
use parser::Template;
|
||||
use renderer::compare_json_numbers;
|
||||
use renderer::compile_template;
|
||||
use renderer::Castable;
|
||||
use renderer::ComparisonNumber;
|
||||
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::Sizable;
|
||||
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 integrations;
|
||||
mod parser;
|
||||
mod renderer;
|
||||
|
||||
@ -90,680 +72,3 @@ fn read_context_from_stdin() -> serde_json::Value {
|
||||
|
||||
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<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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sizable for serde_json::Value {
|
||||
fn is_castable(&self) -> bool {
|
||||
match self {
|
||||
serde_json::Value::Null => true,
|
||||
serde_json::Value::Bool(_) => false,
|
||||
serde_json::Value::Number(_) => true,
|
||||
serde_json::Value::String(_) => true,
|
||||
serde_json::Value::Array(_) => true,
|
||||
serde_json::Value::Object(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_size<'a>(&'a self) -> Option<IceResult<'a>> {
|
||||
match self {
|
||||
serde_json::Value::Null => {
|
||||
Some(IceResult::from_owned(OwnedLiteral::LPositiveInteger(0)))
|
||||
}
|
||||
serde_json::Value::Bool(_boolean) => {
|
||||
Some(IceResult::from_owned(OwnedLiteral::LPositiveInteger(0)))
|
||||
}
|
||||
serde_json::Value::Number(_num) => Some(IceResult::from_borrowed(self)),
|
||||
serde_json::Value::String(text) => Some(IceResult::from_owned(
|
||||
OwnedLiteral::LPositiveInteger(text.len().try_into().unwrap()),
|
||||
)),
|
||||
serde_json::Value::Array(arr) => Some(IceResult::from_owned(
|
||||
OwnedLiteral::LPositiveInteger(arr.len().try_into().unwrap()),
|
||||
)),
|
||||
serde_json::Value::Object(obj) => Some(IceResult::from_owned(
|
||||
OwnedLiteral::LPositiveInteger(obj.len().try_into().unwrap()),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Castable for serde_json::Value {
|
||||
fn cast_to_type<'a>(&'a self, target: &str) -> Option<IceResult<'a>> {
|
||||
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)),
|
||||
(serde_json::Value::Null, "number") => {
|
||||
Some(IceResult::from_owned(serde_json::Value::Number(0.into())))
|
||||
}
|
||||
(serde_json::Value::Bool(boolean), "number") => {
|
||||
if *boolean {
|
||||
Some(IceResult::from_owned(serde_json::Value::Number(1.into())))
|
||||
} else {
|
||||
Some(IceResult::from_owned(serde_json::Value::Number(0.into())))
|
||||
}
|
||||
}
|
||||
(serde_json::Value::Array(_), "number") => None,
|
||||
(serde_json::Value::Object(_), "number") => None,
|
||||
|
||||
(serde_json::Value::String(_), "string") => Some(IceResult::from_borrowed(self)),
|
||||
(serde_json::Value::Number(num), "string") => Some(IceResult::from_owned(
|
||||
serde_json::Value::String(num.to_string()),
|
||||
)),
|
||||
(serde_json::Value::Null, "string") => Some(IceResult::from_owned(
|
||||
serde_json::Value::String("null".to_owned()),
|
||||
)),
|
||||
(serde_json::Value::Bool(boolean), "string") => Some(IceResult::from_owned(
|
||||
serde_json::Value::String(boolean.to_string()),
|
||||
)),
|
||||
(serde_json::Value::Array(_), "string") => Some(IceResult::from_owned(
|
||||
serde_json::Value::String(self.render(&Vec::new()).unwrap_or("".to_owned())),
|
||||
)),
|
||||
(serde_json::Value::Object(_), "string") => Some(IceResult::from_owned(
|
||||
serde_json::Value::String(self.render(&Vec::new()).unwrap_or("".to_owned())),
|
||||
)),
|
||||
|
||||
(serde_json::Value::String(text), "boolean") => {
|
||||
if text.is_empty() {
|
||||
Some(IceResult::from_owned(serde_json::Value::Bool(false)))
|
||||
} else {
|
||||
Some(IceResult::from_owned(serde_json::Value::Bool(true)))
|
||||
}
|
||||
}
|
||||
(serde_json::Value::Number(json_num), "boolean") => {
|
||||
Some(IceResult::from_owned(serde_json::Value::Bool(
|
||||
match (json_num.as_u64(), json_num.as_i64(), json_num.as_f64()) {
|
||||
(Some(num), _, _) => num != 0,
|
||||
(_, Some(num), _) => num != 0,
|
||||
(_, _, Some(num)) => num != 0.0 && !num.is_nan(),
|
||||
_ => false,
|
||||
},
|
||||
)))
|
||||
}
|
||||
(serde_json::Value::Null, "boolean") => {
|
||||
Some(IceResult::from_owned(serde_json::Value::Bool(false)))
|
||||
}
|
||||
(serde_json::Value::Bool(_), "boolean") => Some(IceResult::from_borrowed(self)),
|
||||
(serde_json::Value::Array(_), "boolean") => {
|
||||
Some(IceResult::from_owned(serde_json::Value::Bool(true)))
|
||||
}
|
||||
(serde_json::Value::Object(_), "boolean") => {
|
||||
Some(IceResult::from_owned(serde_json::Value::Bool(true)))
|
||||
}
|
||||
|
||||
(_, _) => 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::<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::LBoolean(boolean)) => {
|
||||
return self.equals(&serde_json::Value::Bool(*boolean) as &dyn ContextElement);
|
||||
}
|
||||
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<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);
|
||||
}
|
||||
|
||||
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 and 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<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),
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
fn math_abs<'a>(&self) -> Option<IceResult<'a>> {
|
||||
std::convert::Into::<MathNumber>::into(self)
|
||||
.math_abs()
|
||||
.map(IceResult::from_owned)
|
||||
}
|
||||
|
||||
fn math_floor<'a>(&self) -> Option<IceResult<'a>> {
|
||||
std::convert::Into::<MathNumber>::into(self)
|
||||
.math_floor()
|
||||
.map(IceResult::from_owned)
|
||||
}
|
||||
|
||||
fn math_ceil<'a>(&self) -> Option<IceResult<'a>> {
|
||||
std::convert::Into::<MathNumber>::into(self)
|
||||
.math_ceil()
|
||||
.map(IceResult::from_owned)
|
||||
}
|
||||
}
|
||||
|
||||
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<&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<&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())
|
||||
}
|
||||
}
|
||||
|
@ -4,3 +4,703 @@
|
||||
//! dependency since ContextElement can be implemented for any
|
||||
//! type. Disable the json-integration feature to avoid compiling this
|
||||
//! file and adding the serde and serde_json dependencies.
|
||||
//!
|
||||
//! In order to recreate perfect compatibility with the original
|
||||
//! dustjs implementation, the logic below needs to follow the "rules"
|
||||
//! of javascript logic.
|
||||
use crate::parser::Filter;
|
||||
use crate::parser::OwnedLiteral;
|
||||
use crate::renderer::compare_json_numbers;
|
||||
use crate::renderer::Castable;
|
||||
use crate::renderer::CompareContextElement;
|
||||
use crate::renderer::ComparisonNumber;
|
||||
use crate::renderer::ContextElement;
|
||||
use crate::renderer::IceResult;
|
||||
use crate::renderer::IntoContextElement;
|
||||
use crate::renderer::Loopable;
|
||||
use crate::renderer::MathNumber;
|
||||
use crate::renderer::RenderError;
|
||||
use crate::renderer::Renderable;
|
||||
use crate::renderer::Sizable;
|
||||
use crate::renderer::Truthiness;
|
||||
use crate::renderer::WalkError;
|
||||
use crate::renderer::Walkable;
|
||||
use std::cmp::Ordering;
|
||||
use std::convert::TryInto;
|
||||
|
||||
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<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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sizable for serde_json::Value {
|
||||
fn is_castable(&self) -> bool {
|
||||
match self {
|
||||
serde_json::Value::Null => true,
|
||||
serde_json::Value::Bool(_) => false,
|
||||
serde_json::Value::Number(_) => true,
|
||||
serde_json::Value::String(_) => true,
|
||||
serde_json::Value::Array(_) => true,
|
||||
serde_json::Value::Object(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_size<'a>(&'a self) -> Option<IceResult<'a>> {
|
||||
match self {
|
||||
serde_json::Value::Null => {
|
||||
Some(IceResult::from_owned(OwnedLiteral::LPositiveInteger(0)))
|
||||
}
|
||||
serde_json::Value::Bool(_boolean) => {
|
||||
Some(IceResult::from_owned(OwnedLiteral::LPositiveInteger(0)))
|
||||
}
|
||||
serde_json::Value::Number(_num) => Some(IceResult::from_borrowed(self)),
|
||||
serde_json::Value::String(text) => Some(IceResult::from_owned(
|
||||
OwnedLiteral::LPositiveInteger(text.len().try_into().unwrap()),
|
||||
)),
|
||||
serde_json::Value::Array(arr) => Some(IceResult::from_owned(
|
||||
OwnedLiteral::LPositiveInteger(arr.len().try_into().unwrap()),
|
||||
)),
|
||||
serde_json::Value::Object(obj) => Some(IceResult::from_owned(
|
||||
OwnedLiteral::LPositiveInteger(obj.len().try_into().unwrap()),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Castable for serde_json::Value {
|
||||
fn cast_to_type<'a>(&'a self, target: &str) -> Option<IceResult<'a>> {
|
||||
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)),
|
||||
(serde_json::Value::Null, "number") => {
|
||||
Some(IceResult::from_owned(serde_json::Value::Number(0.into())))
|
||||
}
|
||||
(serde_json::Value::Bool(boolean), "number") => {
|
||||
if *boolean {
|
||||
Some(IceResult::from_owned(serde_json::Value::Number(1.into())))
|
||||
} else {
|
||||
Some(IceResult::from_owned(serde_json::Value::Number(0.into())))
|
||||
}
|
||||
}
|
||||
(serde_json::Value::Array(_), "number") => None,
|
||||
(serde_json::Value::Object(_), "number") => None,
|
||||
|
||||
(serde_json::Value::String(_), "string") => Some(IceResult::from_borrowed(self)),
|
||||
(serde_json::Value::Number(num), "string") => Some(IceResult::from_owned(
|
||||
serde_json::Value::String(num.to_string()),
|
||||
)),
|
||||
(serde_json::Value::Null, "string") => Some(IceResult::from_owned(
|
||||
serde_json::Value::String("null".to_owned()),
|
||||
)),
|
||||
(serde_json::Value::Bool(boolean), "string") => Some(IceResult::from_owned(
|
||||
serde_json::Value::String(boolean.to_string()),
|
||||
)),
|
||||
(serde_json::Value::Array(_), "string") => Some(IceResult::from_owned(
|
||||
serde_json::Value::String(self.render(&Vec::new()).unwrap_or("".to_owned())),
|
||||
)),
|
||||
(serde_json::Value::Object(_), "string") => Some(IceResult::from_owned(
|
||||
serde_json::Value::String(self.render(&Vec::new()).unwrap_or("".to_owned())),
|
||||
)),
|
||||
|
||||
(serde_json::Value::String(text), "boolean") => {
|
||||
if text.is_empty() {
|
||||
Some(IceResult::from_owned(serde_json::Value::Bool(false)))
|
||||
} else {
|
||||
Some(IceResult::from_owned(serde_json::Value::Bool(true)))
|
||||
}
|
||||
}
|
||||
(serde_json::Value::Number(json_num), "boolean") => {
|
||||
Some(IceResult::from_owned(serde_json::Value::Bool(
|
||||
match (json_num.as_u64(), json_num.as_i64(), json_num.as_f64()) {
|
||||
(Some(num), _, _) => num != 0,
|
||||
(_, Some(num), _) => num != 0,
|
||||
(_, _, Some(num)) => num != 0.0 && !num.is_nan(),
|
||||
_ => false,
|
||||
},
|
||||
)))
|
||||
}
|
||||
(serde_json::Value::Null, "boolean") => {
|
||||
Some(IceResult::from_owned(serde_json::Value::Bool(false)))
|
||||
}
|
||||
(serde_json::Value::Bool(_), "boolean") => Some(IceResult::from_borrowed(self)),
|
||||
(serde_json::Value::Array(_), "boolean") => {
|
||||
Some(IceResult::from_owned(serde_json::Value::Bool(true)))
|
||||
}
|
||||
(serde_json::Value::Object(_), "boolean") => {
|
||||
Some(IceResult::from_owned(serde_json::Value::Bool(true)))
|
||||
}
|
||||
|
||||
(_, _) => 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::<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::LBoolean(boolean)) => {
|
||||
return self.equals(&serde_json::Value::Bool(*boolean) as &dyn ContextElement);
|
||||
}
|
||||
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<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);
|
||||
}
|
||||
|
||||
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 and 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<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),
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
fn math_abs<'a>(&self) -> Option<IceResult<'a>> {
|
||||
std::convert::Into::<MathNumber>::into(self)
|
||||
.math_abs()
|
||||
.map(IceResult::from_owned)
|
||||
}
|
||||
|
||||
fn math_floor<'a>(&self) -> Option<IceResult<'a>> {
|
||||
std::convert::Into::<MathNumber>::into(self)
|
||||
.math_floor()
|
||||
.map(IceResult::from_owned)
|
||||
}
|
||||
|
||||
fn math_ceil<'a>(&self) -> Option<IceResult<'a>> {
|
||||
std::convert::Into::<MathNumber>::into(self)
|
||||
.math_ceil()
|
||||
.map(IceResult::from_owned)
|
||||
}
|
||||
}
|
||||
|
||||
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<&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<&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())
|
||||
}
|
||||
}
|
||||
|
2
src/integrations/mod.rs
Normal file
2
src/integrations/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
#[cfg(feature = "json-integration")]
|
||||
pub mod json;
|
Loading…
Reference in New Issue
Block a user