use std::cmp::Ordering;
use crate::accessor::{compile_path, resolve_opt};
use crate::coerce;
use crate::error::{CoreError, Result};
use crate::value::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SortDirection {
Asc,
Desc,
}
impl SortDirection {
pub fn from_token(token: &str) -> Result<Self> {
match token {
"asc" => Ok(Self::Asc),
"desc" => Ok(Self::Desc),
other => Err(CoreError::Sort {
message: format!("unknown sort direction: {other}"),
}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum NullsPosition {
First,
Last,
}
impl NullsPosition {
pub fn from_token(token: &str) -> Result<Self> {
match token {
"first" => Ok(Self::First),
"last" => Ok(Self::Last),
other => Err(CoreError::Sort {
message: format!("unknown nulls position: {other}"),
}),
}
}
}
#[derive(Debug, Clone)]
pub struct SortSpec {
pub field: String,
pub direction: SortDirection,
pub nulls: NullsPosition,
}
pub fn sort_indices(items: &[Value], specs: &[SortSpec]) -> Result<Vec<usize>> {
sort_indices_of(items, (0..items.len()).collect(), specs)
}
pub fn sort_indices_of(
items: &[Value],
mut order: Vec<usize>,
specs: &[SortSpec],
) -> Result<Vec<usize>> {
if specs.is_empty() {
return Ok(order);
}
for spec in specs.iter().rev() {
let path = compile_path(&spec.field)?;
let null_first = null_sorts_first(spec.direction, spec.nulls);
let reverse = spec.direction == SortDirection::Desc;
let keys: Vec<Option<&Value>> = items.iter().map(|it| resolve_opt(it, &path)).collect();
let mut failure: Option<CoreError> = None;
order.sort_by(|&a, &b| {
if failure.is_some() {
return Ordering::Equal;
}
match compare_keys(keys[a], keys[b], null_first) {
Ok(ordering) if reverse => ordering.reverse(),
Ok(ordering) => ordering,
Err(err) => {
failure = Some(err);
Ordering::Equal
}
}
});
if let Some(err) = failure {
return Err(err);
}
}
Ok(order)
}
pub fn apply(items: &[Value], specs: &[SortSpec]) -> Result<Vec<Value>> {
let order = sort_indices(items, specs)?;
Ok(order.into_iter().map(|i| items[i].clone()).collect())
}
fn null_sorts_first(direction: SortDirection, nulls: NullsPosition) -> bool {
(nulls == NullsPosition::First) != (direction == SortDirection::Desc)
}
fn compare_keys(av: Option<&Value>, bv: Option<&Value>, null_first: bool) -> Result<Ordering> {
let a_flag = null_first ^ av.is_none();
let b_flag = null_first ^ bv.is_none();
if a_flag != b_flag {
return Ok(a_flag.cmp(&b_flag));
}
match (av, bv) {
(Some(x), Some(y)) => coerce::compare(x, y).ok_or_else(|| CoreError::Sort {
message: "field values are not order-comparable".to_owned(),
}),
_ => Ok(Ordering::Equal),
}
}
#[cfg(test)]
mod tests;