-
Notifications
You must be signed in to change notification settings - Fork 830
fix: overwrite validation decorators from parent class when defined in child #748
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
Please don't merge this yet, I've added some additional tests and found some issues. I Will comment again once resolved. |
This PR is now ready for merge Please note: Currently in v0.12.2, validator-decorated properties of subclasses remove validators registered against those properties from parent classes (see 633). It appears this has been a problem for a long time. Validator equality is done by comparing a validators The bug became apparent after v0.11.1 (11a7b8b#diff-6a5d045641599b923bd58ec30032f09dL1) wherein most validators had a unique This PR changes equality to be based on |
Any update on this? I got hit by it again today. It's quite confusing at the moment as some decorators are inherited (e.g. @IsOptional) and others aren't. |
Hey folks, any expectations on merging this change? It seems to me the semantic commit + PR is blocking it. Thanks! |
Please merge this fix, more than one is forced to use version 0.11.1. Thanks! |
Hi! I pinned the tab, I will take a look next week. |
Any update on the merge? |
Looking forward to this to be merged as well. In the meantime, I worked around the problem using |
@NickKelly1 |
4056f3e
2f7b814
to
4056f3e
Compare
@NoNameProvided , @NickKelly1 , did you decide to do not merge it? |
👋🏻 Hi there, just wondering if this fix will land or if y'all have decided not to support this? |
This should really be merged... this is obviously a gap/bug, which anyone requiring this functionality would expect to be working? I mean this is a recursiveness problem, which should not be present in a production grade package. |
Any updates on this PR? Would it merge soon? |
Does anyone know a workaround that can be done so it would inherit the validations from the parent till this PR merges? |
I worked around the buggy decorator inheritance by fixing the You can then do: export class SuperUserDto extends UserDto {
@InheritValidation(UserDto, "name", { omitOptional: true })
@IsNotEmpty()
override name!: NonNullable<UserDto["name"]>;
} import { IS_OPTIONAL } from "class-validator";
import { cloneDeep, defaults } from "lodash";
import type { ValidationMetadata } from "class-validator/types/metadata/ValidationMetadata";
// https://github.com/typestack/class-validator/blob/develop/src/decorator/common/IsOptional.ts
// https://github.com/typestack/class-validator/issues/1288
// https://github.com/typestack/class-validator/pull/748
// https://github.com/typestack/class-validator/pull/161
// https://github.com/VinceOPS/class-validator/blob/7d5d199ccba7408893687dacb0d92169e2acfaae/src/decorator/inherit-validation/inherit-validation.ts
/**
* Allow copying validation metadatas set by `class-validator` from
* a given Class property to another. Copied `ValidationMetadata`s
* will have their `target` and `propertyName` changed according to
* the decorated class and property.
*
* @param fromClass Class to inherit validation metadata from.
* @param fromProperty Name of the target property (default to decorated property).
*
* @return {PropertyDecorator} Responsible for copying and registering `ValidationMetadata`s.
*
* @example
* class SubDto {
* @InheritValidation(Dto)
* readonly id: number;
*
* @InheritValidation(Dto, 'name')
* readonly firstName: string;
*
* @InheritValidation(Dto, 'name')
* readonly lastName: string;
* }
*/
export function InheritValidation<T>(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fromClass: new (...args: any[]) => T,
fromProperty?: keyof T,
options?: { omitOptional?: boolean }
): PropertyDecorator {
const opts = defaults(options, { omitOptional: false });
const validationMetadatas: ValidationMetadata[] =
globalThis.classValidatorMetadataStorage.validationMetadatas.get(fromClass);
/**
* Change the `target` and `propertyName` of each `ValidationMetadata`
* and add it to `MetadataStorage`. Thus, `class-validator` uses it
* during validation.
*
* @param toClass Class owning the decorated property.
* @param toProperty Name of the decorated property.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (toClass: object, toProperty: any) => {
const toPropertyName = toProperty as string;
const sourceProperty = fromProperty || toProperty;
const metadatasCopy = cloneDeep(
validationMetadatas.filter((vm) => vm.target === fromClass && vm.propertyName === sourceProperty)
);
const targetClass = toClass.constructor;
const targetValidationMetadatas = globalThis.classValidatorMetadataStorage.validationMetadatas.get(targetClass);
metadatasCopy.forEach((vm) => {
if (opts.omitOptional && vm.name === IS_OPTIONAL) {
return;
}
vm.target = targetClass;
vm.propertyName = toPropertyName;
if (targetValidationMetadatas) {
targetValidationMetadatas.push(vm);
} else {
globalThis.classValidatorMetadataStorage.validationMetadatas.set(targetClass, [vm]);
}
});
};
} Package patch: diff --git a/cjs/decorator/common/IsOptional.js b/cjs/decorator/common/IsOptional.js
index bb95c0cc51de1e9df550b002d798f462f823494c..e8086acc5b6a106d4b99d91015153d3480ae96aa 100644
--- a/cjs/decorator/common/IsOptional.js
+++ b/cjs/decorator/common/IsOptional.js
@@ -4,6 +4,8 @@ exports.IsOptional = void 0;
const ValidationTypes_1 = require("../../validation/ValidationTypes");
const ValidationMetadata_1 = require("../../metadata/ValidationMetadata");
const MetadataStorage_1 = require("../../metadata/MetadataStorage");
+const IS_OPTIONAL = 'isOptional';
+exports.IS_OPTIONAL = IS_OPTIONAL;
/**
* Checks if value is missing and if so, ignores all validators.
*/
@@ -11,6 +13,7 @@ function IsOptional(validationOptions) {
return function (object, propertyName) {
const args = {
type: ValidationTypes_1.ValidationTypes.CONDITIONAL_VALIDATION,
+ name: IS_OPTIONAL,
target: object.constructor,
propertyName: propertyName,
constraints: [
diff --git a/esm2015/decorator/common/IsOptional.js b/esm2015/decorator/common/IsOptional.js
index 4de1e9d259bc5159bc0e25806561784e9cc1b410..1cbb10e3901915fcbb0db4cb69ef9c38047cf2af 100644
--- a/esm2015/decorator/common/IsOptional.js
+++ b/esm2015/decorator/common/IsOptional.js
@@ -1,6 +1,7 @@
import { ValidationTypes } from '../../validation/ValidationTypes';
import { ValidationMetadata } from '../../metadata/ValidationMetadata';
import { getMetadataStorage } from '../../metadata/MetadataStorage';
+export const IS_OPTIONAL = 'isOptional';
/**
* Checks if value is missing and if so, ignores all validators.
*/
@@ -8,6 +9,7 @@ export function IsOptional(validationOptions) {
return function (object, propertyName) {
const args = {
type: ValidationTypes.CONDITIONAL_VALIDATION,
+ name: IS_OPTIONAL,
target: object.constructor,
propertyName: propertyName,
constraints: [
diff --git a/esm5/decorator/common/IsOptional.js b/esm5/decorator/common/IsOptional.js
index c10abe4f7d04d10304cccaedeace30b75ab2bdde..2003c84dce720d977013d3f42a6c678a4094f96f 100644
--- a/esm5/decorator/common/IsOptional.js
+++ b/esm5/decorator/common/IsOptional.js
@@ -1,6 +1,7 @@
import { ValidationTypes } from '../../validation/ValidationTypes';
import { ValidationMetadata } from '../../metadata/ValidationMetadata';
import { getMetadataStorage } from '../../metadata/MetadataStorage';
+export const IS_OPTIONAL = 'isOptional';
/**
* Checks if value is missing and if so, ignores all validators.
*/
@@ -8,6 +9,7 @@ export function IsOptional(validationOptions) {
return function (object, propertyName) {
var args = {
type: ValidationTypes.CONDITIONAL_VALIDATION,
+ name: IS_OPTIONAL,
target: object.constructor,
propertyName: propertyName,
constraints: [
diff --git a/types/decorator/common/IsOptional.d.ts b/types/decorator/common/IsOptional.d.ts
index 47d58aef82d25ad2180ce36b66bb0a0280bcd3cb..c1975b473baec74c56d91e6e64efd98bf695a188 100644
--- a/types/decorator/common/IsOptional.d.ts
+++ b/types/decorator/common/IsOptional.d.ts
@@ -1,4 +1,5 @@
import { ValidationOptions } from '../ValidationOptions';
+export declare const IS_OPTIONAL = "isOptional";
/**
* Checks if value is missing and if so, ignores all validators.
*/ |
Are there any plans to merge this PR? The problem needs to be solved |
Description
Fixes a bug that caused decorated properties which are also decorated in inherited classes, to skip their inherited validation.
Checklist
Update index.md
)develop
)npm run prettier:check
passesnpm run lint:check
passesFixes
fixes #633 , fixes #622