Skip to content

fix: allow combining global and local validation rules (#5025)#5146

Open
logaretm wants to merge 1 commit into
mainfrom
fix/5025-combine-global-local-rules
Open

fix: allow combining global and local validation rules (#5025)#5146
logaretm wants to merge 1 commit into
mainfrom
fix/5025-combine-global-local-rules

Conversation

@logaretm

@logaretm logaretm commented Mar 4, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #5025

  • Object syntax: { required: true, myLocalRule: myValidatorFn } now works by separating function-valued entries (inline validators) from string-keyed global rule references, running both
  • Array syntax: ['required', myValidatorFn] now works by resolving string elements as global rules instead of calling them directly as functions
  • Added tests for both object and array syntax combining global and local rules

Changes

packages/vee-validate/src/validate.ts

  • In the array branch of _validate, string entries are now resolved via normalizeRules and _test (global rule lookup) instead of being called as functions
  • Added a new object-handling branch that separates function-valued entries from non-function entries, running global rules via _test and inline functions directly

packages/vee-validate/src/useField.ts

  • When the rules object contains function values (inline validators mixed with global rules), it is passed through as-is to _validate instead of going through normalizeRules which would corrupt the function references

Test plan

  • Added test: global and local rules combined in object syntax - validates both rules run, both can fail independently
  • Added test: global and local rules combined in array syntax - validates string rules are resolved globally and function rules run inline
  • All 388 existing tests continue to pass (63 test files)

🤖 Generated with Claude Code

When both global rules (via defineRule) and local/inline validator
functions are used on the same field, they can now be combined using
either the object syntax or the array syntax.

Object syntax: { required: true, myLocalRule: myValidatorFn }
Array syntax: ['required', myValidatorFn]

Previously, the object syntax would throw "No such validator" because
function values were treated as rule parameters rather than inline
validators. The array syntax would throw "rule is not a function"
because string entries were called directly instead of being resolved
as global rules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 4, 2026 07:04
@changeset-bot

changeset-bot Bot commented Mar 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0857677

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@netlify

netlify Bot commented Mar 4, 2026

Copy link
Copy Markdown

Deploy Preview for vee-validate-docs canceled.

Name Link
🔨 Latest commit 0857677
🔍 Latest deploy log https://app.netlify.com/projects/vee-validate-docs/deploys/69a7d979d5ad500008cf0cf0

@netlify

netlify Bot commented Mar 4, 2026

Copy link
Copy Markdown

Deploy Preview for vee-validate-v5 ready!

Name Link
🔨 Latest commit 0857677
🔍 Latest deploy log https://app.netlify.com/projects/vee-validate-v5/deploys/69a7d9798eab770008574bff
😎 Deploy Preview https://deploy-preview-5146--vee-validate-v5.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

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.

Pull request overview

This PR fixes #5025 by enabling mixed usage of global rule references (string/object rule names) and inline/local validator functions in the same rules expression, across both object and array syntaxes.

Changes:

  • Updated _validate to resolve string entries inside rule arrays as global rules, and to support object rules that mix global rules with inline function validators.
  • Updated useField rule normalization to avoid corrupting mixed object rules that contain function values.
  • Added regression tests covering mixed global+local rules in both object and array syntaxes, plus a changeset for a patch release.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
packages/vee-validate/src/validate.ts Adds support for resolving string rules in arrays and splitting object rule entries into global vs inline validators.
packages/vee-validate/src/useField.ts Adjusts computed validator normalization to pass through rule objects containing function values.
packages/vee-validate/tests/validate.spec.ts Adds tests for combining global and local rules in both object and array syntaxes.
.changeset/fix-5025-global-local-rules.md Patch changeset entry documenting the fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +154 to +157
if (isCallable(rules[key])) {
inlineFns.push(rules[key] as GenericValidateFunction<TInput>);
} else {
globalRulesObj[key] = rules[key];

Copilot AI Mar 4, 2026

Copy link

Choose a reason for hiding this comment

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

The new object-rules branch treats any function-valued entry as an inline validator. This changes behavior for cases where the key refers to a defined global rule but the rule’s params are intentionally a function (e.g. passing a locator/function param). In that case the global rule will no longer run. Consider disambiguating by checking whether resolveRule(key) exists (or whether the function is a locator) before classifying it as an inline validator; only treat it as inline when there is no global rule with that name.

Suggested change
if (isCallable(rules[key])) {
inlineFns.push(rules[key] as GenericValidateFunction<TInput>);
} else {
globalRulesObj[key] = rules[key];
const value = rules[key];
const globalRule = resolveRule(key);
// Treat as inline validator only when there is no global rule with this name
if (isCallable(value) && !globalRule) {
inlineFns.push(value as GenericValidateFunction<TInput>);
} else {
globalRulesObj[key] = value;

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +108
for (let j = 0; j < ruleNames.length; j++) {
const normalizedContext = {
...field,
rules: stringRules,
};

Copilot AI Mar 4, 2026

Copy link

Choose a reason for hiding this comment

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

In the array validation pipeline, normalizedContext is recreated inside the inner ruleNames loop even though it doesn’t change. This adds avoidable allocations/spreads for every rule and every validation run. Move normalizedContext creation outside the inner loop (once per string entry) and reuse it when calling _test.

Suggested change
for (let j = 0; j < ruleNames.length; j++) {
const normalizedContext = {
...field,
rules: stringRules,
};
const normalizedContext = {
...field,
rules: stringRules,
};
for (let j = 0; j < ruleNames.length; j++) {

Copilot uses AI. Check for mistakes.
Comment on lines +122 to +129
// If the rules object contains function values (inline validators mixed with global rules),
// pass it through as-is so that _validate can handle both types (#5025)
if (rulesValue && typeof rulesValue === 'object') {
const hasInlineFns = Object.values(rulesValue).some(v => isCallable(v));
if (hasInlineFns) {
return rulesValue;
}
}

Copilot AI Mar 4, 2026

Copy link

Choose a reason for hiding this comment

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

This pass-through logic flags any function-valued rule entry as an inline validator. That can break object rule usage where a function is intended to be a parameter for a globally defined rule (or a locator), because it will bypass normalizeRules and later be treated as an inline validator. Consider only treating function values as inline validators when the corresponding key does not resolve to a global rule name (and/or when the function isn’t a locator).

Copilot uses AI. Check for mistakes.
Comment on lines +71 to +74
// Local rule fails (bails: false to see both errors)
result = await validate('ab', { required: true, myLocalRule } as any, { bails: false });
expect(result.valid).toBe(false);
expect(result.errors).toContain('Must be at least 3 characters');

Copilot AI Mar 4, 2026

Copy link

Choose a reason for hiding this comment

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

The comment says “bails: false to see both errors”, but this test case only exercises a value where one rule fails. Add an assertion/case where both the global and local rules fail with bails: false (e.g. empty string) and verify both error messages are returned, to ensure the combined-rules behavior works in the non-bailing path.

Copilot uses AI. Check for mistakes.
// Local rule fails
result = await validate('ab', ['required', myLocalRule] as any, { bails: false });
expect(result.valid).toBe(false);
expect(result.errors).toContain('Must be at least 3 characters');

Copilot AI Mar 4, 2026

Copy link

Choose a reason for hiding this comment

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

Similar to the object-syntax test: the bails: false case here only covers a value where the local rule fails. Add a case where both the global string rule and the local function rule fail with bails: false (e.g. empty string) and assert that both errors are collected.

Suggested change
expect(result.errors).toContain('Must be at least 3 characters');
expect(result.errors).toContain('Must be at least 3 characters');
// Both global and local rules fail with bails: false, both errors should be collected
result = await validate('', ['required', myLocalRule] as any, { bails: false });
expect(result.valid).toBe(false);
expect(result.errors).toContain('This field is required');
expect(result.errors).toContain('Must be at least 3 characters');

Copilot uses AI. Check for mistakes.
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.

Global and local rules cannot be combined

2 participants