diff --git a/js/test_cases/helpers_eq/main.dust b/js/test_cases/helpers_eq/main.dust index 777fa89..ac77ac9 100644 --- a/js/test_cases/helpers_eq/main.dust +++ b/js/test_cases/helpers_eq/main.dust @@ -30,4 +30,13 @@ Do objects with different paths referencing the same variable match?{~n} Floating point equality{~n} ======================={~n} -{@eq key=int value=7.0}int is equal to 7.0{:else}int is not equal to 7.0{/eq} +{@eq key=int value=7.0}int is equal to 7.0{~n}{:else}int is not equal to 7.0{~n}{/eq} + +Type cast{~n} +========={~n} +{@eq key=int value="7"}int is equal to "7"{~n}{:else}int is not equal to "7"{~n}{/eq} +{@eq key=int value="7" type="number"}int is equal to "7"::number{~n}{:else}int is not equal to "7"::number{~n}{/eq} +{@eq key=beta value=21 type="string"}beta is equal to 21::string{~n}{:else}beta is not equal to 21::string{~n}{/eq} +{@eq key=beta value="21" type="string"}beta is equal to "21"::string{~n}{:else}beta is not equal to "21"::string{~n}{/eq} +{@eq key=1 value=true_value type="number"}1 is equal to true_value::number{~n}{:else}1 is not equal to true_value::number{~n}{/eq} +{@eq key=0 value=false_value type="number"}0 is equal to false_value::number{~n}{:else}0 is not equal to false_value::number{~n}{/eq} diff --git a/src/bin.rs b/src/bin.rs index d5e89d8..a614f88 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -4,8 +4,10 @@ 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; @@ -315,6 +317,17 @@ impl Loopable for serde_json::Value { } 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> { match self { serde_json::Value::Null => { @@ -353,10 +366,64 @@ impl Castable for serde_json::Value { }) .ok(), (serde_json::Value::Number(_), "number") => Some(IceResult::from_borrowed(self)), - (serde_json::Value::Null, "number") => None, - (serde_json::Value::Bool(_), "number") => None, + (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"), } } @@ -384,6 +451,9 @@ impl CompareContextElement for serde_json::Value { 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 @@ -435,7 +505,7 @@ impl CompareContextElement for serde_json::Value { _, Some(OwnedLiteral::LString(other_string)), ) => return self_string.partial_cmp(&other_string), - // Otherwise, convert to numbers are compare them that way + // 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."), @@ -591,14 +661,6 @@ impl CompareContextElement for serde_json::Value { } } -#[derive(Debug)] -enum ComparisonNumber { - UnsignedInteger(u64), - SignedInteger(i64), - Decimal(f64), - Failure, -} - impl From<&serde_json::Value> for ComparisonNumber { /// Convert from a JSON value to a number for comparison based on /// the logic described at @@ -625,24 +687,6 @@ impl From<&serde_json::Value> for ComparisonNumber { } } -impl From<&String> for ComparisonNumber { - fn from(original: &String) -> Self { - match original.parse::() { - Ok(num) => return ComparisonNumber::UnsignedInteger(num), - Err(_) => (), - }; - match original.parse::() { - Ok(num) => return ComparisonNumber::SignedInteger(num), - Err(_) => (), - }; - match original.parse::() { - Ok(num) => return ComparisonNumber::Decimal(num), - Err(_) => (), - }; - ComparisonNumber::Failure - } -} - impl From<&serde_json::Number> for ComparisonNumber { fn from(original: &serde_json::Number) -> Self { match original.as_u64() { @@ -661,85 +705,6 @@ impl From<&serde_json::Number> for ComparisonNumber { } } -impl From<&OwnedLiteral> for ComparisonNumber { - fn from(original: &OwnedLiteral) -> Self { - match original { - OwnedLiteral::LPositiveInteger(num) => ComparisonNumber::UnsignedInteger(*num), - OwnedLiteral::LNegativeInteger(num) => ComparisonNumber::SignedInteger(*num), - OwnedLiteral::LString(text) => text.into(), - OwnedLiteral::LFloat(num) => { - if num.is_nan() { - ComparisonNumber::Failure - } else { - ComparisonNumber::Decimal(*num) - } - } - } - } -} - -/// Compare json numbers -/// -/// While this function can be called with two strings, it would not -/// make sense because javascript does not use numeric comparisons for -/// strings -fn compare_json_numbers(self_input: S, other_input: O) -> Option -where - S: Into, - O: Into, -{ - let self_number: ComparisonNumber = self_input.into(); - let other_number: ComparisonNumber = other_input.into(); - match (self_number, other_number) { - (ComparisonNumber::Failure, _) => return None, - (_, ComparisonNumber::Failure) => return None, - ( - ComparisonNumber::UnsignedInteger(self_num), - ComparisonNumber::UnsignedInteger(other_num), - ) => return self_num.partial_cmp(&other_num), - ( - ComparisonNumber::UnsignedInteger(self_num), - ComparisonNumber::SignedInteger(other_num), - ) => { - if self_num < std::i64::MAX as u64 { - return (self_num as i64).partial_cmp(&other_num); - } else { - return Some(Ordering::Greater); - } - } - (ComparisonNumber::UnsignedInteger(self_num), ComparisonNumber::Decimal(other_num)) => { - return (self_num as f64).partial_cmp(&other_num) - } - - ( - ComparisonNumber::SignedInteger(self_num), - ComparisonNumber::UnsignedInteger(other_num), - ) => { - if other_num < std::i64::MAX as u64 { - return self_num.partial_cmp(&(other_num as i64)); - } else { - return Some(Ordering::Less); - } - } - (ComparisonNumber::SignedInteger(self_num), ComparisonNumber::SignedInteger(other_num)) => { - return self_num.partial_cmp(&other_num) - } - (ComparisonNumber::SignedInteger(self_num), ComparisonNumber::Decimal(other_num)) => { - return (self_num as f64).partial_cmp(&other_num) - } - - (ComparisonNumber::Decimal(self_num), ComparisonNumber::UnsignedInteger(other_num)) => { - return self_num.partial_cmp(&(other_num as f64)) - } - (ComparisonNumber::Decimal(self_num), ComparisonNumber::SignedInteger(other_num)) => { - return self_num.partial_cmp(&(other_num as f64)) - } - (ComparisonNumber::Decimal(self_num), ComparisonNumber::Decimal(other_num)) => { - return self_num.partial_cmp(&other_num) - } - } -} - impl From<&serde_json::Value> for MathNumber { fn from(original: &serde_json::Value) -> Self { match original { diff --git a/src/parser/parser.rs b/src/parser/parser.rs index c25ba08..e35a44b 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -123,6 +123,11 @@ pub enum OwnedLiteral { LPositiveInteger(u64), LNegativeInteger(i64), LFloat(f64), + // Unlike the other OwnedLiterals, booleans cannot occur in DustJS + // templates because true/false are not reserved + // names. Regardless, they are needed here for type casting in the + // renderer. + LBoolean(bool), } #[derive(Debug, PartialEq)] diff --git a/src/renderer/comparison_number.rs b/src/renderer/comparison_number.rs new file mode 100644 index 0000000..d85307f --- /dev/null +++ b/src/renderer/comparison_number.rs @@ -0,0 +1,89 @@ +use std::cmp::Ordering; + +#[derive(Debug)] +pub enum ComparisonNumber { + UnsignedInteger(u64), + SignedInteger(i64), + Decimal(f64), + Failure, +} + +/// 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 +pub fn compare_json_numbers(self_input: S, other_input: O) -> Option +where + S: Into, + O: Into, +{ + let self_number: ComparisonNumber = self_input.into(); + let other_number: ComparisonNumber = other_input.into(); + match (self_number, other_number) { + (ComparisonNumber::Failure, _) => return None, + (_, ComparisonNumber::Failure) => return None, + ( + ComparisonNumber::UnsignedInteger(self_num), + ComparisonNumber::UnsignedInteger(other_num), + ) => return self_num.partial_cmp(&other_num), + ( + ComparisonNumber::UnsignedInteger(self_num), + ComparisonNumber::SignedInteger(other_num), + ) => { + if self_num < std::i64::MAX as u64 { + return (self_num as i64).partial_cmp(&other_num); + } else { + return Some(Ordering::Greater); + } + } + (ComparisonNumber::UnsignedInteger(self_num), ComparisonNumber::Decimal(other_num)) => { + return (self_num as f64).partial_cmp(&other_num) + } + + ( + ComparisonNumber::SignedInteger(self_num), + ComparisonNumber::UnsignedInteger(other_num), + ) => { + if other_num < std::i64::MAX as u64 { + return self_num.partial_cmp(&(other_num as i64)); + } else { + return Some(Ordering::Less); + } + } + (ComparisonNumber::SignedInteger(self_num), ComparisonNumber::SignedInteger(other_num)) => { + return self_num.partial_cmp(&other_num) + } + (ComparisonNumber::SignedInteger(self_num), ComparisonNumber::Decimal(other_num)) => { + return (self_num as f64).partial_cmp(&other_num) + } + + (ComparisonNumber::Decimal(self_num), ComparisonNumber::UnsignedInteger(other_num)) => { + return self_num.partial_cmp(&(other_num as f64)) + } + (ComparisonNumber::Decimal(self_num), ComparisonNumber::SignedInteger(other_num)) => { + return self_num.partial_cmp(&(other_num as f64)) + } + (ComparisonNumber::Decimal(self_num), ComparisonNumber::Decimal(other_num)) => { + return self_num.partial_cmp(&other_num) + } + } +} + +impl From<&String> for ComparisonNumber { + fn from(original: &String) -> Self { + match original.parse::() { + Ok(num) => return ComparisonNumber::UnsignedInteger(num), + Err(_) => (), + }; + match original.parse::() { + Ok(num) => return ComparisonNumber::SignedInteger(num), + Err(_) => (), + }; + match original.parse::() { + Ok(num) => return ComparisonNumber::Decimal(num), + Err(_) => (), + }; + ComparisonNumber::Failure + } +} diff --git a/src/renderer/context_element.rs b/src/renderer/context_element.rs index 01281d3..0e4779c 100644 --- a/src/renderer/context_element.rs +++ b/src/renderer/context_element.rs @@ -65,6 +65,18 @@ pub trait Castable { } pub trait Sizable { + /// Special case: In DustJS the @size helper usually attempts to + /// cast to a number before calculating the size. The exception to + /// this is booleans. `Number(true) == 1` but `@size` on any + /// boolean is always 0. Make this function return false for any + /// type that casting to a number shouldn't be attempted. + /// + /// Note: Its fine for objects that cannot be cast to a number to + /// return true here. False is only needed for cases where casting + /// to a number would cause a deviation in the final value for + /// `@size`. + fn is_castable(&self) -> bool; + fn get_size<'a>(&'a self) -> Option>; } diff --git a/src/renderer/math.rs b/src/renderer/math.rs index 8161019..dd972e8 100644 --- a/src/renderer/math.rs +++ b/src/renderer/math.rs @@ -18,6 +18,13 @@ impl From<&OwnedLiteral> for MathNumber { fn from(original: &OwnedLiteral) -> Self { match original { OwnedLiteral::LString(_) => panic!("Strings should not be cast to numbers for math"), + OwnedLiteral::LBoolean(boolean) => { + if *boolean { + MathNumber::Integer(1) + } else { + MathNumber::Integer(0) + } + } OwnedLiteral::LPositiveInteger(num) => { return MathNumber::Integer((*num).try_into().unwrap()) } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 9b1f5b7..739eede 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,6 +1,7 @@ //! This module contains a renderer for a rust implementation of LinkedIn Dust mod breadcrumb_tree; +mod comparison_number; mod context_element; mod errors; mod inline_partial_tree; @@ -11,6 +12,8 @@ mod renderer; mod select_context; mod walking; +pub use comparison_number::compare_json_numbers; +pub use comparison_number::ComparisonNumber; pub use context_element::Castable; pub use context_element::CompareContextElement; pub use context_element::ContextElement; diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index ad2585c..a03535c 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -3,6 +3,8 @@ use crate::parser::KVPair; use crate::parser::OwnedLiteral; use crate::parser::RValue; use crate::renderer::breadcrumb_tree::BreadcrumbTreeElement; +use crate::renderer::comparison_number::compare_json_numbers; +use crate::renderer::comparison_number::ComparisonNumber; use crate::renderer::context_element::CompareContextElement; use crate::renderer::context_element::ContextElement; use crate::renderer::context_element::IceResult; @@ -160,6 +162,7 @@ impl ContextElement for OwnedLiteral {} impl Truthiness for OwnedLiteral { fn is_truthy(&self) -> bool { match self { + OwnedLiteral::LBoolean(boolean) => *boolean, OwnedLiteral::LString(text) => !text.is_empty(), OwnedLiteral::LPositiveInteger(_num) => true, OwnedLiteral::LNegativeInteger(_num) => true, @@ -171,6 +174,7 @@ impl Truthiness for OwnedLiteral { impl Renderable for OwnedLiteral { fn render(&self, _filters: &Vec) -> Result { match self { + OwnedLiteral::LBoolean(boolean) => Ok(boolean.to_string()), OwnedLiteral::LString(text) => Ok(text.clone()), OwnedLiteral::LPositiveInteger(num) => Ok(num.to_string()), OwnedLiteral::LNegativeInteger(num) => Ok(num.to_string()), @@ -192,8 +196,21 @@ impl Walkable for OwnedLiteral { } impl Sizable for OwnedLiteral { + fn is_castable(&self) -> bool { + match self { + OwnedLiteral::LBoolean(_) => false, + OwnedLiteral::LFloat(_) => true, + OwnedLiteral::LPositiveInteger(_) => true, + OwnedLiteral::LNegativeInteger(_) => true, + OwnedLiteral::LString(_) => true, + } + } + fn get_size<'a>(&'a self) -> Option> { match self { + OwnedLiteral::LBoolean(_) => { + Some(IceResult::from_owned(OwnedLiteral::LPositiveInteger(0))) + } OwnedLiteral::LFloat(_) => Some(IceResult::from_borrowed(self)), OwnedLiteral::LPositiveInteger(_) => Some(IceResult::from_borrowed(self)), OwnedLiteral::LNegativeInteger(_) => Some(IceResult::from_borrowed(self)), @@ -219,9 +236,60 @@ impl Castable for OwnedLiteral { .map(|num| IceResult::from_owned(OwnedLiteral::LFloat(num))) }) .ok(), + (OwnedLiteral::LBoolean(boolean), "number") => { + if *boolean { + Some(IceResult::from_owned(OwnedLiteral::LPositiveInteger(1))) + } else { + Some(IceResult::from_owned(OwnedLiteral::LPositiveInteger(0))) + } + } (OwnedLiteral::LPositiveInteger(_), "number") => Some(IceResult::from_borrowed(self)), (OwnedLiteral::LNegativeInteger(_), "number") => Some(IceResult::from_borrowed(self)), (OwnedLiteral::LFloat(_), "number") => Some(IceResult::from_borrowed(self)), + + (OwnedLiteral::LString(_), "string") => Some(IceResult::from_borrowed(self)), + (OwnedLiteral::LBoolean(boolean), "string") => Some(IceResult::from_owned( + OwnedLiteral::LString(boolean.to_string()), + )), + (OwnedLiteral::LPositiveInteger(num), "string") => Some(IceResult::from_owned( + OwnedLiteral::LString(num.to_string()), + )), + (OwnedLiteral::LNegativeInteger(num), "string") => Some(IceResult::from_owned( + OwnedLiteral::LString(num.to_string()), + )), + (OwnedLiteral::LFloat(num), "string") => Some(IceResult::from_owned( + OwnedLiteral::LString(num.to_string()), + )), + + (OwnedLiteral::LString(text), "boolean") => { + if text.is_empty() { + Some(IceResult::from_owned(OwnedLiteral::LBoolean(false))) + } else { + Some(IceResult::from_owned(OwnedLiteral::LBoolean(true))) + } + } + (OwnedLiteral::LBoolean(_), "boolean") => Some(IceResult::from_borrowed(self)), + (OwnedLiteral::LPositiveInteger(num), "boolean") => { + if *num == 0 { + Some(IceResult::from_owned(OwnedLiteral::LBoolean(false))) + } else { + Some(IceResult::from_owned(OwnedLiteral::LBoolean(true))) + } + } + (OwnedLiteral::LNegativeInteger(num), "boolean") => { + if *num == 0 { + Some(IceResult::from_owned(OwnedLiteral::LBoolean(false))) + } else { + Some(IceResult::from_owned(OwnedLiteral::LBoolean(true))) + } + } + (OwnedLiteral::LFloat(num), "boolean") => { + if *num == 0.0 || num.is_nan() { + Some(IceResult::from_owned(OwnedLiteral::LBoolean(false))) + } else { + Some(IceResult::from_owned(OwnedLiteral::LBoolean(true))) + } + } (_, _) => panic!("Unimplemented cast"), } } @@ -248,9 +316,11 @@ impl CompareContextElement for OwnedLiteral { OwnedLiteral::LNegativeInteger(self_num), OwnedLiteral::LNegativeInteger(other_num), ) => self_num == other_num, - (OwnedLiteral::LString(_self_text), _) | (_, OwnedLiteral::LString(_self_text)) => { - false + (OwnedLiteral::LBoolean(self_bool), OwnedLiteral::LBoolean(other_bool)) => { + self_bool == other_bool } + (OwnedLiteral::LString(_), _) | (_, OwnedLiteral::LString(_)) => false, + (OwnedLiteral::LBoolean(_), _) | (_, OwnedLiteral::LBoolean(_)) => false, (OwnedLiteral::LFloat(self_num), OwnedLiteral::LFloat(other_num)) => { self_num == other_num } @@ -305,70 +375,12 @@ impl CompareContextElement for OwnedLiteral { }, }, Some(other_literal) => match (self, other_literal) { + // If they're both strings, compare them directly (OwnedLiteral::LString(self_text), OwnedLiteral::LString(other_text)) => { self_text.partial_cmp(other_text) } - ( - OwnedLiteral::LPositiveInteger(self_num), - OwnedLiteral::LPositiveInteger(other_num), - ) => self_num.partial_cmp(other_num), - ( - OwnedLiteral::LNegativeInteger(self_num), - OwnedLiteral::LNegativeInteger(other_num), - ) => self_num.partial_cmp(other_num), - (OwnedLiteral::LFloat(self_num), OwnedLiteral::LFloat(other_num)) => { - self_num.partial_cmp(other_num) - } - (OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LString(other_text)) => { - self_num.to_string().partial_cmp(other_text) - } - (OwnedLiteral::LString(self_text), OwnedLiteral::LPositiveInteger(other_num)) => { - self_text.partial_cmp(&other_num.to_string()) - } - (OwnedLiteral::LNegativeInteger(self_num), OwnedLiteral::LString(other_text)) => { - self_num.to_string().partial_cmp(other_text) - } - (OwnedLiteral::LString(self_text), OwnedLiteral::LNegativeInteger(other_num)) => { - self_text.partial_cmp(&other_num.to_string()) - } - (OwnedLiteral::LFloat(self_num), OwnedLiteral::LString(other_text)) => { - self_num.to_string().partial_cmp(other_text) - } - (OwnedLiteral::LString(self_text), OwnedLiteral::LFloat(other_num)) => { - self_text.partial_cmp(&other_num.to_string()) - } - (OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LFloat(other_num)) => { - (*self_num as f64).partial_cmp(other_num) - } - (OwnedLiteral::LFloat(self_num), OwnedLiteral::LPositiveInteger(other_num)) => { - self_num.partial_cmp(&(*other_num as f64)) - } - (OwnedLiteral::LNegativeInteger(self_num), OwnedLiteral::LFloat(other_num)) => { - (*self_num as f64).partial_cmp(other_num) - } - (OwnedLiteral::LFloat(self_num), OwnedLiteral::LNegativeInteger(other_num)) => { - self_num.partial_cmp(&(*other_num as f64)) - } - ( - OwnedLiteral::LPositiveInteger(self_num), - OwnedLiteral::LNegativeInteger(other_num), - ) => { - if *self_num < std::i64::MAX as u64 { - (*self_num as i64).partial_cmp(other_num) - } else { - Some(Ordering::Greater) - } - } - ( - OwnedLiteral::LNegativeInteger(self_num), - OwnedLiteral::LPositiveInteger(other_num), - ) => { - if *other_num < std::i64::MAX as u64 { - self_num.partial_cmp(&(*other_num as i64)) - } else { - Some(Ordering::Less) - } - } + // Otherwise, convert to numbers and compare them that way + (_, _) => return compare_json_numbers(self, other_literal), }, } } @@ -471,3 +483,27 @@ impl CompareContextElement for OwnedLiteral { .map(IceResult::from_owned) } } + +impl From<&OwnedLiteral> for ComparisonNumber { + fn from(original: &OwnedLiteral) -> Self { + match original { + OwnedLiteral::LBoolean(boolean) => { + if *boolean { + ComparisonNumber::UnsignedInteger(1) + } else { + ComparisonNumber::UnsignedInteger(0) + } + } + OwnedLiteral::LPositiveInteger(num) => ComparisonNumber::UnsignedInteger(*num), + OwnedLiteral::LNegativeInteger(num) => ComparisonNumber::SignedInteger(*num), + OwnedLiteral::LString(text) => text.into(), + OwnedLiteral::LFloat(num) => { + if num.is_nan() { + ComparisonNumber::Failure + } else { + ComparisonNumber::Decimal(*num) + } + } + } + } +} diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index 561d224..023cd38 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -730,7 +730,6 @@ impl<'a> DustRenderer<'a> { maybe_ice .as_ref() .map(|ice| ice.get_context_element_reference()) - // .map(|ce| ce.get_size()) }); match value_ce { // If "key" is not on the @size tag at all, render 0. @@ -742,8 +741,8 @@ impl<'a> DustRenderer<'a> { // numbers, and if that succeeds it uses the // number, otherwise we'll get the size of the // original type. - match ce.cast_to_type("number") { - Some(ice) => { + match (ce.cast_to_type("number"), ce.is_castable()) { + (Some(ice), true) => { return ice .get_context_element_reference() .get_size() @@ -752,7 +751,15 @@ impl<'a> DustRenderer<'a> { }) .unwrap_or(Ok("".to_owned())) } - None => { + (Some(_), false) => { + return ce + .get_size() + .map(|ce_size| { + ce_size.get_context_element_reference().render(&Vec::new()) + }) + .unwrap_or(Ok("".to_owned())) + } + (None, _) => { return ce .get_size() .map(|ce_size| { @@ -1012,21 +1019,41 @@ impl<'a> DustRenderer<'a> { let left_side = self.tap(breadcrumbs, ¶m_map, "key"); let right_side = self.tap(breadcrumbs, ¶m_map, "value"); + let type_cast = self.tap(breadcrumbs, ¶m_map, "type"); let left_side_ce = left_side.as_ref().map(|maybe_ice| { maybe_ice .as_ref() .map(|ice| ice.get_context_element_reference()) }); - let right_side_ce = right_side.as_ref().map(|maybe_ice| { + let mut right_side_ce = right_side.as_ref().map(|maybe_ice| { maybe_ice .as_ref() .map(|ice| ice.get_context_element_reference()) }); + let type_rendered = match type_cast.as_ref().map(|maybe_ice| { + maybe_ice + .as_ref() + .map(|ice| ice.get_context_element_reference()) + .map(|ce| ce.render(&Vec::new())) + }) { + Some(Ok(Ok(val))) => Some(val), + _ => None, + }; if left_side_ce.is_none() { // If key did not exist at all, return None return None; } + let right_side_cast = match (right_side_ce, type_rendered) { + (Some(Ok(ce)), Some(target_type)) => ce.cast_to_type(&target_type), + _ => None, + }; + match right_side_cast.as_ref() { + Some(ice) => { + right_side_ce = Some(Ok(ice.get_context_element_reference())); + } + None => (), + } match tag { // Special case: when comparing two RVPaths, if the path // points to the same value then they are equal. This is