Skip to content

feat(contract): Request Validation Plugin#1000

Merged
dinwwwh merged 2 commits intomainfrom
unnoq/issue999
Sep 15, 2025
Merged

feat(contract): Request Validation Plugin#1000
dinwwwh merged 2 commits intomainfrom
unnoq/issue999

Conversation

@dinwwwh
Copy link
Copy Markdown
Member

@dinwwwh dinwwwh commented Sep 15, 2025

Closes: https://github.com/unnoq/orpc/issues/999

Summary by CodeRabbit

  • New Features

    • Introduced a client-side Request Validation plugin that validates inputs against contract schemas before sending, returns BAD_REQUEST errors with issue details, and preserves original inputs.
  • Documentation

    • Added a “Request Validation” guide with setup, integration examples, and form validation patterns; linked in the Plugins sidebar.
  • Tests

    • Added tests for valid calls, no-schema passthrough, invalid-input errors, unmatched routes, and interceptor ordering.

@vercel
Copy link
Copy Markdown

vercel bot commented Sep 15, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
orpc Ready Ready Preview Comment Sep 15, 2025 8:43am

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Sep 15, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Introduces a client-side RequestValidationPlugin for contract-driven requests, exports it publicly, adds tests for validation and error cases, and updates documentation and site navigation with a new “Request Validation” docs page.

Changes

Cohort / File(s) Summary
Docs Navigation
apps/content/.vitepress/config.ts
Adds sidebar link “Request Validation” under Plugins, pointing to /docs/plugins/request-validation.
Plugin Docs
apps/content/docs/plugins/request-validation.md
New documentation page describing RequestValidationPlugin usage, setup with oRPC links, and form validation example.
Plugin Exports
packages/contract/src/plugins/index.ts
Re-exports request validation: export * from './request-validation'.
RequestValidationPlugin
packages/contract/src/plugins/request-validation.ts, packages/contract/src/plugins/request-validation.test.ts
Implements RequestValidationPlugin and RequestValidationPluginError; resolves contract procedure by path, validates input against procedure input schema (if present), throws ORPCError(BAD_REQUEST) with issues on validation failure, and adds tests covering success, no-schema passthrough, schema failure, missing-path error, and interceptor ordering.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as Client Code
  participant CL as Client Link (StandardLink)
  participant RV as RequestValidationPlugin
  participant NX as Next Interceptors
  participant DS as Downstream (Transport/Server)

  UI->>CL: call(path, input)
  activate CL
  CL->>RV: intercept(path, input)
  activate RV
  alt Contract path found
    alt Procedure has inputSchema
      RV->>RV: validate(input)
      alt Validation passes
        RV-->>NX: next(path, input)
        activate NX
        NX-->>DS: forward(path, input)
        DS-->>NX: response
        NX-->>UI: response
        deactivate NX
      else Validation fails
        RV-->>UI: throw ORPCError(BAD_REQUEST, issues)
      end
    else No inputSchema
      RV-->>NX: next(path, input)
      activate NX
      NX-->>DS: forward(path, input)
      DS-->>NX: response
      NX-->>UI: response
      deactivate NX
    end
  else No matching contract procedure
    RV-->>UI: throw RequestValidationPluginError
  end
  deactivate RV
  deactivate CL
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

size:XL

Poem

I twitch my nose at every call and query,
Checking carrots—er, inputs—sans worry. 🥕
If forms go astray, I thump in place,
Bad requests hop back with a kindly face.
Valid paths ahead, we bound with delight.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat(contract): Request Validation Plugin" is concise, follows conventional-commit style, and accurately summarizes the primary change (adding a RequestValidationPlugin to the contract package along with tests, docs, and an export), so a reviewer scanning history will understand the main intent.
Linked Issues Check ✅ Passed The changes satisfy the objectives of issue #999 by providing a client-side RequestValidationPlugin that resolves contract procedures, validates inputs against procedure input schemas, surfaces BAD_REQUEST ORPCErrors with validation issue details and a ValidationError cause, and includes tests covering success, failure, no-schema, and ordering; documentation and a public re-export are also provided to enable usage.
Out of Scope Changes Check ✅ Passed All modifications are directly related to implementing the request validation feature: the new plugin implementation, tests, documentation page and sidebar entry, and the index re-export; there are no unrelated code changes outside the contract package and documentation.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch unnoq/issue999

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 356c4b5 and eb279aa.

📒 Files selected for processing (1)
  • packages/contract/src/plugins/request-validation.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/contract/src/plugins/request-validation.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: publish-commit
  • GitHub Check: test
  • GitHub Check: lint

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 and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @unnoq, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant new feature: the RequestValidationPlugin for the oRPC contract library. This plugin empowers developers to perform client-side validation of request payloads against their defined contract schemas, thereby enhancing data integrity and reducing the load on server-side validation. The changes encompass the plugin's core implementation, thorough unit tests to ensure its reliability, and clear, concise documentation to guide users on its setup and usage.

Highlights

  • New Request Validation Plugin: Introduced a new RequestValidationPlugin that enables client-side validation of requests against the defined contract schemas. This plugin intercepts requests and ensures data integrity before reaching the server.
  • Comprehensive Documentation: Added a new documentation page for the Request Validation Plugin, detailing its purpose, setup instructions, and practical examples, including how to leverage it for form validation.
  • Robust Test Coverage: Included extensive unit tests for the RequestValidationPlugin, covering scenarios such as successful validation, handling of procedures without input schemas, and proper error reporting for invalid inputs or missing contract procedures.
  • Error Handling: The plugin throws an ORPCError with a BAD_REQUEST code when input validation fails, providing detailed issues. It also throws a RequestValidationPluginError if a matching procedure is not found in the contract.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new RequestValidationPlugin for client-side request validation, which is a great feature for improving application robustness by catching invalid data before it hits the server. The implementation is solid, with good documentation and comprehensive tests covering various scenarios. I have one suggestion to improve the React form validation example in the documentation to follow standard React practices and avoid confusion.

Comment thread apps/content/docs/plugins/request-validation.md
@codecov
Copy link
Copy Markdown

codecov bot commented Sep 15, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Sep 15, 2025

More templates

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@1000

@orpc/client

npm i https://pkg.pr.new/@orpc/client@1000

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@1000

@orpc/experimental-durable-event-iterator

npm i https://pkg.pr.new/@orpc/experimental-durable-event-iterator@1000

@orpc/hey-api

npm i https://pkg.pr.new/@orpc/hey-api@1000

@orpc/interop

npm i https://pkg.pr.new/@orpc/interop@1000

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@1000

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@1000

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@1000

@orpc/openapi-client

npm i https://pkg.pr.new/@orpc/openapi-client@1000

@orpc/otel

npm i https://pkg.pr.new/@orpc/otel@1000

@orpc/react

npm i https://pkg.pr.new/@orpc/react@1000

@orpc/react-query

npm i https://pkg.pr.new/@orpc/react-query@1000

@orpc/experimental-react-swr

npm i https://pkg.pr.new/@orpc/experimental-react-swr@1000

@orpc/server

npm i https://pkg.pr.new/@orpc/server@1000

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@1000

@orpc/solid-query

npm i https://pkg.pr.new/@orpc/solid-query@1000

@orpc/standard-server

npm i https://pkg.pr.new/@orpc/standard-server@1000

@orpc/standard-server-aws-lambda

npm i https://pkg.pr.new/@orpc/standard-server-aws-lambda@1000

@orpc/standard-server-fetch

npm i https://pkg.pr.new/@orpc/standard-server-fetch@1000

@orpc/standard-server-node

npm i https://pkg.pr.new/@orpc/standard-server-node@1000

@orpc/standard-server-peer

npm i https://pkg.pr.new/@orpc/standard-server-peer@1000

@orpc/svelte-query

npm i https://pkg.pr.new/@orpc/svelte-query@1000

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@1000

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@1000

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@1000

@orpc/vue-colada

npm i https://pkg.pr.new/@orpc/vue-colada@1000

@orpc/vue-query

npm i https://pkg.pr.new/@orpc/vue-query@1000

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@1000

commit: eb279aa

@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Sep 15, 2025
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (6)
apps/content/docs/plugins/request-validation.md (2)

10-12: Tighten wording on minified contracts

Small grammar tweak.

-This plugin is best suited for [Contract-First Development](/docs/contract-first/define-contract). [Minified Contract](/docs/contract-first/router-to-contract#minify-export-the-contract-router-for-the-client) is **not supported** because it removes the schema from the contract.
+This plugin is best suited for [Contract-First Development](/docs/contract-first/define-contract). [Minified contracts](/docs/contract-first/router-to-contract#minify-export-the-contract-router-for-the-client) are **not supported** because they remove the schema from the contract.

42-47: Make the TSX example standalone

Add the missing React import so readers can paste‑run.

-```tsx
-import { getIssueMessage, parseFormData } from '@orpc/openapi-client/helpers'
+```tsx
+import { useState } from 'react'
+import { getIssueMessage, parseFormData } from '@orpc/openapi-client/helpers'
packages/contract/src/plugins/request-validation.ts (1)

53-57: Polish comment grammar

Minor clarity edit.

-      /**
-       * we should not use validated input here,
-       * because validated input maybe is transformed by schema
-       * leading input no longer matching expected schema
-       */
+      /**
+       * Do not pass the validated input here,
+       * because the schema may transform it,
+       * which could make the original input diverge from expectations downstream.
+       */
packages/contract/src/plugins/request-validation.test.ts (3)

61-61: Avoid async describe

describe callbacks should be synchronous in Vitest/Jest.

-describe('validate input', async () => {
+describe('validate input', () => {

86-93: Assert issues is an array for stronger guarantees

The validator returns a list; assert Array to tighten the contract.

-            issues: expect.any(Object),
+            issues: expect.any(Array),

99-106: Test the intended execution order explicitly

If the plugin runs first (recommended), the user interceptor shouldn’t be called when validation fails.

-    await expect(link.call(['not', 'found'], {}, { context: {} })).rejects.toThrow(
+    await expect(link.call(['not', 'found'], {}, { context: {} })).rejects.toThrow(
       new RequestValidationPluginError('No valid procedure found at path "not.found", this may happen when the contract router is not properly configured.'),
     )
-    await expect(interceptor.mock.results[0]?.value).rejects.toThrow(
-      new RequestValidationPluginError('No valid procedure found at path "not.found", this may happen when the contract router is not properly configured.'),
-    )
+    expect(interceptor).not.toHaveBeenCalled()

If your intercept() runs last‑in‑first‑out, keep push() and this test as-is; otherwise switch to unshift() per the plugin comment above and apply this assertion.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd705fc and 356c4b5.

📒 Files selected for processing (5)
  • apps/content/.vitepress/config.ts (1 hunks)
  • apps/content/docs/plugins/request-validation.md (1 hunks)
  • packages/contract/src/plugins/index.ts (1 hunks)
  • packages/contract/src/plugins/request-validation.test.ts (1 hunks)
  • packages/contract/src/plugins/request-validation.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/contract/src/plugins/request-validation.ts (4)
packages/client/src/adapters/standard/plugin.ts (1)
  • StandardLinkPlugin (4-7)
packages/contract/src/router.ts (1)
  • AnyContractRouter (17-17)
packages/client/src/adapters/standard/link.ts (1)
  • StandardLinkOptions (18-22)
packages/contract/src/procedure.ts (1)
  • isContractProcedure (51-66)
packages/contract/src/plugins/request-validation.test.ts (2)
packages/client/src/adapters/standard/link.ts (1)
  • StandardLink (24-109)
packages/contract/src/plugins/request-validation.ts (2)
  • RequestValidationPlugin (18-61)
  • RequestValidationPluginError (9-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test
  • GitHub Check: publish-commit
  • GitHub Check: lint
🔇 Additional comments (3)
apps/content/.vitepress/config.ts (1)

142-142: Sidebar entry LGTM

Link path and placement under Plugins look correct.

packages/contract/src/plugins/index.ts (1)

1-1: Export looks good

Public re‑export enables the documented import path.

apps/content/docs/plugins/request-validation.md (1)

34-36: Confirm OpenAPILink forwards StandardLink plugins at call time

OpenAPILink extends StandardOpenAPILink and creates a LinkFetchClient(options); StandardLinkOptions includes plugins?: StandardLinkPlugin[]. Verify OpenAPILinkOptions accepts a plugins array and that LinkFetchClient / StandardOpenAPILink forward options.plugins into the underlying StandardLink when making calls (see packages/openapi-client/src/adapters/fetch/openapi-link.ts and packages/client/src/adapters/standard/link.ts).

Comment thread packages/contract/src/plugins/request-validation.ts
@dinwwwh dinwwwh merged commit e4b0df6 into main Sep 15, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Request Validation Plugin

1 participant