Skip to main content

acme_proxy/cli/
window.rs

1//! The `--limit`/`--offset` window every paged listing takes.
2//!
3//! Four commands page — `account list`, `order list` (both queries) and
4//! `audit list` — and every one of them wants the same default, the same two
5//! clamps and the same envelope under `--json`. That envelope is
6//! [`crate::cli::render::json_page`]; this is the window that produced it.
7
8/// Default rows per page.
9///
10/// There is deliberately no "everything" spelling, and `--limit 0` is not a way
11/// around it: `orders` and `audit_log` each grow a row per issuance for the
12/// life of the deployment, so on a year-old CA an unwindowed listing is a
13/// terminal full of scrollback and a table loaded into memory. Page with
14/// `--offset`; the `N of M row(s)` footer is what says there is more.
15pub const DEFAULT_LIMIT: i64 = 50;
16
17/// A resolved window, clamped into something a query can be handed.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct Window {
20    pub limit: i64,
21    pub offset: i64,
22}
23
24impl Window {
25    /// Clamps a caller's window rather than refusing it.
26    ///
27    /// A `--limit 0` or a negative offset is nonsense rather than an attack, and
28    /// answering with the smallest usable page is more useful than an error;
29    /// passed through to SQL, `LIMIT -1` means *no limit* in SQLite, which is
30    /// the one answer a window must never accidentally give.
31    ///
32    /// Deliberately **not** clamped to `admin.page_size_max`: that key is a
33    /// ceiling on what an HTTP caller may ask the server for, and this front end
34    /// answers to a shell on the host. There is no upper bound on the offset
35    /// either, unlike `webadmin::handlers::paging`, because nothing here
36    /// computes `offset + limit` — a terminal has no "next page" link to place.
37    #[must_use]
38    pub fn resolve(limit: i64, offset: i64) -> Self {
39        Self {
40            limit: limit.max(1),
41            offset: offset.max(0),
42        }
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn a_nonsense_window_is_clamped_rather_than_refused() {
52        for (limit, expected) in [(50, 50), (1, 1), (0, 1), (-5, 1)] {
53            assert_eq!(Window::resolve(limit, 0).limit, expected, "limit={limit}");
54        }
55        for (offset, expected) in [(0, 0), (7, 7), (-7, 0)] {
56            assert_eq!(
57                Window::resolve(50, offset).offset,
58                expected,
59                "offset={offset}"
60            );
61        }
62    }
63}