fix(api): don't count $ne/$nin-excluded languages toward the language cap (#1764)#1769
Open
johan-bell wants to merge 2 commits into
Open
fix(api): don't count $ne/$nin-excluded languages toward the language cap (#1764)#1769johan-bell wants to merge 2 commits into
johan-bell wants to merge 2 commits into
Conversation
… cap (#1764) The /query language validator collected every string leaf under a `language` field into the distinct-language set used for the non-CMS language cap. Because exclusion operators start with `$`, walkSelector preserved `owningField = "language"` through them, so `{language: {$ne: "fr"}}` and `{language: {$nin: [...]}}` counted the EXCLUDED ids toward the cap — a latent over-count that could reject a legitimate narrowing query. Exclusion operators ($ne/$nin) now clear the owning field when descending, so their values are not collected. Included ids ($eq/$in/equality) still count. Adds tests for $ne/$nin exclusion and the mixed include+exclude case. Closes #1764 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Review summary — 🟡 Approve with nitsSmall, clean fix (+56 −5). No squash-merge diff artifact here —
🤖 AI-assisted review summary (Claude Code). Read-only analysis of the PR diff — not a substitute for human review. |
…lector
Per review: {language: {$not: {$in: [...]}}} and {$not: {$eq: ...}} are
semantically exclusions (same as $ne/$nin) but $not was bucketed as a
plain logical operator, so it preserved the owning field and its wrapped
ids still counted toward the cap. $not now clears the owning field like
the other exclusion operators - checked first, since $not remains (and
still needs to remain) a member of LOGICAL_OPERATORS for its own taxonomy.
The one deliberately unhandled case: a double-negated $not: {$ne: x}
(meaning "must equal x") gets miscategorized as an exclusion by this
blanket rule - documented in the EXCLUSION_OPERATORS comment as an
unrealistic shape for a scalar language field, not worth special-casing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
The
/queryuniversal validator enforces a per-request language cap for non-CMS queries by collecting the distinct language ids a selector references.walkSelectorcollected every string leaf whose nearest owning field islanguage.The bug: exclusion operators start with
$, sowalkSelectorpreservedowningField = "language"when descending into them. That meant:Exclusion operators narrow the result — they reference languages the query wants to exclude, not include — so they should never count toward the cap. This was a latent over-count that could reject a perfectly legitimate narrowing query.
Change
EXCLUSION_OPERATORSset ($ne,$nin). WhenwalkSelectordescends into one, it clears the owning field so the excluded ids aren't collected.$eq/$in) still count exactly as before.$ninover-cap exclusion accepted,$neexclusion on top of an at-cap$inaccepted, and a mixed case proving over-cap inclusions are still rejected while a sibling exclusion is ignored.Verification
npx jest src/validation/query/validateQuery.spec.ts→ 46 passed (11 in the language-cap block, incl. 3 new).Closes #1764