Skip to content

feat(core): request-context threading & per-parameter schema validate hooks - #807

Merged
tada5hi merged 2 commits into
masterfrom
feat/context-hooks-806
Jul 21, 2026
Merged

feat(core): request-context threading & per-parameter schema validate hooks#807
tada5hi merged 2 commits into
masterfrom
feat/context-hooks-806

Conversation

@tada5hi

@tada5hi tada5hi commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Implements RFC #806, phase 1: per-request dynamic authorization at decode time.

What

  • context parse/decode optionParseQueryOptions/ParseParameterOptions carry an opaque context?: unknown (typically the authenticated actor). URLCodec.decode/decodeAsync forward options verbatim, so the codec entry points get it for free.
  • Per-key validate hooks on the relations, fields and sort sub-schemas — (name, context) => MaybeAsync<boolean | undefined>. Hooks run on the canonical (alias-resolved) key against the schema that governs it: include=items.realm invokes the root schema's hook with items and the item schema's hook with realm (via schemaMapping), so an include can never bypass the related schema's own gate. A rejected relation also prunes every deeper relation reached through it.
  • filters.validate arity extension — the existing leaf validator now receives the context as a second argument (backward compatible; all three dialects forward it).
  • Failure policy — hook rejections follow the existing dial: dropped by default, thrown under throwOnFailure with the new ErrorCode.KEY_VALIDATE_REJECTED (distinguishable from allow-list misses).
  • Sync/async — mirrors the filters-validator contract: hooks are collected during resolution and evaluated afterwards; sync parse() refuses a thenable with SCHEMA_VALIDATOR_ASYNC_REQUIRES_ASYNC_PARSER, parseAsync()/decodeAsync() await sequentially.
  • TypingSchema, SchemaRegistry, defineSchema and the per-parameter options/factories carry a defaulted CONTEXT generic, so hooks are typed at the definition site (defineSchema<User, Actor>).
  • Removed the never-invoked fields.verify option and its VerifyFn type (declared with a context parameter but dead since introduction).

Design decisions

  • Obligations over an async ResolutionScope. Key resolution stays fully synchronous; parsers record (key, path, schema) obligations during resolution and evaluate them once the parameter is assembled (applyKeySchemaValidation/...Async in core). One resolution pass serves both entry points, and pruning is name-based since Fields.execute clones nodes.
  • Client input only. Schema defaults are server-authored and bypass the hooks; rejected keys are removed after parameter assembly, so fields/sort defaults do not re-materialize when a hook empties the selection (documented).
  • CONTEXT defaults to any, not unknown — under strict function-type contravariance, unknown would make Schema<User, Actor> unassignable to the bare Schema<RECORD> references used internally (resolver, registry, parsers).
  • Sort hooks run after tuple-group matching — a vetoed member drops from a matched group (documented).

Out of scope (per the RFC)

Relation-scoped residual conditions (join-ON rendering), ExecuteOptions.context on backends, and any policy vocabulary — rapiq stays IR + hooks + interpreters.

Docs

  • guide/schemas.md: new "Validate hooks & parse context" section + per-parameter option table rows
  • guide/recipes/authorization.md: new "Gating: per-actor checks at decode time" layer (motivating include-permission case) + layered-defense row
  • guide/filters.md, guide/errors.md: context argument, KEY_VALIDATE_REJECTED

Tests

  • New parser-simple spec: relation drop/keep with context, undefined context default, target-schema resolution for deep paths, descendant pruning, throwOnFailure, sync-refuses-async, parseAsync awaiting, fields/sort veto + defaults-bypass, filters context forwarding, end-to-end context threading through SimpleParser.
  • Full monorepo: 9/9 build, 8/8 test targets green (uncached), lint clean on changed files, docs site builds.

Closes #806

Summary by CodeRabbit

  • New Features

    • Added per-key validation hooks for fields, relations, and sort parameters.
    • Parse and decode operations can now pass custom context to validation hooks.
    • Rejected keys are automatically removed, with an option to raise a parse error instead.
    • Added support for asynchronous validation during async parsing.
    • Filter validation hooks now receive the supplied context.
  • Documentation

    • Added guidance and examples for context-aware validation and authorization scenarios.
    • Documented the new key-validation error code and rejection behavior.

… hooks

Parse/decode calls accept an opaque context option (e.g. the
authenticated actor) that is forwarded to schema validate hooks:

- relations/fields/sort schemas gain a per-key validate hook, invoked
  on the canonical key against the schema that governs it (the target
  schema for dotted paths); rejections follow the drop-vs-throwOnFailure
  policy via the new KEY_VALIDATE_REJECTED error code, and a rejected
  relation prunes its descendants
- the filters validate hook receives the context as second argument
- hooks are collected during resolution and evaluated afterwards, so
  sync parse() refuses async hooks (validatorAsyncRequiresAsyncParser)
  while parseAsync()/decodeAsync() await them sequentially
- Schema/SchemaRegistry/define* carry a defaulted CONTEXT generic for
  definition-site hook typing; schema defaults bypass the hooks
- the never-invoked fields.verify option (VerifyFn) is removed
Copilot AI review requested due to automatic review settings July 20, 2026 21:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Context-aware validate hooks are added for schema keys and filters. Parse options now carry request context through query parsing, while synchronous and asynchronous parsers validate and prune rejected fields, relations, and sorts. Error codes, public types, tests, and documentation are updated.

Context-aware validation

Layer / File(s) Summary
Schema and error contracts
packages/core/src/schema/..., packages/core/src/parser/types.ts, packages/core/src/errors/...
Schema generics and per-key validate hooks now accept typed context; validation rejection errors are exposed.
Context propagation and filter validation
packages/core/src/parser/query.ts, packages/core/src/parser/parameter/filters/..., packages/parser-*/.../filters/module.ts
Query and parameter context is forwarded through synchronous and asynchronous filter validation.
Deferred key validation engine
packages/core/src/parser/parameter/validate.ts
Pending key validations are resolved synchronously or asynchronously, returning rejected paths or throwing configured errors.
Field, relation, and sort parser integration
packages/parser-simple/src/parameter/{fields,relations,sorts}/module.ts
Simple parsers collect nested validation obligations and prune rejected output paths.
Validation coverage and documentation
packages/parser-simple/test/unit/parser/validate-context.spec.ts, packages/docs/guide/...
Tests and guides cover context forwarding, rejection behavior, nested validation, defaults, and async hooks.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SimpleParser
  participant SchemaValidator
  participant ParsedQuery
  Client->>SimpleParser: parseAsync(input, context)
  SimpleParser->>SchemaValidator: validate requested keys and filters with context
  SchemaValidator-->>SimpleParser: accepted values or rejected paths
  SimpleParser-->>ParsedQuery: pruned parsed output
Loading

Possibly related PRs

  • tada5hi/rapiq#700: Modifies related schema validate hook surfaces and filter types.
  • tada5hi/rapiq#745: Introduces the query parsing orchestration extended here for context forwarding.
  • tada5hi/rapiq#766: Overlaps with the filter validation pipeline and validator contracts.

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: request-context threading and schema validate hooks in core.
Linked Issues check ✅ Passed The changes implement the RFC goals: context threading, per-key validate hooks, async support, error handling, and removal of fields.verify.
Out of Scope Changes check ✅ Passed The added docs, tests, and code all support the request-context and validate-hook feature set; no unrelated changes stand out.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/context-hooks-806

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/parser-simple/test/unit/parser/validate-context.spec.ts (1)

187-231: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider adding a regression test for excluded fields under a rejecting hook.

Given the EXCLUDE-operator issue flagged in packages/parser-simple/src/parameter/fields/module.ts (pending validated even for -field exclusions, throwing under throwOnFailure for a field the client isn't trying to read), a test like parser.parse(['-email'], { schema: throwOnFailureSchemaRejectingEmail }) expecting no throw would pin down the fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/parser-simple/test/unit/parser/validate-context.spec.ts` around
lines 187 - 231, The validation tests in the fields parser should cover excluded
fields: add a regression case alongside the existing validate-hook tests using a
schema with throwOnFailure enabled whose validate hook rejects the excluded
field, then parse that field with the EXCLUDE operator (for example, “-email”)
and assert parsing completes without throwing. Ensure the exclusion prevents
validation for fields the client is not requesting.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/parser/parameter/validate.ts`:
- Around line 73-75: Use the full client-facing validation path when
constructing key rejection errors: update both synchronous and asynchronous
validators in validate.ts to pass entry.path, rather than entry.key, to
options.errors.keyValidateRejected at lines 73-75 and 103-105.

In `@packages/parser-simple/src/parameter/fields/module.ts`:
- Around line 145-151: Only add fields to pending in the non-EXCLUDE operator
path, so excluded fields skip read-access validation while remaining in
fields.value for exclusion processing. Also de-duplicate pending entries by
canonical field identity, matching the existing output.value de-duplication, so
alias expansion and relation traversal invoke validation only once.

In `@packages/parser-simple/src/parameter/relations/module.ts`:
- Around line 131-138: Update the mapped-alias handling around Relation creation
and pending validation so dotted mapping targets validate every path segment,
including the root schema’s relations.validate for items. Mirror the
parent-segment processing used for literal dotted inputs, or reject dotted
mapping targets before enqueueing; preserve existing behavior for single-segment
mappings.

---

Nitpick comments:
In `@packages/parser-simple/test/unit/parser/validate-context.spec.ts`:
- Around line 187-231: The validation tests in the fields parser should cover
excluded fields: add a regression case alongside the existing validate-hook
tests using a schema with throwOnFailure enabled whose validate hook rejects the
excluded field, then parse that field with the EXCLUDE operator (for example,
“-email”) and assert parsing completes without throwing. Ensure the exclusion
prevents validation for fields the client is not requesting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 12abc9e9-a699-4937-8495-29280b047346

📥 Commits

Reviewing files that changed from the base of the PR and between 45981e7 and 6929c10.

📒 Files selected for processing (37)
  • packages/core/src/errors/code.ts
  • packages/core/src/errors/parse.ts
  • packages/core/src/parser/parameter/filters/types.ts
  • packages/core/src/parser/parameter/filters/validate.ts
  • packages/core/src/parser/parameter/index.ts
  • packages/core/src/parser/parameter/relations/types.ts
  • packages/core/src/parser/parameter/sort/types.ts
  • packages/core/src/parser/parameter/validate.ts
  • packages/core/src/parser/query.ts
  • packages/core/src/parser/types.ts
  • packages/core/src/schema/define.ts
  • packages/core/src/schema/module.ts
  • packages/core/src/schema/parameter/fields/define.ts
  • packages/core/src/schema/parameter/fields/schema.ts
  • packages/core/src/schema/parameter/fields/types.ts
  • packages/core/src/schema/parameter/filters/define.ts
  • packages/core/src/schema/parameter/filters/schema.ts
  • packages/core/src/schema/parameter/filters/types.ts
  • packages/core/src/schema/parameter/relations/define.ts
  • packages/core/src/schema/parameter/relations/schema.ts
  • packages/core/src/schema/parameter/relations/types.ts
  • packages/core/src/schema/parameter/sort/define.ts
  • packages/core/src/schema/parameter/sort/schema.ts
  • packages/core/src/schema/parameter/sort/types.ts
  • packages/core/src/schema/registry/module.ts
  • packages/core/src/schema/types.ts
  • packages/docs/guide/errors.md
  • packages/docs/guide/filters.md
  • packages/docs/guide/recipes/authorization.md
  • packages/docs/guide/schemas.md
  • packages/parser-expression/src/parameter/filters/module.ts
  • packages/parser-mongo/src/parameter/filters/module.ts
  • packages/parser-simple/src/parameter/fields/module.ts
  • packages/parser-simple/src/parameter/filters/module.ts
  • packages/parser-simple/src/parameter/relations/module.ts
  • packages/parser-simple/src/parameter/sorts/module.ts
  • packages/parser-simple/test/unit/parser/validate-context.spec.ts

Comment thread packages/core/src/parser/parameter/validate.ts
Comment thread packages/parser-simple/src/parameter/fields/module.ts
Comment thread packages/parser-simple/src/parameter/relations/module.ts
@tada5hi

tada5hi commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai pause

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews paused.

- throw KEY_VALIDATE_REJECTED with the full client-facing path
- skip read-access validation for EXCLUDE-operator fields
- validate every traversed segment of dotted relation mapping targets
- de-duplicate hook invocations for repeated client input
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RFC: request-context threading & per-parameter validate hooks (dynamic authorization at decode time)

2 participants