pub mod engine;
pub mod jsonl;
pub use engine::{Capped, PreviewPage, PreviewRequest, read_capped};
pub const DEFAULT_PREVIEW_ROWS: usize = 500;
pub const MAX_PREVIEW_ROWS: usize = 5_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RowRequest {
Count(usize),
All,
}
impl RowRequest {
pub fn parse(raw: &str) -> Result<Self, String> {
let trimmed = raw.trim();
if trimmed.eq_ignore_ascii_case("all") {
return Ok(Self::All);
}
match trimmed.parse::<usize>() {
Ok(0) => Ok(Self::All),
Ok(n) => Ok(Self::Count(n)),
Err(_) => Err(format!(
"row_count_to_load must be a row count or `all` (`0` also means all) — got `{raw}`"
)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RowCap {
Rows(usize),
Unlimited,
}
impl RowCap {
pub fn rows(self) -> Option<usize> {
match self {
Self::Rows(n) => Some(n),
Self::Unlimited => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PreviewConfig {
pub enabled: bool,
pub default_rows: usize,
pub max_rows: usize,
}
impl Default for PreviewConfig {
fn default() -> Self {
Self {
enabled: false,
default_rows: DEFAULT_PREVIEW_ROWS,
max_rows: MAX_PREVIEW_ROWS,
}
}
}
impl PreviewConfig {
pub fn new(enabled: bool, default_rows: usize, max_rows: usize) -> Self {
let clamped = if max_rows > 0 && default_rows > max_rows {
tracing::warn!(
requested = default_rows,
effective = max_rows,
max_rows,
"preview default row count clamped to the hard cap \
(--preview-default-rows above --preview-max-rows)"
);
max_rows
} else {
default_rows
};
Self {
enabled,
default_rows: clamped,
max_rows,
}
}
pub fn default_rows(&self) -> Option<usize> {
(self.default_rows > 0).then_some(self.default_rows)
}
pub fn max_rows(&self) -> Option<usize> {
(self.max_rows > 0).then_some(self.max_rows)
}
pub fn resolve_rows(&self, requested: Option<RowRequest>) -> RowCap {
let asked = match requested {
None => self.default_rows,
Some(RowRequest::All) => 0,
Some(RowRequest::Count(n)) => n,
};
match (asked, self.max_rows) {
(0, 0) => RowCap::Unlimited,
(0, ceiling) => RowCap::Rows(ceiling),
(n, 0) => RowCap::Rows(n),
(n, ceiling) => RowCap::Rows(n.min(ceiling)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_are_off_with_the_documented_caps() {
let c = PreviewConfig::default();
assert!(!c.enabled, "preview must be opt-in");
assert_eq!(c.default_rows, 500);
assert_eq!(c.max_rows, 5_000);
}
#[test]
fn an_omitted_row_count_uses_the_soft_cap() {
let c = PreviewConfig::new(true, 25, 1000);
assert_eq!(c.resolve_rows(None), RowCap::Rows(25));
}
#[test]
fn a_request_over_the_hard_cap_is_clamped_not_honoured() {
let c = PreviewConfig::new(true, 100, 500);
assert_eq!(
c.resolve_rows(Some(RowRequest::Count(10_000))),
RowCap::Rows(500)
);
assert_eq!(
c.resolve_rows(Some(RowRequest::Count(499))),
RowCap::Rows(499)
);
assert_eq!(
c.resolve_rows(Some(RowRequest::Count(usize::MAX))),
RowCap::Rows(500)
);
}
#[test]
fn asking_for_everything_is_clamped_by_a_configured_ceiling() {
let c = PreviewConfig::new(true, 100, 500);
assert_eq!(c.resolve_rows(Some(RowRequest::All)), RowCap::Rows(500));
}
#[test]
fn asking_for_everything_is_unlimited_only_when_the_operator_lifted_the_ceiling() {
let c = PreviewConfig::new(true, 500, 0);
assert_eq!(c.resolve_rows(Some(RowRequest::All)), RowCap::Unlimited);
assert_eq!(c.resolve_rows(Some(RowRequest::Count(7))), RowCap::Rows(7));
}
#[test]
fn a_zero_soft_cap_means_everything_by_default() {
let unbounded = PreviewConfig::new(true, 0, 0);
assert_eq!(unbounded.resolve_rows(None), RowCap::Unlimited);
let bounded = PreviewConfig::new(true, 0, 250);
assert_eq!(bounded.resolve_rows(None), RowCap::Rows(250));
}
#[test]
fn a_soft_cap_above_the_hard_cap_is_clamped_to_it() {
let c = PreviewConfig::new(true, 5_000, 200);
assert_eq!(c.default_rows, 200, "the hard cap wins");
assert_eq!(c.max_rows, 200);
assert_eq!(c.resolve_rows(None), RowCap::Rows(200));
}
#[test]
fn the_wire_shape_reports_no_limit_as_null() {
let c = PreviewConfig::new(true, 0, 0);
assert_eq!(c.default_rows(), None);
assert_eq!(c.max_rows(), None);
let c = PreviewConfig::new(true, 10, 20);
assert_eq!(c.default_rows(), Some(10));
assert_eq!(c.max_rows(), Some(20));
}
#[test]
fn row_request_parses_counts_and_every_spelling_of_all() {
assert_eq!(RowRequest::parse("25").unwrap(), RowRequest::Count(25));
assert_eq!(RowRequest::parse(" 25 ").unwrap(), RowRequest::Count(25));
assert_eq!(RowRequest::parse("all").unwrap(), RowRequest::All);
assert_eq!(RowRequest::parse("ALL").unwrap(), RowRequest::All);
assert_eq!(RowRequest::parse("0").unwrap(), RowRequest::All);
}
#[test]
fn a_nonsense_row_count_is_an_error_not_a_silent_default() {
for bad in ["lots", "-1", "1.5", ""] {
let err = RowRequest::parse(bad).unwrap_err();
assert!(err.contains("row_count_to_load"), "{bad}: {err}");
}
}
}