//! Sort an array following some rules.
//!
//! If you want to sort [`Value::Number`]s or [`Value::String`]s, you should use the `sort`
//! function:
//!
//! - [`Value::Number`]s are sorted by their numerical value
//! - [`Value::String`]s are sorted in alphabetical order
//!
//! If you want to sort [`Value::Array`]s or [`Value::Object`]s, you should use the `sort_by`
//! function. It takes a list of indexes separated by `.`. The indexes can be strings (to index
//! objects) or positive numbers (to index arrays).
//!
//! ### Example
//!
//! <div class="example-wrap"><pre class="rust rust-example-rendered" style="border-left: 2px solid red;"><span style="position: absolute; right: 0; top: 0; padding: 0.1rem 0.4rem 0 0; font-size: 0.75em; font-weight: bold; color: #333;">input</span><code><span class="fn">sort</span>([<span class="string">"foo"</span>, <span class="string">"bar"</span>, <span class="string">"baz"</span>])</code></pre></div>
//!
//! <div class="example-wrap"><pre class="rust rust-example-rendered" style="border-left: 2px solid red;"><span style="position: absolute; right: 0; top: 0; padding: 0.1rem 0.4rem 0 0; font-size: 0.75em; font-weight: bold; color: #333;">output</span><code>[<span class="string">"bar"</span>, <span class="string">"baz"</span>, <span class="string">"foo"</span>]</code></pre></div>
//!
//! <div class="example-wrap"><pre class="rust rust-example-rendered" style="position: relative; margin-top: 2rem; border-left: 2px solid blue;"><span style="position: absolute; right: 0; top: 0; padding: 0.1rem 0.4rem 0 0; font-size: 0.75em; font-weight: bold; color: #333;">input</span><code><span class="fn">sort_by</span>([{ meta: [<span class="string">"foo"</span>, <span class="number">3</span>] }, { meta: [<span class="string">"bar"</span>, <span class="number">12</span>] }, { meta: [<span class="string">"qux"</span>, <span class="number">2</span>] }], <span class="string">"meta.1"</span>)</code></pre></div>
//!
//! <div class="example-wrap"><pre class="rust rust-example-rendered" style="border-left: 2px solid blue;"><span style="position: absolute; right: 0; top: 0; padding: 0.1rem 0.4rem 0 0; font-size: 0.75em; font-weight: bold; color: #333;">output</span><code>[{ meta: [<span class="string">"qux"</span>, <span class="number">2</span>] }, { meta: [<span class="string">"foo"</span>, <span class="number">3</span>] }, { meta: [<span class="string">"bar"</span>, <span class="number">12</span>] }]</code></pre></div>
use std::cmp::Ordering;
use crate::{FunctionResult, Value};
fn index_value<'a>(val: &'a Value, index: &str) -> FunctionResult<&'a Value> {
match val {
Value::Object(obj) => {
if let Some(res) = obj.get(index) {
Ok(res)
} else {
Err(
format!("object `{val}` does not have the field {index}, failed to sort",)
.into(),
)
}
}
Value::Array(arr) => {
if let Ok(index) = index.parse::<usize>() {
if let Some(res) = arr.get(index) {
Ok(res)
} else {
Err(
format!("array `{val}` does not have the index {index}, failed to sort",)
.into(),
)
}
} else {
Err("array index must be a positive number".into())
}
}
_ => Err(format!("{:?} cannot be indexed, failed to sort", val.type_name()).into()),
}
}
fn compare_values(val1: &Value, val2: &Value) -> FunctionResult<Ordering> {
match (val1, val2) {
(Value::String(val1), Value::String(val2)) => Ok(val1.partial_cmp(val2).unwrap()),
(Value::Number(val1), Value::Number(val2)) => Ok(val1.partial_cmp(val2).unwrap()),
_ => Err(format!(
"cannot compare {} with {}, failed to sort",
val1.type_name(),
val2.type_name(),
)
.into()),
}
}
pub(crate) fn sort(mut val: Vec<Value>) -> FunctionResult<Vec<Value>> {
let mut err = None;
val.sort_by(|val1, val2| {
if err.is_some() {
// Quickly skip all remaining elements if there was an error
return Ordering::Equal;
}
match compare_values(val1, val2) {
Ok(ord) => ord,
Err(e) => {
// Only return the first error
if err.is_none() {
err = Some(e);
}
Ordering::Equal
}
}
});
if let Some(err) = err {
Err(err)
} else {
Ok(val)
}
}
pub(crate) fn sort_by(mut val: Vec<Value>, by: String) -> FunctionResult<Vec<Value>> {
let mut err = None;
val.sort_by(|mut val1, mut val2| {
if err.is_some() {
// Quickly skip all remaining elements if there was an error
return Ordering::Equal;
}
for index in by.split('.') {
val1 = match index_value(val1, index) {
Ok(val) => val,
Err(e) => {
if err.is_none() {
err = Some(e);
}
return Ordering::Equal;
}
};
val2 = match index_value(val2, index) {
Ok(val) => val,
Err(e) => {
if err.is_none() {
err = Some(e);
}
return Ordering::Equal;
}
};
}
match compare_values(val1, val2) {
Ok(ord) => ord,
Err(e) => {
// Only return the first error
if err.is_none() {
err = Some(e);
}
Ordering::Equal
}
}
});
if let Some(err) = err {
Err(err)
} else {
Ok(val)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{object, IntoValue};
#[test]
#[allow(clippy::too_many_lines)]
fn sort_test() {
assert_eq!(
sort(vec![
"foo".into_value(),
"bar".into_value(),
"baz".into_value()
])
.unwrap(),
vec!["bar".into_value(), "baz".into_value(), "foo".into_value()],
);
assert_eq!(
sort_by(
vec![
object! {
"a" => object! {
"b" => "a",
},
}
.into_value(),
object! {
"a" => object! {
"b" => "d",
},
}
.into_value(),
object! {
"a" => object! {
"b" => "b",
},
}
.into_value(),
object! {
"a" => object! {
"b" => "c",
},
}
.into_value(),
],
"a.b".to_string()
)
.unwrap(),
vec![
object! {
"a" => object! {
"b" => "a",
},
}
.into_value(),
object! {
"a" => object! {
"b" => "b",
},
}
.into_value(),
object! {
"a" => object! {
"b" => "c",
},
}
.into_value(),
object! {
"a" => object! {
"b" => "d",
},
}
.into_value(),
],
);
assert_eq!(
sort_by(
vec![
vec!["About".into_value(), 12.into_value()].into_value(),
vec!["Login".into_value(), 10e10.into_value()].into_value(),
vec!["Home".into_value(), (-13).into_value()].into_value(),
vec!["Contact".into_value(), 24.12.into_value()].into_value(),
vec!["Signup".into_value(), 25.into_value()].into_value(),
],
"1".to_string()
)
.unwrap(),
vec![
vec!["Home".into_value(), (-13).into_value()].into_value(),
vec!["About".into_value(), 12.into_value()].into_value(),
vec!["Contact".into_value(), 24.12.into_value()].into_value(),
vec!["Signup".into_value(), 25.into_value()].into_value(),
vec!["Login".into_value(), 10e10.into_value()].into_value(),
],
);
assert_eq!(
sort_by(
vec![
object! {
"meta" => vec!["foo".into_value(), 3.into_value()],
}
.into_value(),
object! {
"meta" => vec!["bar".into_value(), 12.into_value()],
}
.into_value(),
object! {
"meta" => vec!["qux".into_value(), 2.into_value()],
}
.into_value(),
],
"meta.1".to_string()
)
.unwrap(),
vec![
object! {
"meta" => vec!["qux".into_value(), 2.into_value()],
}
.into_value(),
object! {
"meta" => vec!["foo".into_value(), 3.into_value()],
}
.into_value(),
object! {
"meta" => vec!["bar".into_value(), 12.into_value()],
}
.into_value(),
],
);
}
}