2020-04-05 23:12:48 +00:00
|
|
|
extern crate nom;
|
2020-04-10 23:07:02 +00:00
|
|
|
|
2020-05-10 23:16:55 +00:00
|
|
|
use crate::renderer::CompareContextElement;
|
2020-04-13 01:54:15 +00:00
|
|
|
use parser::Filter;
|
2020-05-17 02:30:04 +00:00
|
|
|
use parser::OwnedLiteral;
|
2020-04-11 00:27:27 +00:00
|
|
|
use renderer::compile_template;
|
|
|
|
use renderer::CompiledTemplate;
|
2020-04-12 02:47:31 +00:00
|
|
|
use renderer::ContextElement;
|
2020-04-11 00:58:55 +00:00
|
|
|
use renderer::DustRenderer;
|
2020-04-28 23:34:52 +00:00
|
|
|
use renderer::Loopable;
|
2020-04-12 22:29:40 +00:00
|
|
|
use renderer::RenderError;
|
2020-04-11 23:11:14 +00:00
|
|
|
use renderer::Renderable;
|
2020-05-09 18:18:45 +00:00
|
|
|
use renderer::WalkError;
|
2020-04-12 02:47:31 +00:00
|
|
|
use renderer::Walkable;
|
2020-05-16 17:31:52 +00:00
|
|
|
use std::cmp::Ordering;
|
2020-04-11 00:27:27 +00:00
|
|
|
use std::env;
|
|
|
|
use std::fs;
|
2020-04-10 23:07:02 +00:00
|
|
|
use std::io::{self, Read};
|
2020-04-11 00:27:27 +00:00
|
|
|
use std::path::Path;
|
2020-04-05 23:12:48 +00:00
|
|
|
|
|
|
|
mod parser;
|
2020-04-11 00:27:27 +00:00
|
|
|
mod renderer;
|
2020-04-05 23:12:48 +00:00
|
|
|
|
|
|
|
fn main() {
|
2020-04-10 23:07:02 +00:00
|
|
|
let context = read_context_from_stdin();
|
2020-04-11 00:27:27 +00:00
|
|
|
|
|
|
|
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: Vec<CompiledTemplate> = template_contents
|
|
|
|
.iter()
|
|
|
|
.map(|(p, contents)| template_from_file(p, contents))
|
|
|
|
.collect();
|
2020-04-11 22:25:48 +00:00
|
|
|
let mut dust_renderer = DustRenderer::new();
|
2020-04-11 00:55:44 +00:00
|
|
|
compiled_templates.iter().for_each(|template| {
|
2020-04-11 22:25:48 +00:00
|
|
|
dust_renderer.load_source(template);
|
2020-04-11 00:55:44 +00:00
|
|
|
});
|
2020-04-11 22:25:48 +00:00
|
|
|
let main_template_name = &compiled_templates
|
|
|
|
.first()
|
|
|
|
.expect("There should be more than 1 template")
|
|
|
|
.name;
|
2020-05-05 23:51:07 +00:00
|
|
|
let breadcrumbs = vec![&context as &dyn ContextElement];
|
2020-04-11 22:25:48 +00:00
|
|
|
println!(
|
|
|
|
"{}",
|
|
|
|
dust_renderer
|
2020-05-06 00:46:31 +00:00
|
|
|
.render(main_template_name, &breadcrumbs)
|
2020-04-11 22:25:48 +00:00
|
|
|
.expect("Failed to render")
|
|
|
|
);
|
2020-04-11 00:27:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn template_from_file<'a>(file_path: &str, file_contents: &'a str) -> CompiledTemplate<'a> {
|
|
|
|
let path: &Path = Path::new(file_path);
|
|
|
|
let name = path.file_stem().unwrap();
|
|
|
|
compile_template(file_contents, name.to_string_lossy().to_string())
|
2020-04-11 22:25:48 +00:00
|
|
|
.expect("Failed to compile template")
|
2020-04-10 23:07:02 +00:00
|
|
|
}
|
|
|
|
|
2020-04-12 02:52:20 +00:00
|
|
|
fn read_context_from_stdin() -> serde_json::Value {
|
2020-04-10 23:07:02 +00:00
|
|
|
let mut buffer = String::new();
|
|
|
|
io::stdin()
|
|
|
|
.read_to_string(&mut buffer)
|
|
|
|
.expect("Failed to read stdin");
|
|
|
|
|
2020-05-03 19:29:02 +00:00
|
|
|
serde_json::from_str(&buffer).expect("Failed to parse json")
|
2020-04-05 23:12:48 +00:00
|
|
|
}
|
2020-04-11 23:11:14 +00:00
|
|
|
|
2020-05-23 21:23:48 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-05-23 21:23:48 +00:00
|
|
|
fn apply_filter(
|
|
|
|
json_value: &serde_json::Value,
|
|
|
|
filter: &Filter,
|
|
|
|
) -> Result<serde_json::Value, RenderError> {
|
|
|
|
match (json_value, filter) {
|
2020-05-23 22:14:23 +00:00
|
|
|
// Html escape filter
|
2020-05-23 21:23:48 +00:00
|
|
|
(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())?,
|
|
|
|
))),
|
2020-05-23 22:14:23 +00:00
|
|
|
// 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()))
|
|
|
|
}
|
2020-05-23 22:40:09 +00:00
|
|
|
// Javascript escape filter
|
|
|
|
(serde_json::Value::String(string), Filter::JavascriptStringEncode) => {
|
|
|
|
Ok(serde_json::Value::String(javascript_escape(string)))
|
|
|
|
}
|
2020-05-23 23:34:38 +00:00
|
|
|
(serde_json::Value::Bool(boolean), Filter::JavascriptStringEncode) => {
|
|
|
|
Ok(serde_json::Value::Bool(*boolean))
|
|
|
|
}
|
2020-05-24 02:06:25 +00:00
|
|
|
(serde_json::Value::Number(number), Filter::JavascriptStringEncode) => {
|
|
|
|
Ok(serde_json::Value::Number(number.clone()))
|
|
|
|
}
|
2020-05-23 22:40:09 +00:00
|
|
|
(_, Filter::JavascriptStringEncode) => Ok(serde_json::Value::String(javascript_escape(
|
|
|
|
&json_value.render(&Vec::new())?,
|
|
|
|
))),
|
2020-05-23 23:10:02 +00:00
|
|
|
// EncodeURI filter
|
2020-05-23 23:06:09 +00:00
|
|
|
(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())?,
|
|
|
|
))),
|
2020-05-23 23:10:02 +00:00
|
|
|
// 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())?,
|
|
|
|
))),
|
2020-05-23 21:23:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2020-04-12 02:47:31 +00:00
|
|
|
impl ContextElement for serde_json::Value {}
|
|
|
|
|
2020-04-11 23:11:14 +00:00
|
|
|
impl Renderable for serde_json::Value {
|
2020-04-13 01:57:42 +00:00
|
|
|
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
2020-05-23 21:23:48 +00:00
|
|
|
let after_apply = if _filters.is_empty() {
|
|
|
|
None
|
2020-05-23 22:14:23 +00:00
|
|
|
} else {
|
|
|
|
Some(apply_filters(self, _filters)?)
|
2020-05-23 21:23:48 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
match after_apply.as_ref().unwrap_or(self) {
|
2020-04-13 01:03:55 +00:00
|
|
|
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()),
|
2020-04-13 01:40:34 +00:00
|
|
|
serde_json::Value::Array(arr) => {
|
2020-04-13 01:54:15 +00:00
|
|
|
// TODO: Handle the filters instead of passing a Vec::new()
|
2020-04-13 01:40:34 +00:00
|
|
|
let rendered: Result<Vec<String>, RenderError> =
|
2020-04-13 01:54:15 +00:00
|
|
|
arr.iter().map(|val| val.render(&Vec::new())).collect();
|
2020-04-13 01:40:34 +00:00
|
|
|
let rendered_slice: &[String] = &rendered?;
|
|
|
|
Ok(rendered_slice.join(","))
|
|
|
|
}
|
|
|
|
serde_json::Value::Object(_obj) => Ok("[object Object]".to_owned()),
|
2020-04-11 23:19:40 +00:00
|
|
|
}
|
2020-04-11 23:11:14 +00:00
|
|
|
}
|
|
|
|
}
|
2020-04-12 02:47:31 +00:00
|
|
|
|
|
|
|
impl Walkable for serde_json::Value {
|
2020-05-09 18:18:45 +00:00
|
|
|
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
|
2020-04-12 02:47:31 +00:00
|
|
|
match self {
|
2020-05-09 18:18:45 +00:00
|
|
|
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),
|
2020-04-12 02:47:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-28 23:34:52 +00:00
|
|
|
|
|
|
|
impl Loopable for serde_json::Value {
|
2020-05-09 18:14:22 +00:00
|
|
|
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
2020-04-28 23:34:52 +00:00
|
|
|
match self {
|
2020-05-09 18:18:45 +00:00
|
|
|
serde_json::Value::Null => Vec::new(),
|
2020-04-28 23:34:52 +00:00
|
|
|
serde_json::Value::Bool(boolean) => {
|
|
|
|
if *boolean {
|
2020-05-09 18:18:45 +00:00
|
|
|
vec![self]
|
2020-04-28 23:34:52 +00:00
|
|
|
} else {
|
2020-05-09 18:18:45 +00:00
|
|
|
Vec::new()
|
2020-04-28 23:34:52 +00:00
|
|
|
}
|
|
|
|
}
|
2020-05-09 18:18:45 +00:00
|
|
|
serde_json::Value::Number(_num) => vec![self],
|
2020-04-28 23:34:52 +00:00
|
|
|
serde_json::Value::String(string_value) => {
|
|
|
|
if string_value.is_empty() {
|
2020-05-09 18:18:45 +00:00
|
|
|
Vec::new()
|
2020-04-28 23:34:52 +00:00
|
|
|
} else {
|
2020-05-09 18:18:45 +00:00
|
|
|
vec![self]
|
2020-04-28 23:34:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
serde_json::Value::Array(array_value) => {
|
|
|
|
if array_value.is_empty() {
|
2020-05-09 18:18:45 +00:00
|
|
|
Vec::new()
|
2020-04-28 23:34:52 +00:00
|
|
|
} else {
|
2020-05-09 18:18:45 +00:00
|
|
|
array_value.iter().map(|x| x as _).collect()
|
2020-04-28 23:34:52 +00:00
|
|
|
}
|
|
|
|
}
|
2020-05-09 18:18:45 +00:00
|
|
|
serde_json::Value::Object(_obj) => vec![self],
|
2020-04-28 23:34:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-11 02:04:41 +00:00
|
|
|
|
|
|
|
impl CompareContextElement for serde_json::Value {
|
|
|
|
fn equals(&self, other: &dyn ContextElement) -> bool {
|
2020-05-17 03:19:02 +00:00
|
|
|
// println!("equals json {:?} | {:?}", self, other);
|
2020-05-11 02:26:47 +00:00
|
|
|
// Handle other serde_json::Value
|
|
|
|
match other.to_any().downcast_ref::<Self>() {
|
|
|
|
None => (),
|
2020-05-16 19:30:17 +00:00
|
|
|
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,
|
|
|
|
},
|
2020-05-11 02:26:47 +00:00
|
|
|
}
|
2020-05-17 03:19:02 +00:00
|
|
|
// Handle literals
|
|
|
|
match other.to_any().downcast_ref::<OwnedLiteral>() {
|
2020-05-11 02:26:47 +00:00
|
|
|
None => (),
|
2020-05-17 03:19:02 +00:00
|
|
|
Some(OwnedLiteral::LString(other_string)) => {
|
|
|
|
return self.as_str().map_or(false, |s| s == other_string)
|
|
|
|
}
|
|
|
|
Some(OwnedLiteral::LPositiveInteger(other_num)) => {
|
|
|
|
return self.as_u64().map_or(false, |n| n == *other_num)
|
|
|
|
}
|
2020-05-11 03:11:16 +00:00
|
|
|
}
|
2020-05-11 02:26:47 +00:00
|
|
|
false
|
2020-05-11 02:04:41 +00:00
|
|
|
}
|
2020-05-16 17:31:52 +00:00
|
|
|
|
|
|
|
fn partial_compare(&self, other: &dyn ContextElement) -> Option<Ordering> {
|
2020-05-17 02:30:04 +00:00
|
|
|
// println!("partial_compare json {:?} | {:?}", self, other);
|
2020-05-17 01:30:51 +00:00
|
|
|
// 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 {
|
2020-05-17 19:52:38 +00:00
|
|
|
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-05-17 01:30:51 +00:00
|
|
|
}
|
|
|
|
|
2020-05-16 17:40:56 +00:00
|
|
|
// Handle other serde_json::Value
|
|
|
|
match other.to_any().downcast_ref::<Self>() {
|
|
|
|
None => (),
|
2020-05-16 17:44:34 +00:00
|
|
|
Some(other_json_value) => {
|
|
|
|
return match (self, other_json_value) {
|
2020-05-16 18:08:59 +00:00
|
|
|
(
|
|
|
|
serde_json::Value::Bool(self_boolean),
|
|
|
|
serde_json::Value::Bool(other_boolean),
|
|
|
|
) => self_boolean.partial_cmp(other_boolean),
|
|
|
|
(
|
|
|
|
serde_json::Value::Number(self_number),
|
|
|
|
serde_json::Value::Number(other_number),
|
2020-05-17 04:27:21 +00:00
|
|
|
) => return compare_json_numbers(self_number, other_number),
|
|
|
|
(
|
|
|
|
serde_json::Value::String(self_string),
|
|
|
|
serde_json::Value::Number(other_number),
|
|
|
|
) => return compare_json_numbers(self_string, other_number),
|
|
|
|
(
|
|
|
|
serde_json::Value::Number(self_number),
|
|
|
|
serde_json::Value::String(other_string),
|
|
|
|
) => return compare_json_numbers(self_number, other_string),
|
2020-05-16 18:08:59 +00:00
|
|
|
(
|
|
|
|
serde_json::Value::String(self_string),
|
|
|
|
serde_json::Value::String(other_string),
|
|
|
|
) => self_string.partial_cmp(other_string),
|
2020-05-17 04:27:21 +00:00
|
|
|
(
|
|
|
|
serde_json::Value::Array(self_array),
|
|
|
|
serde_json::Value::Array(other_array),
|
2020-05-17 04:40:55 +00:00
|
|
|
) => {
|
|
|
|
return self
|
|
|
|
.render(&Vec::new())
|
|
|
|
.unwrap_or("".to_owned())
|
|
|
|
.partial_cmp(
|
|
|
|
&other_json_value
|
|
|
|
.render(&Vec::new())
|
|
|
|
.unwrap_or("".to_owned()),
|
|
|
|
)
|
|
|
|
}
|
2020-05-16 17:44:34 +00:00
|
|
|
_ => None,
|
|
|
|
};
|
|
|
|
}
|
2020-05-16 17:40:56 +00:00
|
|
|
}
|
2020-05-17 03:07:05 +00:00
|
|
|
// Handle literals
|
|
|
|
match other.to_any().downcast_ref::<OwnedLiteral>() {
|
2020-05-16 17:40:56 +00:00
|
|
|
None => (),
|
2020-05-17 03:07:05 +00:00
|
|
|
Some(other_literal) => match (self, other_literal) {
|
|
|
|
(serde_json::Value::String(self_string), OwnedLiteral::LString(other_string)) => {
|
2020-05-17 01:30:51 +00:00
|
|
|
return self_string.partial_cmp(other_string)
|
|
|
|
}
|
2020-05-17 03:07:05 +00:00
|
|
|
(
|
|
|
|
serde_json::Value::String(self_string),
|
|
|
|
OwnedLiteral::LPositiveInteger(other_num),
|
2020-05-17 04:27:21 +00:00
|
|
|
) => return compare_json_numbers(self_string, other_literal),
|
|
|
|
(serde_json::Value::Number(self_num), OwnedLiteral::LString(other_string)) => {
|
|
|
|
return compare_json_numbers(self_num, other_string)
|
|
|
|
}
|
2020-05-17 03:07:05 +00:00
|
|
|
(
|
|
|
|
serde_json::Value::Number(self_num),
|
|
|
|
OwnedLiteral::LPositiveInteger(other_num),
|
2020-05-17 04:27:21 +00:00
|
|
|
) => return compare_json_numbers(self_num, other_literal),
|
2020-05-17 03:07:05 +00:00
|
|
|
(serde_json::Value::Array(_), _) => {
|
|
|
|
// TODO
|
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
(serde_json::Value::Object(_), _) => {
|
|
|
|
// TODO
|
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
(serde_json::Value::Bool(_), _) => {
|
|
|
|
// TODO
|
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
(serde_json::Value::Null, _) => {
|
|
|
|
// TODO
|
|
|
|
todo!()
|
2020-05-17 01:30:51 +00:00
|
|
|
}
|
|
|
|
},
|
2020-05-16 17:40:56 +00:00
|
|
|
}
|
2020-05-16 17:31:52 +00:00
|
|
|
None
|
|
|
|
}
|
2020-05-11 02:04:41 +00:00
|
|
|
}
|
2020-05-16 22:15:03 +00:00
|
|
|
|
|
|
|
/// Create a new vec by of references to the serde_json::Values as
|
|
|
|
/// ContextElement trait objects so we can use its implementation of
|
|
|
|
/// PartialOrd.
|
|
|
|
///
|
|
|
|
/// You cannot implement a trait you do not define for a type you do
|
|
|
|
/// not define, so I cannot implement PartialOrd for
|
|
|
|
/// serde_json::value. Instead, I just re-use the PartialOrd
|
|
|
|
/// implementation for ContextElement which unfortunately has extra
|
|
|
|
/// overhead of downcasting. This would be a good spot for
|
|
|
|
/// optimization.
|
|
|
|
fn convert_vec_to_context_element(array: &Vec<serde_json::Value>) -> Vec<&dyn ContextElement> {
|
|
|
|
array.iter().map(|v| v as _).collect()
|
|
|
|
}
|
2020-05-17 01:30:51 +00:00
|
|
|
|
2020-05-17 04:27:21 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum JsonNumber {
|
|
|
|
UnsignedInteger(u64),
|
|
|
|
SignedInteger(i64),
|
|
|
|
Decimal(f64),
|
|
|
|
Failure,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&String> for JsonNumber {
|
|
|
|
fn from(original: &String) -> Self {
|
|
|
|
match original.parse::<u64>() {
|
|
|
|
Ok(num) => return JsonNumber::UnsignedInteger(num),
|
|
|
|
Err(_) => (),
|
|
|
|
};
|
|
|
|
match original.parse::<i64>() {
|
|
|
|
Ok(num) => return JsonNumber::SignedInteger(num),
|
|
|
|
Err(_) => (),
|
|
|
|
};
|
|
|
|
match original.parse::<f64>() {
|
|
|
|
Ok(num) => return JsonNumber::Decimal(num),
|
|
|
|
Err(_) => (),
|
|
|
|
};
|
|
|
|
JsonNumber::Failure
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&serde_json::Number> for JsonNumber {
|
|
|
|
fn from(original: &serde_json::Number) -> Self {
|
|
|
|
match original.as_u64() {
|
|
|
|
Some(num) => return JsonNumber::UnsignedInteger(num),
|
|
|
|
None => (),
|
|
|
|
};
|
|
|
|
match original.as_i64() {
|
|
|
|
Some(num) => return JsonNumber::SignedInteger(num),
|
|
|
|
None => (),
|
|
|
|
};
|
|
|
|
match original.as_f64() {
|
|
|
|
Some(num) => return JsonNumber::Decimal(num),
|
|
|
|
None => (),
|
|
|
|
};
|
|
|
|
JsonNumber::Failure
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&OwnedLiteral> for JsonNumber {
|
|
|
|
fn from(original: &OwnedLiteral) -> Self {
|
|
|
|
match original {
|
|
|
|
OwnedLiteral::LPositiveInteger(num) => JsonNumber::UnsignedInteger(*num),
|
|
|
|
OwnedLiteral::LString(text) => text.into(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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
|
|
|
|
S: Into<JsonNumber>,
|
|
|
|
O: Into<JsonNumber>,
|
|
|
|
{
|
|
|
|
let self_number: JsonNumber = self_input.into();
|
|
|
|
let other_number: JsonNumber = other_input.into();
|
|
|
|
// println!(
|
|
|
|
// "compare_number_and_string {:?} | {:?}",
|
|
|
|
// self_number, other_number
|
|
|
|
// );
|
|
|
|
// TODO: Figure out how javascript compares floats and ints
|
|
|
|
match (self_number, other_number) {
|
|
|
|
(JsonNumber::Failure, _) => return None,
|
|
|
|
(_, JsonNumber::Failure) => return None,
|
|
|
|
(JsonNumber::UnsignedInteger(self_num), JsonNumber::UnsignedInteger(other_num)) => {
|
|
|
|
return self_num.partial_cmp(&other_num)
|
|
|
|
}
|
|
|
|
(JsonNumber::UnsignedInteger(self_num), JsonNumber::SignedInteger(other_num)) => {
|
|
|
|
return Some(Ordering::Greater)
|
|
|
|
}
|
|
|
|
(JsonNumber::UnsignedInteger(self_num), JsonNumber::Decimal(other_num)) => return None,
|
|
|
|
|
|
|
|
(JsonNumber::SignedInteger(self_num), JsonNumber::UnsignedInteger(other_num)) => {
|
|
|
|
return Some(Ordering::Less)
|
|
|
|
}
|
|
|
|
(JsonNumber::SignedInteger(self_num), JsonNumber::SignedInteger(other_num)) => {
|
|
|
|
return self_num.partial_cmp(&other_num)
|
|
|
|
}
|
|
|
|
(JsonNumber::SignedInteger(self_num), JsonNumber::Decimal(other_num)) => return None,
|
|
|
|
|
|
|
|
(JsonNumber::Decimal(self_num), JsonNumber::UnsignedInteger(other_num)) => return None,
|
|
|
|
(JsonNumber::Decimal(self_num), JsonNumber::SignedInteger(other_num)) => return None,
|
|
|
|
(JsonNumber::Decimal(self_num), JsonNumber::Decimal(other_num)) => {
|
|
|
|
return self_num.partial_cmp(&other_num)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2020-05-17 01:30:51 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
2020-05-17 04:40:55 +00:00
|
|
|
fn test_nested_array_render() {
|
2020-05-17 01:30:51 +00:00
|
|
|
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())
|
|
|
|
);
|
2020-05-17 01:30:51 +00:00
|
|
|
}
|
2020-05-23 21:23:48 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_html_escape() {
|
|
|
|
assert_eq!(html_escape("<>&\"'"), "<>&"'".to_owned())
|
|
|
|
}
|
2020-05-17 01:30:51 +00:00
|
|
|
}
|