Skip to content

refactor(orm): move validateInput logic into InputValidator#2480

Merged
ymc9 merged 2 commits intodevfrom
feat/validate-input-option
Mar 12, 2026
Merged

refactor(orm): move validateInput logic into InputValidator#2480
ymc9 merged 2 commits intodevfrom
feat/validate-input-option

Conversation

@ymc9
Copy link
Member

@ymc9 ymc9 commented Mar 12, 2026

Summary

  • Add enabled option to InputValidator constructor to control whether Zod-based validation runs
  • When validateInput: false, the validator skips parsing and returns input as-is instead of receiving undefined as the validator
  • Update ZodSchemaFactory to always include extra validations (controlled at the InputValidator level now)
  • Improve validateInput JSDoc to clarify it controls all query args validation, not just attribute-based validation

Test plan

  • Existing allows disabling validation test passes (renamed to disabling validation makes validation attributes ineffective)
  • New test: disabling validation skips structural validation for all CRUD operations — verifies malformed args bypass Zod validation when disabled
  • New test: $setInputValidation toggles validation — verifies enable/disable/re-enable flow
  • New test: disabling validation at client creation time — verifies validateInput: false works from initial client options
  • All existing validation tests pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Runtime control for input validation per client instance; validation can be enabled/disabled via client options and the $setInputValidation method.
  • Documentation

    • Clarified docs for input validation (query-argument validation) and added a caution about risks when disabling validation.
  • Tests

    • Added end-to-end tests covering validation toggling, disabling at creation, and CRUD behavior when validation is off.

…ed option

Instead of conditionally passing undefined for inputValidator when
validateInput is false, the InputValidator now accepts an `enabled`
option that controls whether Zod-based validation runs. When disabled,
validate methods return the input as-is without parsing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Contributor

claude bot commented Mar 12, 2026

Claude finished @ymc9's task —— View job


Pull Request Review

Todo List:

  • Read and analyze all changed files
  • Review validation architecture changes
  • Examine test coverage and quality
  • Check for potential bugs and issues
  • Assess performance and security considerations
  • Provide comprehensive feedback

Overall Assessment

This is a well-designed refactoring that improves the separation of concerns in the validation system. The changes move validation control logic into the InputValidator class where it belongs, making the system more coherent and easier to understand.

✅ Strengths

1. Clean Architecture Improvement

  • Moving validation control into InputValidator via the enabled option is the right approach
  • Eliminates the awkward pattern of passing undefined as the validator when disabled
  • Clear separation between validation control (InputValidator.enabled) and schema generation (ZodSchemaFactory)

2. Consistent API Design

  • The enabled option follows established patterns in the codebase
  • Consistent constructor signature across all InputValidator instantiations
  • Backwards compatibility maintained through the validateInput option

3. Comprehensive Test Coverage

The test additions are excellent:

  • disabling validation skips structural validation for all CRUD operations - tests the core change
  • $setInputValidation toggles validation - tests the dynamic enabling/disabling
  • disabling validation at client creation time - tests initial configuration
  • Covers both validation attributes and structural validation

4. Code Quality

  • Good use of default parameters (options?.enabled !== false)
  • Consistent error handling and early returns
  • Clear JSDoc improvements clarifying the scope of validateInput

🔍 Technical Review

InputValidator Changes (packages/orm/src/client/crud/validator/validator.ts:28-45)

The new enabled option is well implemented:

export type InputValidatorOptions = {
    enabled?: boolean;
};

constructor(
    private readonly client: ClientContract<Schema>,
    options?: InputValidatorOptions,
) {
    this.zodFactory = new ZodSchemaFactory(client);
    this.enabled = options?.enabled !== false; // ✅ Good default
}

Validation Logic (packages/orm/src/client/crud/validator/validator.ts:287-302)

The core validation logic is clean:

private validate<T>(/* ... */) {
    if (!this.enabled) {
        return args as T; // ✅ Correct bypass behavior
    }
    // ... existing validation logic
}

Client Integration (packages/orm/src/client/client-impl.ts:123-125)

Proper integration with client options:

this.inputValidator = 
    baseClient?.inputValidator ?? 
    new InputValidator(this as any, { enabled: this.$options.validateInput !== false });

ZodSchemaFactory Simplification (packages/orm/src/client/zod/factory.ts)

Good decision to always enable extra validations in the schema factory - control is now properly delegated to the validator level.

⚠️ Minor Observations

1. Validation Bypass Behavior

The bypass logic returns args as T when disabled. This is correct and safe since:

  • TypeScript ensures type safety at compile time
  • Runtime validation is intentionally being skipped
  • The alternative would be complex partial validation which could be more error-prone

2. Performance Impact

Positive: When validation is disabled, there's now a very early return that bypasses all Zod schema parsing, which should improve performance.

3. Security Considerations

Appropriate: The changes maintain the existing security model. When validation is disabled, it's an explicit opt-in by the developer who takes responsibility for input safety.

🧪 Test Quality Assessment

The new tests are comprehensive and well-structured:

  1. Structural Validation Test - Excellent approach testing that malformed arguments bypass Zod validation
  2. Toggle Test - Good coverage of enable/disable/re-enable flow
  3. Client Creation Test - Important edge case coverage
  4. Backward Compatibility - Existing tests ensure no regression

The test helper expectNonValidationError is particularly well-designed, checking that errors are not Zod-based when validation is disabled.

📋 Recommendations

1. Documentation

The JSDoc improvement is good but could be slightly enhanced:

/**
 * Whether to enable query args validation. Defaults to `true`.
 *
 * When disabled, bypasses all Zod-based validation including both structural 
 * validation (required fields, correct types) and attribute-based validation 
 * (@email, @url, custom @@validate rules).
 * 
 * USE WITH CAUTION: Disabling validation allows malformed input that may 
 * cause incorrect SQL generation or runtime errors.
 */

2. Type Safety

The current approach is appropriately typed. The as T cast in the bypass is safe since we're explicitly choosing to skip validation.

🎯 Final Verdict

This PR represents a high-quality refactoring that:

  • ✅ Improves architectural clarity
  • ✅ Maintains backward compatibility
  • ✅ Has excellent test coverage
  • ✅ Follows established patterns
  • ✅ Improves performance when validation is disabled

Recommendation: Approve - This is a solid improvement to the validation system with no identified issues.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

Adds runtime control to input validation by letting InputValidator accept an options object with an enabled flag; clients and clones now initialize validators with { enabled: (validateInput !== false) }. Documentation and tests updated to reflect and exercise toggling validation at runtime.

Changes

Cohort / File(s) Summary
Validator & Client
packages/orm/src/client/crud/validator/validator.ts, packages/orm/src/client/client-impl.ts
InputValidator now accepts an optional options object (e.g., { enabled?: boolean }) and short-circuits validation when disabled. All client construction/cloning paths pass { enabled: (validateInput !== false) } when instantiating validators.
Client API Docs
packages/orm/src/client/contract.ts, packages/orm/src/client/options.ts
JSDoc for $setInputValidation and ClientOptions.validateInput updated to describe toggling of query-argument validation and warn about risks when disabled.
Zod Schema Factory
packages/orm/src/client/zod/factory.ts
Replaced dynamic extraValidationsEnabled getter with a private readonly field set to true, making extra validations unconditionally enabled in this module.
Tests
tests/e2e/orm/validation/custom-validation.test.ts
Renamed test and added suites covering: skipping structural validation when disabled across CRUD ops, $setInputValidation toggling behavior (disable/enable), and disabling validation at client creation time.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I twitch my nose at validators' song,
Toggle a flag and they skip along.
When enabled they probe, when false they rest,
A rabbit's small cheer for options—best! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and concisely describes the primary change: refactoring the validateInput logic to be handled within InputValidator, which is the main architectural change in this changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/validate-input-option
📝 Coding Plan
  • Generate coding plan for human review comments

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
Contributor

@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.

🧹 Nitpick comments (1)
packages/orm/src/client/zod/factory.ts (1)

111-111: Consider simplifying now-dead validation branches.

With extraValidationsEnabled fixed to true, the ternary branches using it are now unreachable on the false side. Collapsing those branches will reduce complexity and make intent clearer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/zod/factory.ts` at line 111, The field
extraValidationsEnabled is hardcoded true so any ternary or conditional branches
that select the false path are dead; locate usages of extraValidationsEnabled
(e.g., in the Zod factory class methods and any expressions like
extraValidationsEnabled ? A : B) and collapse them to the true branch (remove
the ?: or if/else false branches), simplify logic accordingly, and delete any
now-unused helper code guarded only by the false branch to reduce complexity and
clarify intent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/orm/src/client/zod/factory.ts`:
- Line 111: The field extraValidationsEnabled is hardcoded true so any ternary
or conditional branches that select the false path are dead; locate usages of
extraValidationsEnabled (e.g., in the Zod factory class methods and any
expressions like extraValidationsEnabled ? A : B) and collapse them to the true
branch (remove the ?: or if/else false branches), simplify logic accordingly,
and delete any now-unused helper code guarded only by the false branch to reduce
complexity and clarify intent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 03c6cafe-8ae9-44e9-866c-e30e9dfec0c6

📥 Commits

Reviewing files that changed from the base of the PR and between d4fbb38 and 43fd989.

📒 Files selected for processing (6)
  • packages/orm/src/client/client-impl.ts
  • packages/orm/src/client/contract.ts
  • packages/orm/src/client/crud/validator/validator.ts
  • packages/orm/src/client/options.ts
  • packages/orm/src/client/zod/factory.ts
  • tests/e2e/orm/validation/custom-validation.test.ts

@ymc9
Copy link
Member Author

ymc9 commented Mar 12, 2026

@claude check why postgres tests are failing

@claude
Copy link
Contributor

claude bot commented Mar 12, 2026

Claude finished @ymc9's task —— View job


I'll analyze the postgres test failures for you.

Todo List

  • Check CI status to identify failing postgres tests
  • Examine workflow run details for specific failures
  • Download and analyze failure logs
  • Identify root cause related to validation changes
  • Provide actionable feedback and recommendations

Root Cause Analysis

The postgres test failure is in the new $setInputValidation toggles validation test due to a database ID collision. Here's what's happening:

The Problem

  1. Line 243: dbDisabled.item.create({ data: { id: 1, url: 'not-a-url' } }) creates an item with id: 1
  2. Line 250: dbReEnabled.item.create({ data: { url: 'https://example.com' } }) tries to create another item without specifying an ID
  3. PostgreSQL auto-increment starts at 1, so it tries to assign id: 1 again
  4. Unique constraint violation: "duplicate key value violates unique constraint "unique_Item_id"

Why it only fails on PostgreSQL

  • SQLite: More permissive with auto-increment behavior or different sequence handling
  • MySQL: Different auto-increment behavior than PostgreSQL
  • PostgreSQL: Strict about unique constraints and doesn't automatically skip manually inserted IDs

Fix Recommendation

The test needs to avoid ID collision. Here are two solutions:

Option 1: Use different explicit IDs

// Line 243: Use a high ID that won't conflict
await expect(dbDisabled.item.create({ data: { id: 1000, url: 'not-a-url' } })).toResolveTruthy();

// Line 250: Use a different high ID  
await expect(dbReEnabled.item.create({ data: { id: 1001, url: 'https://example.com' } })).toResolveTruthy();

Option 2: Don't specify IDs (recommended)

// Line 243: Let auto-increment handle it
await expect(dbDisabled.item.create({ data: { url: 'not-a-url' } })).toResolveTruthy();

// Line 250: Let auto-increment handle it
await expect(dbReEnabled.item.create({ data: { url: 'https://example.com' } })).toResolveTruthy();

Option 2 is preferred because it tests the validation behavior without introducing PostgreSQL-specific ID collision issues and better matches real-world usage patterns.

Impact Assessment

  • This is a test-only issue, not a bug in the validation refactoring logic
  • The validation feature itself is working correctly
  • Only affects PostgreSQL due to stricter unique constraint handling
  • Quick fix needed to unblock the CI pipeline

Would you like me to implement the fix?


@ymc9
Copy link
Member Author

ymc9 commented Mar 12, 2026

Claude finished @ymc9's task —— View job

I'll analyze the postgres test failures for you. 435229200-5ac382c7-e004-429b-8e35-7feb3e8f9c6f.gif?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzMzNTU1NTksIm5iZiI6MTc3MzM1NTI1OSwicGF0aCI6Ii8zMDU5MjQ5LzQzNTIyOTIwMC01YWMzODJjNy1lMDA0LTQyOWItOGUzNS03ZmViM2U4ZjljNmYuZ2lmP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDMxMiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjAzMTJUMjI0MDU5WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZWExMTQzZGI4MWZiOGY5MWM0M2MwYzdjNzE0N2JkNzkxNmM1ZDM5MWEyNDdmYTMxZDliZDc3NWZjYmFmMDYwMSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.5Tl978FlLAe7lLBxs_2e3SjbIvYML8G9aIHbl5SIEaE

Todo List

  • Check CI status to identify failing postgres tests
  • Examine workflow run details for specific failures
  • Download and analyze failure logs
  • Identify root cause related to validation changes
  • Provide actionable feedback and recommendations

Root Cause Analysis

The postgres test failure is in the new $setInputValidation toggles validation test due to a database ID collision. Here's what's happening:

The Problem

  1. Line 243: dbDisabled.item.create({ data: { id: 1, url: 'not-a-url' } }) creates an item with id: 1
  2. Line 250: dbReEnabled.item.create({ data: { url: 'https://example.com' } }) tries to create another item without specifying an ID
  3. PostgreSQL auto-increment starts at 1, so it tries to assign id: 1 again
  4. Unique constraint violation: "duplicate key value violates unique constraint "unique_Item_id"

Why it only fails on PostgreSQL

  • SQLite: More permissive with auto-increment behavior or different sequence handling
  • MySQL: Different auto-increment behavior than PostgreSQL
  • PostgreSQL: Strict about unique constraints and doesn't automatically skip manually inserted IDs

Fix Recommendation

The test needs to avoid ID collision. Here are two solutions:

Option 1: Use different explicit IDs

// Line 243: Use a high ID that won't conflict
await expect(dbDisabled.item.create({ data: { id: 1000, url: 'not-a-url' } })).toResolveTruthy();

// Line 250: Use a different high ID  
await expect(dbReEnabled.item.create({ data: { id: 1001, url: 'https://example.com' } })).toResolveTruthy();

Option 2: Don't specify IDs (recommended)

// Line 243: Let auto-increment handle it
await expect(dbDisabled.item.create({ data: { url: 'not-a-url' } })).toResolveTruthy();

// Line 250: Let auto-increment handle it
await expect(dbReEnabled.item.create({ data: { url: 'https://example.com' } })).toResolveTruthy();

Option 2 is preferred because it tests the validation behavior without introducing PostgreSQL-specific ID collision issues and better matches real-world usage patterns.

Impact Assessment

  • This is a test-only issue, not a bug in the validation refactoring logic
  • The validation feature itself is working correctly
  • Only affects PostgreSQL due to stricter unique constraint handling
  • Quick fix needed to unblock the CI pipeline

Would you like me to implement the fix?

Yes, fix with option 2 @claude

@claude
Copy link
Contributor

claude bot commented Mar 12, 2026

Claude finished @ymc9's task —— View job


Fixed PostgreSQL test failure

Todo List

  • Read the failing test file to locate problematic lines
  • Remove explicit ID specifications from lines 243 and 250
  • Commit and push the fix to resolve PostgreSQL constraint violation
  • Verify the fix addresses the root cause

Root Cause

The test failure occurred because PostgreSQL's auto-increment doesn't automatically skip manually inserted IDs, causing a unique constraint violation when trying to create a second item.

Fix Applied

  • Removed explicit id: 1 from line 243 in the $setInputValidation toggles validation test
  • Changed { id: 1, url: 'not-a-url' } to { url: 'not-a-url' }
  • Line 250 was already correct (no explicit ID)

Result

  • PostgreSQL will now use auto-increment for both item creations
  • No more unique constraint violation
  • Test validates the same validation behavior without database-specific conflicts
  • Pushed fix to branch feat/validate-input-option in commit 2178eb1

The PostgreSQL tests should now pass! 🎯


… constraint violation

Co-authored-by: Yiming Cao <ymc9@users.noreply.github.com>
Copy link
Contributor

@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.

🧹 Nitpick comments (1)
tests/e2e/orm/validation/custom-validation.test.ts (1)

149-153: Prefer avoiding explicit IDs in these inserts to prevent sequence-related test brittleness.

Given the recent PostgreSQL ID-collision issue, these explicit IDs can reintroduce sequence-coupling risks if test flow changes later. Let autoincrement assign IDs and reuse returned IDs for follow-up operations.

Suggested diff
-        await expect(
-            dbNoValidation.user.create({
-                data: {
-                    id: 1,
-                    email: 'xyz',
-                },
-            }),
-        ).toResolveTruthy();
+        const createdUser = await dbNoValidation.user.create({
+            data: {
+                email: 'xyz',
+            },
+        });
+        expect(createdUser).toBeTruthy();

         await expect(
             dbNoValidation.user.update({
-                where: { id: 1 },
+                where: { id: createdUser.id },
                 data: {
                     email: 'a@b.com',
                 },
             }),
         ).toResolveTruthy();

-        await expect(db.post.create({ data: { id: 1, title: 'ab' } })).toResolveTruthy();
+        await expect(db.post.create({ data: { title: 'ab' } })).toResolveTruthy();

Also applies to: 158-159, 265-265

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/orm/validation/custom-validation.test.ts` around lines 149 - 153,
The test inserts use explicit id values which can cause sequence-related
brittleness; update calls to dbNoValidation.user.create to omit the id field so
the DB autoincrements, capture the returned record (e.g., const user = await
dbNoValidation.user.create(...)) and reuse user.id for subsequent operations and
assertions; apply the same change to the other dbNoValidation.user.create
occurrences in this test to avoid ID collisions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/e2e/orm/validation/custom-validation.test.ts`:
- Around line 149-153: The test inserts use explicit id values which can cause
sequence-related brittleness; update calls to dbNoValidation.user.create to omit
the id field so the DB autoincrements, capture the returned record (e.g., const
user = await dbNoValidation.user.create(...)) and reuse user.id for subsequent
operations and assertions; apply the same change to the other
dbNoValidation.user.create occurrences in this test to avoid ID collisions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5c0f0795-19ce-48c4-acd3-3db529974760

📥 Commits

Reviewing files that changed from the base of the PR and between 43fd989 and 2178eb1.

📒 Files selected for processing (1)
  • tests/e2e/orm/validation/custom-validation.test.ts

@ymc9 ymc9 merged commit bf6c9a4 into dev Mar 12, 2026
8 of 9 checks passed
@ymc9 ymc9 deleted the feat/validate-input-option branch March 12, 2026 23:46
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.

1 participant