Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::collections_search_text::fold_ascii; ← from collections.search-text ^1.0.0 · built alongside by fune
use super::collections_sort_by::sort_by; ← from collections.sort-by ^1.0.0 · built alongside by fune
use super::todo_item::{todo_from_value, todos_to_value, Todo}; ← from todo.item ^1.0.0 · built alongside by fune
use super::todo_item_validate_todo::is_utc_timestamp;
/// Sort passes, least significant first; the last is the primary key and takes the direction.
fn passes(by: &str) -> Option<&'static [(&'static str, &'static str)]> {
match by {
"manual" => Some(&[("created", "asc"), ("order", "dir")]),
"due" => Some(&[("order", "asc"), ("rank", "desc"), ("due", "dir")]),
"priority" => Some(&[("order", "asc"), ("due", "asc"), ("rank", "dir")]),
"created" => Some(&[("order", "asc"), ("created", "dir")]),
"title" => Some(&[("order", "asc"), ("title", "dir")]),
_ => None,
}
}
fn rank(priority: &str) -> i64 {
match priority {
"none" => 0,
"low" => 1,
"medium" => 2,
"high" => 3,
other => panic!("unknown priority \"{}\": expected none, low, medium or high", other),
}
}
/// A creation time that sorts as text: the fraction padded to nine digits, so
/// "...:00Z" comes before "...:00.5Z", where the raw strings put "." first.
fn created_key(created_at: &str) -> String {
if !is_utc_timestamp(created_at) {
panic!("createdAt \"{}\" is not a UTC timestamp, e.g. 2026-09-28T09:30:00Z", created_at);
}
let fraction = if created_at.len() > 20 { &created_at[20..created_at.len() - 1] } else { "" };
format!("{}.{:0<9}", &created_at[..19], fraction)
}
/// Todos in list order for the chosen key: stable, built from
/// collections.sort-by passes from the least to the most significant key; the
/// direction flips only the primary key, and no-date todos stay last.
///
/// # Panics
/// Panics on an unknown key or direction, an unknown priority (due and
/// priority sorts) or a createdAt that is not a UTC timestamp (manual and
/// created sorts).
pub fn sort_todos(todos: &[Todo], by: &str, direction: &str) -> Vec<Todo> {
let passes = match passes(by) {
Some(p) => p,
None => panic!("unknown sort key \"{}\": expected manual, due, priority, created or title", by),
};
if direction != "asc" && direction != "desc" {
panic!("direction must be \"asc\" or \"desc\", received \"{}\"", direction);
}
let uses = |field: &str| passes.iter().any(|(f, _)| *f == field);
let mut records: Vec<Value> = todos
.iter()
.enumerate()
.map(|(i, todo)| {
let mut fields: Vec<(&str, Value)> = vec![("i", Value::Int(i as i64)), ("order", Value::Int(todo.order))];
if uses("created") {
fields.push(("created", Value::Str(created_key(&todo.created_at))));
}
if uses("rank") {
fields.push(("rank", Value::Int(rank(&todo.priority))));
}
if uses("due") {
fields.push(("due", todo.due.as_deref().map(Value::str).unwrap_or(Value::Null)));
}
if uses("title") {
fields.push(("title", Value::Str(fold_ascii(&todo.title))));
}
Value::obj(fields)
})
.collect();
for (field, d) in passes {
records = sort_by(&records, field, if *d == "dir" { direction } else { d });
}
records.iter().map(|r| todos[r.get("i").as_i64() as usize].clone()).collect()
}
pub fn fune_vector(args: &[Value]) -> Value {
let todos: Vec<Todo> = args[0].as_arr().iter().map(todo_from_value).collect();
todos_to_value(&sort_todos(&todos, args[1].as_str(), args[2].as_str()))
}