diff --git a/src/builtins/filters/array.rs b/src/builtins/filters/array.rs index 48b89267..5c420e3f 100644 --- a/src/builtins/filters/array.rs +++ b/src/builtins/filters/array.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use crate::context::{get_json_pointer, ValueRender}; use crate::errors::{Error, Result}; -use crate::filter_utils::{get_sort_strategy_for_type, get_unique_strategy_for_type}; +use crate::filter_utils::{compare_values, get_unique_strategy_for_type}; use crate::utils::render_to_string; use serde_json::value::{to_value, Map, Value}; @@ -65,11 +65,10 @@ pub fn join(value: &Value, args: &HashMap) -> Result { /// Sorts the array in ascending order. /// Use the 'attribute' argument to define a field to sort by. pub fn sort(value: &Value, args: &HashMap) -> Result { - let arr = try_get_value!("sort", "value", Vec, value); + let mut arr = try_get_value!("sort", "value", Vec, value); if arr.is_empty() { return Ok(arr.into()); } - let attribute = match args.get("attribute") { Some(val) => try_get_value!("sort", "attribute", String, val), None => String::new(), @@ -79,20 +78,40 @@ pub fn sort(value: &Value, args: &HashMap) -> Result { s => get_json_pointer(s), }; + // make sure the types are identical, and the field exists let first = arr[0].pointer(&ptr).ok_or_else(|| { Error::msg(format!("attribute '{}' does not reference a field", attribute)) })?; - - let mut strategy = get_sort_strategy_for_type(first)?; - for v in &arr { - let key = v.pointer(&ptr).ok_or_else(|| { - Error::msg(format!("attribute '{}' does not reference a field", attribute)) - })?; - strategy.try_add_pair(v, key)?; - } - let sorted = strategy.sort(); - - Ok(sorted.into()) + let discriminant = std::mem::discriminant(first); + let misfits: Result> = arr[1..] + .iter() + .map(|item| { + let a = item.pointer(&ptr).ok_or(Error::msg(format!( + "attribute '{}' does not reference a field for {}", + attribute, item + )))?; + + if std::mem::discriminant(a) != discriminant { + return Err(Error::msg(format!( + "expected same types but got {} and {}", + first, item + ))); + } + Ok(()) + }) + .collect(); + misfits?; + + arr.sort_unstable_by(|a, b| { + compare_values( + a.pointer(&ptr) + .expect(&format!("attribute '{}' does not reference a field", attribute)), + b.pointer(&ptr) + .expect(&format!("attribute '{}' does not reference a field", attribute)), + ) + .expect("values should be compareable") + }); + Ok(arr.into()) } /// Remove duplicates from an array. @@ -469,7 +488,7 @@ mod tests { let result = sort(&v, &args); assert!(result.is_err()); - assert_eq!(result.unwrap_err().to_string(), "expected number got []"); + assert_eq!(result.unwrap_err().to_string(), "expected same types but got 12 and []"); } #[test] @@ -482,8 +501,15 @@ mod tests { let args = HashMap::new(); let result = sort(&v, &args); - assert!(result.is_err()); - assert_eq!(result.unwrap_err().to_string(), "Null is not a sortable value"); + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + to_value(vec![ + ::std::f64::NAN, // NaN and friends get deserialized as Null by serde. + ::std::f64::NAN, + ]) + .unwrap() + ); } #[derive(Deserialize, Eq, Hash, PartialEq, Serialize)] diff --git a/src/filter_utils.rs b/src/filter_utils.rs index 49e2871d..b80b65b4 100644 --- a/src/filter_utils.rs +++ b/src/filter_utils.rs @@ -2,6 +2,37 @@ use crate::errors::{Error, Result}; use serde_json::Value; use std::cmp::Ordering; +pub fn compare_values(a: &Value, b: &Value) -> Result { + if std::mem::discriminant(a) != std::mem::discriminant(b) { + return Err(Error::msg(format!("expected same types but got {a} and {b}"))); + } + match a { + Value::Null => Ok(Ordering::Equal), + Value::Bool(a) => Ok(a.cmp(&b.as_bool().unwrap())), + Value::Number(a) => { + if let Some(a) = a.as_f64() { + if let Some(b) = b.as_f64() { + return Ok(a.total_cmp(&b)); + } + } + if let Some(a) = a.as_i64() { + if let Some(b) = b.as_i64() { + return Ok(a.cmp(&b)); + } + } + if let Some(a) = a.as_u64() { + if let Some(b) = b.as_u64() { + return Ok(a.cmp(&b)); + } + } + Err(Error::msg(format!("{a} cannot be sorted"))) + } + Value::String(a) => Ok(a.as_str().cmp(b.as_str().unwrap())), + Value::Array(a) => Ok(a.len().cmp(&b.as_array().unwrap().len())), + Value::Object(a) => Ok(a.len().cmp(&b.as_array().unwrap().len())), + } +} + #[derive(PartialEq, PartialOrd, Default, Copy, Clone)] pub struct OrderedF64(f64); @@ -66,56 +97,6 @@ impl GetValue for ArrayLen { } } -#[derive(Default)] -pub struct SortPairs { - pairs: Vec<(Value, K)>, -} - -type SortNumbers = SortPairs; -type SortBools = SortPairs; -type SortStrings = SortPairs; -type SortArrays = SortPairs; - -impl SortPairs { - fn try_add_pair(&mut self, val: &Value, key: &Value) -> Result<()> { - let key = K::get_value(key)?; - self.pairs.push((val.clone(), key)); - Ok(()) - } - - fn sort(&mut self) -> Vec { - self.pairs.sort_by_key(|a| a.1.clone()); - self.pairs.iter().map(|a| a.0.clone()).collect() - } -} - -pub trait SortStrategy { - fn try_add_pair(&mut self, val: &Value, key: &Value) -> Result<()>; - fn sort(&mut self) -> Vec; -} - -impl SortStrategy for SortPairs { - fn try_add_pair(&mut self, val: &Value, key: &Value) -> Result<()> { - SortPairs::try_add_pair(self, val, key) - } - - fn sort(&mut self) -> Vec { - SortPairs::sort(self) - } -} - -pub fn get_sort_strategy_for_type(ty: &Value) -> Result> { - use crate::Value::*; - match *ty { - Null => Err(Error::msg("Null is not a sortable value")), - Bool(_) => Ok(Box::new(SortBools::default())), - Number(_) => Ok(Box::new(SortNumbers::default())), - String(_) => Ok(Box::new(SortStrings::default())), - Array(_) => Ok(Box::new(SortArrays::default())), - Object(_) => Err(Error::msg("Object is not a sortable value")), - } -} - #[derive(Default)] pub struct Unique { unique: std::collections::HashSet,