hive-forge: page past the forge's 50-row clamp; show list --json truncation
The forge clamps page_size to 50. branches, pr reviews, the review bodies merged into `comments <pr>`, and view's comment list each sent one request, so anything past the first page was dropped silently. labels::repo_labels did loop, but asked for 100/page and stopped on `len < 100`, so a clamped full page of 50 ended the walk. Add verbs::all_pages: requests 50/page until a short page, capped at 50 pages with a stderr warning when the cap is hit. Use it in all of the above; repo-labels now calls labels::repo_labels instead of its own copy of the loop. list --json now prints the same "N of M shown" stderr trailer as the pretty output; its paging and stdout shape are unchanged. Closes #5051
This commit is contained in:
parent
52f589780f
commit
0e5c61d12c
9 changed files with 164 additions and 90 deletions
|
|
@ -120,9 +120,99 @@ pub(crate) fn clamp_limit(requested: u64) -> (u64, bool) {
|
|||
(limit, limit < requested)
|
||||
}
|
||||
|
||||
/// [`PAGE_SIZE`] as the `u32` the generated client's `.page_size()` takes.
|
||||
const PAGE_SIZE_U32: u32 = 50;
|
||||
|
||||
/// Runaway cap for [`all_pages`]: at most `MAX_PAGES * PAGE_SIZE` rows.
|
||||
const MAX_PAGES: u32 = 50;
|
||||
|
||||
/// Every row of a paged forge listing. `fetch(page, page_size)` sends one
|
||||
/// request; the walk stops at the first page shorter than `page_size`.
|
||||
/// The page size is the forge's clamp, not a larger request: a clamped
|
||||
/// full page would read as short and end the walk after one page.
|
||||
/// Stopping at [`MAX_PAGES`] prints a warning on stderr, so a capped
|
||||
/// result is never presented as the whole population.
|
||||
pub(crate) fn all_pages<T>(fetch: impl FnMut(u32, u32) -> Result<Vec<T>>) -> Result<Vec<T>> {
|
||||
let (rows, capped) = walk_pages(MAX_PAGES, fetch)?;
|
||||
if capped {
|
||||
eprintln!(
|
||||
"hive-forge: stopped after {MAX_PAGES} pages ({} rows); the result may be incomplete",
|
||||
rows.len()
|
||||
);
|
||||
}
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
/// [`all_pages`]' loop, with the cap as a parameter. Returns the rows and
|
||||
/// whether the walk hit `max_pages` without seeing a short page.
|
||||
fn walk_pages<T>(
|
||||
max_pages: u32,
|
||||
mut fetch: impl FnMut(u32, u32) -> Result<Vec<T>>,
|
||||
) -> Result<(Vec<T>, bool)> {
|
||||
let mut rows = Vec::new();
|
||||
for page in 1..=max_pages {
|
||||
let batch = fetch(page, PAGE_SIZE_U32)?;
|
||||
let short = batch.len() < PAGE_SIZE_U32 as usize;
|
||||
rows.extend(batch);
|
||||
if short {
|
||||
return Ok((rows, false));
|
||||
}
|
||||
}
|
||||
Ok((rows, true))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod page_limit_tests {
|
||||
use super::{MAX_LIMIT, PAGE_SIZE, clamp_limit};
|
||||
use anyhow::Result;
|
||||
|
||||
use super::{MAX_LIMIT, PAGE_SIZE, PAGE_SIZE_U32, clamp_limit, walk_pages};
|
||||
|
||||
/// A fake listing of `total` rows, served `page_size` at a time.
|
||||
fn listing(total: usize) -> impl FnMut(u32, u32) -> Result<Vec<usize>> {
|
||||
move |page, size| {
|
||||
let (page, size) = (page as usize, size as usize);
|
||||
Ok(((page - 1) * size..(page * size).min(total)).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_size_u32_matches_page_size() {
|
||||
assert_eq!(u64::from(PAGE_SIZE_U32), PAGE_SIZE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walk_pages_collects_every_row_past_the_first_page() {
|
||||
let (rows, capped) = walk_pages(50, listing(86)).unwrap();
|
||||
assert_eq!(rows, (0..86).collect::<Vec<_>>());
|
||||
assert!(!capped);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walk_pages_stops_on_the_empty_page_after_an_exact_multiple() {
|
||||
let mut calls = 0;
|
||||
let mut inner = listing(100);
|
||||
let (rows, capped) = walk_pages(50, |p, s| {
|
||||
calls += 1;
|
||||
inner(p, s)
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(rows.len(), 100);
|
||||
assert_eq!(calls, 3);
|
||||
assert!(!capped);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walk_pages_reports_hitting_the_cap() {
|
||||
let (rows, capped) = walk_pages(2, listing(500)).unwrap();
|
||||
assert_eq!(rows.len(), 100);
|
||||
assert!(capped);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walk_pages_propagates_a_fetch_error() {
|
||||
let err = walk_pages::<usize>(5, |_, _| anyhow::bail!("boom")).unwrap_err();
|
||||
assert_eq!(err.to_string(), "boom");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clamp_limit_passes_small_requests_through() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue