fix(forge): validate list's label + milestone filters, and paginate both
A filter value the forge cannot resolve is DISCARDED, not rejected, so a typo does not narrow the result set -- it returns the unfiltered one. That does not waste a query, it inverts the answer: "is anything open in this milestone" comes back as every open issue and reads as yes, and a duplicate check gets a list that never narrowed. `list` now resolves both before querying. Labels reuse the write side's resolver; the ids are discarded because this endpoint filters by name, so resolution here is a spell-check rather than a lookup -- reusing it keeps the message identical to the one the write side has always produced. Milestones accept a title or an id and are checked against the ALL-state set: filtering on a closed milestone is a normal query, and validating against open-only would reject exactly the retrospective ones. Both fetchers paginate. `repo_labels` asked for one page of 100 and treated it as the population -- the inverse of the trailer bug, same root: a valid label past the cut fails to resolve, and the error then prints an "available labels" list that is itself truncated, so the message argues for the typo. `--assignee` / `--author` stay unvalidated on purpose: someone who has left still legitimately appears on old issues, so a login that is not a current member is not necessarily a typo. Also drops the docs paragraph claiming unknown labels are silently dropped on the write side; that has not been true since the resolver landed.
This commit is contained in:
parent
433b294099
commit
0aa9a854bc
4 changed files with 206 additions and 36 deletions
|
|
@ -90,18 +90,38 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// First page (100) of the repo's label set, for name → id resolution.
|
||||
/// `pub(crate)` so other creation verbs (`issue-create`, `pr-create`) can
|
||||
/// resolve a `--label` name to the id the create-payload structs need
|
||||
/// without duplicating the lookup.
|
||||
/// The repo's whole label set, for name → id resolution. `pub(crate)` so
|
||||
/// other verbs (`issue-create`, `pr-create`, `list`) can resolve a label
|
||||
/// name without duplicating the lookup.
|
||||
///
|
||||
/// Paginated, where this used to ask for one page of 100 — **a page size
|
||||
/// with no page loop is a lie the size of the page.** Truncation here is
|
||||
/// worse than it looks: it doesn't drop a result, it makes a *valid*
|
||||
/// label past the cut fail to resolve, and then prints an "available
|
||||
/// labels" list that is itself incomplete, so the error argues for the
|
||||
/// typo. Same root as the `list` trailer bug (assuming one request
|
||||
/// returns the whole population), opposite direction — that one was a
|
||||
/// false positive, this is a false negative.
|
||||
pub(crate) fn repo_labels(client: &Client) -> Result<Vec<Label>> {
|
||||
/// Generous cap so a misbehaving server can't spin us forever.
|
||||
const MAX_PAGES: u32 = 50;
|
||||
const PAGE: u32 = 100;
|
||||
let (owner, name) = client.owner_repo()?;
|
||||
let (_, labels) = client
|
||||
.api()
|
||||
.issue_list_labels(owner, name, IssueListLabelsQuery::default())
|
||||
.page_size(100)
|
||||
.send()?;
|
||||
Ok(labels)
|
||||
let mut all = Vec::new();
|
||||
for page in 1..=MAX_PAGES {
|
||||
let (_, batch) = client
|
||||
.api()
|
||||
.issue_list_labels(owner, name, IssueListLabelsQuery::default())
|
||||
.page(page)
|
||||
.page_size(PAGE)
|
||||
.send()?;
|
||||
let short = batch.len() < PAGE as usize;
|
||||
all.extend(batch);
|
||||
if short {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(all)
|
||||
}
|
||||
|
||||
/// Resolve label names to ids, hard-erroring if any name doesn't match an
|
||||
|
|
|
|||
Loading…
Reference in a new issue