fix: enforce NonNegativeInt on GraphQL pagination arguments#9965
fix: enforce NonNegativeInt on GraphQL pagination arguments#9965iddocohen wants to merge 4 commits into
Conversation
Add a NonNegativeInt GraphQL scalar and apply it to the limit, offset, log_limit and log_offset arguments across the branch, event, task, search, relationship, account-token, resource-manager and diff-tree queries, as well as the generated node filters. Negative or non-integer pagination values are now rejected at the schema level with an error that names the offending argument and its source location. This extends the resolver-level guard on the main node resolver to every paginated query by validating declaratively at the schema level. The scalar raises ValidationError so the executor wraps it with the argument type name and location, matching the behaviour of the built-in scalars. parse_literal accepts the variables argument passed by the executor, so a paginated query that also declares query variables is coerced correctly instead of failing argument validation. Update the frontend query documents and regenerate the GraphQL schema and frontend generated types accordingly.
There was a problem hiding this comment.
2 issues found across 30 files
Confidence score: 4/5
- In
backend/infrahub/graphql/queries/branch.py,InfrahubBranchQueryList.limitacceptsNonNegativeIntwhileinfrahub_branch_resolverrejects values< 1, so a client can pass schema validation withlimit=0and then fail at runtime with a different error path; align the schema and resolver rule (or normalize0in the resolver) before merging to avoid confusing client-side failures. - In
backend/infrahub/graphql/queries/account.py, the GraphQL type change appears out of sync with the linked SDK fixture (opsmill/infrahub-sdk-python/tests/fixtures/unit/test_graphql_plugin/schema.graphql), which can cause downstream test or codegen drift even if this repo is otherwise stable; update the fixture (or coordinate a companion SDK PR) to de-risk integration mismatches.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="backend/infrahub/graphql/queries/account.py">
<violation number="1" location="backend/infrahub/graphql/queries/account.py:67">
P3: The linked SDK repo's test fixture schema at opsmill/infrahub-sdk-python/tests/fixtures/unit/test_graphql_plugin/schema.graphql:8577 still declares `InfrahubAccountToken(limit: Int, offset: Int)` rather than `NonNegativeInt`, which will be out of date once this PR merges. If the SDK loads this fixture for GraphQL plugin tests or codegen, those tests could fail or produce stale generated code.</violation>
</file>
<file name="backend/infrahub/graphql/queries/branch.py">
<violation number="1" location="backend/infrahub/graphql/queries/branch.py:155">
P3: InfrahubBranchQueryList's limit arg uses NonNegativeInt (allows 0), but infrahub_branch_resolver rejects limit < 1. Clients passing limit=0 get a schema-level pass then a ValidationError in the resolver with a different phrasing. Consider either documenting the constraint or aligning the resolver check with NonNegativeInt's semantics.</violation>
</file>
Shadow auto-approve: would not auto-approve because issues were found.
Re-trigger cubic
…odegen The NonNegativeInt scalar defaulted to `unknown` in the generated GraphQL types, so query documents using it for limit/offset inferred `unknown` variables and failed TypeScript checks where a number was expected. Map NonNegativeInt to number in the gql.tada setupSchema augmentation and in the graphql-codegen scalar config, and regenerate the type caches.
There was a problem hiding this comment.
0 issues found across 4 files (changes from recent commits).
Shadow auto-approve: would not auto-approve. Auto-approval blocked by 4 unresolved issues from previous reviews.
Re-trigger cubic
Reject booleans, strings and fractional floats in the NonNegativeInt variable path instead of coercing them, so variable inputs enforce the same integer contract as inline integer literals (whole-number floats are still accepted, matching the built-in Int scalar). Drop the now-unreachable negative-offset check in the branch resolver; the NonNegativeInt scalar rejects negative offsets before the resolver runs.
|
Thanks for the review. Addressed below. 1. 2. 3. Frontend scalar mapping (P2): already fixed. 4. SDK fixture |
There was a problem hiding this comment.
0 issues found across 3 files (changes from recent commits).
Shadow auto-approve: would not auto-approve. Auto-approval blocked by 3 unresolved issues from previous reviews.
Re-trigger cubic
Revert the generic node filters in the GraphQL schema manager back to Int. Generic node list pagination is already validated at the resolver level, and switching those arguments to NonNegativeInt was a breaking change for every client that paginates nodes with an Int variable (including the internal node-id fetch queries used by computed attributes, display labels and HFIDs). NonNegativeInt now applies only to the branch, event, task, search, relationship, account-token, resource-manager and diff-tree queries, which the resolver-level guard does not cover. Move the scalar next to the existing FixedGenericScalar and cover it with unit tests. Revert the frontend documents and tests that paginate generic nodes back to Int and regenerate the schema and frontend types.
|
Scoped the change down after the |
There was a problem hiding this comment.
All reported issues were addressed across 22 files (changes from recent commits).
Shadow auto-approve: would not auto-approve because issues were found.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
Latest cubic review: 1 (P2, scalars path in description): Fixed — updated the PR description; the scalar lives in 2 (P1, negative pagination on relationship/hierarchy/IPAM): This is pre-existing behavior, not a regression from this PR. Those nested/hierarchy/IPAM pagination args were |
Summary
Adds a
NonNegativeIntGraphQL scalar and applies it to the pagination arguments (limit,offset,log_limit,log_offset) of the specialized top-level queries: branch, event, task, search, relationship, account-token, resource-manager and diff-tree. Negative or non-integer pagination values on these queries are now rejected at the schema level with an error that names the offending argument and its location.The scalar lives in
backend/infrahub/graphql/scalars.py(next toFixedGenericScalar).This supersedes #7610 (originally opened from a personal fork before I joined OpsMill), rebased onto current
developand reworked per the review there.Context / scope
#7474(late errors for negative offset/limit) was already fixed and released in v1.9.5 by #9203, which added a resolver-levelvalidate_offset_and_limit()guard indefault_paginated_list_resolver— the resolver used by the generic node list queries. That guard covers generic node pagination but not the specialized query resolvers.This PR closes that gap by validating declaratively at the schema level on exactly those specialized queries. The generic node filters are intentionally left as
Int— they are already validated by #9203, and switching them toNonNegativeIntwould be a breaking change for every client (frontend, SDK, internal node-id fetch queries) that paginates a node with anIntvariable.Review feedback addressed (from #7610)
ValidationErrorrather thanGraphQLErrorso the response keeps the argument type name and source location. The scalar raisesinfrahub.exceptions.ValidationError, which graphql-core wraps asExpected value of type 'NonNegativeInt', found -1; Value must be a non-negative integerwithlocationspopulated.ValueError/TypeErrorhandled.Notable fixes beyond the original PR
parse_literalaccepts thevariablesargument the executor passes, so a paginated query that also declares query variables is coerced correctly instead of failing argument validation (the original raised a spuriousinvalid value).parse_valuevalidates strictly: booleans, strings and fractional floats are rejected (whole-number floats accepted, matching the built-inInt), so variable inputs enforce the same integer contract as inline literals.Changes
NonNegativeIntinbackend/infrahub/graphql/scalars.py, with unit tests inbackend/tests/unit/graphql/types/test_scalars.py.Int()→NonNegativeInt()on the pagination args of the eight specialized query modules.schema/schema.graphql(scalar + the eight fields) and the frontend generated GraphQL files; updated the frontend query documents for the specialized queries and mappedNonNegativeInt→numberin gql.tada / graphql-codegen.Test plan
uv run invoke format/lint,mypy— clean.codegen+betterer/biome/knipall pass.Closes #7610.