use crate::read::Sorter;
use crate::warn_once::warn_once;
pub(crate) fn warn_if_page_order_undefined(type_name: &str, skip: i64, sort: Option<&[Sorter]>) {
if skip <= 0 || sort.is_some_and(|s| !s.is_empty()) {
return;
}
warn_once(type_name, || {
tracing::warn!(
type_name = %type_name,
skip = skip,
"paginating a view with no sort in effect: page contents are undefined, \
declare Query::default_sort() ending in a unique field such as the id \
(logged once per view)"
);
});
}
#[cfg(test)]
mod tests {
use crate::log_capture::events_of;
use super::*;
use crate::read::SortDirection;
fn sorters(field: &str) -> Vec<Sorter> {
vec![Sorter {
field: field.to_string(),
direction: SortDirection::Asc,
}]
}
#[test]
fn paginating_without_a_sort_warns_with_the_fields_needed_to_act() {
let events = events_of(|| warn_if_page_order_undefined("fields_view", 20, None));
assert_eq!(events.len(), 1, "exactly one warning, got {events:?}");
let event = &events[0];
assert!(
event.starts_with("WARN "),
"degraded but recoverable: {event}"
);
assert!(
event.contains("type_name=fields_view"),
"the view must be named so the caller knows where to add a sort: {event}"
);
assert!(
event.contains("skip=20"),
"and the offset that triggered it: {event}"
);
assert!(
event.contains("default_sort"),
"the message must name the remedy: {event}"
);
}
#[test]
fn a_view_is_warned_about_once_and_not_again() {
let first = events_of(|| warn_if_page_order_undefined("once_view", 20, None));
assert_eq!(first.len(), 1, "the first call warns, got {first:?}");
let second = events_of(|| {
warn_if_page_order_undefined("once_view", 20, None);
warn_if_page_order_undefined("once_view", 40, None);
});
assert!(
second.is_empty(),
"and no call after it does, got {second:?}"
);
}
#[test]
fn a_second_view_gets_its_own_warning() {
let events = events_of(|| {
warn_if_page_order_undefined("view_a", 20, None);
warn_if_page_order_undefined("view_b", 20, None);
});
assert_eq!(events.len(), 2, "got {events:?}");
assert!(events[0].contains("type_name=view_a"), "{}", events[0]);
assert!(events[1].contains("type_name=view_b"), "{}", events[1]);
}
#[test]
fn an_empty_sort_list_counts_as_no_sort() {
let events = events_of(|| warn_if_page_order_undefined("empty_sort_view", 20, Some(&[])));
assert_eq!(events.len(), 1, "got {events:?}");
}
#[test]
fn the_first_page_is_silent() {
assert!(events_of(|| warn_if_page_order_undefined("first_page_view", 0, None)).is_empty());
assert!(
events_of(|| warn_if_page_order_undefined("first_page_view", 0, Some(&[]))).is_empty()
);
}
#[test]
fn a_page_with_a_sort_in_effect_is_silent() {
let sort = sorters("created_at");
assert!(
events_of(|| warn_if_page_order_undefined("sorted_view", 20, Some(&sort))).is_empty()
);
assert!(
events_of(|| warn_if_page_order_undefined("sorted_view", 0, Some(&sort))).is_empty()
);
}
}