use crate::{utils::Number, Value}; impl Eq for Value {} impl PartialEq for Value { fn eq(&self, other: &Self) -> bool { match self { Value::Expression(_) => false, Value::Null => other.is_null(), Value::Array(a) => other.is_array() && Some(a) == other.as_array(), Value::Boolean(b) => other.is_bool() && Some(*b) == other.as_bool(), Value::Number(Number { number: n }) => { other.is_number() && float_eq(*n, other.as_number().unwrap().into()) } Value::String(s) => other.is_str() && s == other.as_str().unwrap(), Value::Object(o) => other.is_object() && Some(o) == other.as_object(), } } } impl PartialEq for bool { fn eq(&self, other: &Value) -> bool { other == self } } impl PartialEq for Value { fn eq(&self, other: &bool) -> bool { self.is_bool() && if *other { self.is_true() } else { self.is_false() } } } impl PartialEq for Option<()> { fn eq(&self, other: &Value) -> bool { other == self } } impl PartialEq> for Value { fn eq(&self, _: &Option<()>) -> bool { self.is_null() } } impl PartialEq for &str { fn eq(&self, other: &Value) -> bool { other == self } } impl PartialEq<&str> for Value { fn eq(&self, other: &&str) -> bool { self.is_str() && other == &self.as_str().unwrap() } } impl PartialEq for String { fn eq(&self, other: &Value) -> bool { other == self } } impl PartialEq for Value { fn eq(&self, other: &String) -> bool { self.is_str() && other == &self.as_str().unwrap() } } macro_rules! eq_num_lhs { ($int_type:ty) => { impl PartialEq<$int_type> for Value { fn eq(&self, other: &$int_type) -> bool { self.is_number() && float_eq(self.as_f64().unwrap(), (*other) as f64) } } }; } eq_num_lhs!(i8); eq_num_lhs!(i16); eq_num_lhs!(i32); eq_num_lhs!(i64); eq_num_lhs!(u8); eq_num_lhs!(u16); eq_num_lhs!(u32); eq_num_lhs!(u64); eq_num_lhs!(f32); eq_num_lhs!(f64); eq_num_lhs!(isize); eq_num_lhs!(usize); macro_rules! eq_num_rhs { ($int_type:ty) => { impl PartialEq for $int_type { fn eq(&self, other: &Value) -> bool { other == self } } }; } eq_num_rhs!(i8); eq_num_rhs!(i16); eq_num_rhs!(i32); eq_num_rhs!(i64); eq_num_rhs!(u8); eq_num_rhs!(u16); eq_num_rhs!(u32); eq_num_rhs!(u64); eq_num_rhs!(f32); eq_num_rhs!(f64); eq_num_rhs!(isize); eq_num_rhs!(usize); /// Compares two floating point numbers #[inline] pub(crate) fn float_eq(a: f64, b: f64) -> bool { let diff = (b - a).abs(); return diff < std::f64::EPSILON; } #[cfg(test)] mod tests { use crate::map; use crate::utils::Number; use crate::{Map, Value}; use rstest::*; #[rstest] #[case(Value::Boolean(true), true.into(), true)] #[case(Value::Number(Number::from(42.000000000000000001).unwrap()), 42.into(), true)] #[case(Value::String("str".to_string()), "str".into(), true)] #[case(Value::Null, None.into(), true)] #[case(Value::from_json(r#"{"foo": "bar"}"#).unwrap(), map!("foo" => "bar").into(), true)] #[case(Value::from_json(r#"["one", 2, {"three": 3}]"#).unwrap(), vec!["one".into(), >::into(2), map!("three" => 3).into()].into(), true)] fn it_implements_partial_eq(#[case] left: Value, #[case] right: Value, #[case] expected: bool) { assert_eq!(expected, left == right); } #[test] fn it_implements_partial_eq_bool_lhs() { assert_eq!(Value::Boolean(true), true); assert_eq!(Value::Boolean(false), false); } #[test] fn it_implements_partial_eq_bool_rhs() { assert_eq!(true, Value::Boolean(true)); assert_eq!(false, Value::Boolean(false)); } #[test] fn it_implements_partial_eq_null_lhs() { assert_eq!(Value::Null, None); assert_eq!(Value::Null, Some(())); } #[test] fn it_implements_partial_eq_null_rhs() { assert_eq!(None, Value::Null); assert_eq!(Some(()), Value::Null); } macro_rules! eq_num_lhs { ($ident:ident, $type:ty, $expected:expr, $input:expr) => { #[test] fn $ident() { assert_eq!( $expected, Value::Number(Number::from($input as f64).unwrap()) ); } }; } eq_num_lhs!(it_implements_partial_eq_i8_lhs, i8, 42.0, 42i8); eq_num_lhs!(it_implements_partial_eq_i16_lhs, i16, 42.0, 42i16); eq_num_lhs!(it_implements_partial_eq_i32_lhs, i32, 42.0, 42i32); eq_num_lhs!(it_implements_partial_eq_u8_lhs, u8, 42.0, 42u8); eq_num_lhs!(it_implements_partial_eq_u16_lhs, u16, 42.0, 42u16); eq_num_lhs!(it_implements_partial_eq_u32_lhs, u32, 42.0, 42u32); eq_num_lhs!(it_implements_partial_eq_isize_lhs, isize, 42.0, 42isize); eq_num_lhs!(it_implements_partial_eq_usize_lhs, usize, 42.0, 42usize); eq_num_lhs!(it_implements_partial_eq_f32_lhs, f32, 42.0, 42.0f32); eq_num_lhs!(it_implements_partial_eq_f64_lhs, f64, 42.0, 42.0f64); macro_rules! eq_num_rhs { ($ident:ident, $type:ty, $expected:expr, $input:expr) => { #[test] fn $ident() { assert_eq!( $expected, Value::Number(Number::from($input as f64).unwrap()) ); } }; } eq_num_rhs!(it_implements_partial_eq_i8_rhs, i8, 42.0, 42i8); eq_num_rhs!(it_implements_partial_eq_i16_rhs, i16, 42.0, 42i16); eq_num_rhs!(it_implements_partial_eq_i32_rhs, i32, 42.0, 42i32); eq_num_rhs!(it_implements_partial_eq_u8_rhs, u8, 42.0, 42u8); eq_num_rhs!(it_implements_partial_eq_u16_rhs, u16, 42.0, 42u16); eq_num_rhs!(it_implements_partial_eq_u32_rhs, u32, 42.0, 42u32); eq_num_rhs!(it_implements_partial_eq_isize_rhs, isize, 42.0, 42isize); eq_num_rhs!(it_implements_partial_eq_usize_rhs, usize, 42.0, 42usize); eq_num_rhs!(it_implements_partial_eq_f32_rhs, f32, 42.0, 42.0f32); eq_num_rhs!(it_implements_partial_eq_f64_rhs, f64, 42.0, 42.0f64); #[test] fn it_implements_partial_eq_str_lhs() { assert_eq!(Value::String("text".to_string()), "text"); } #[test] fn it_implements_partial_eq_str_rhs() { assert_eq!("text", Value::String("text".to_string())); } #[test] fn it_implements_partial_eq_string_lhs() { let text = "text".to_string(); assert_eq!(Value::String("text".to_string()), text); } #[test] fn it_implements_partial_eq_string_rhs() { let text = "text".to_string(); assert_eq!(text, Value::String("text".to_string())); } #[rstest] #[case(true, 42.0, 42.0)] #[case(false, 42.0, 43.0)] #[case(true, 0.0, std::f64::EPSILON/2.0)] #[case(false, 0.0, std::f64::EPSILON)] fn float_eq(#[case] expected: bool, #[case] left: f64, #[case] right: f64) { assert_eq!(expected, super::float_eq(left, right)); } }