fix: overwrite validation decorators from parent class when defined in child #748
fix: overwrite validation decorators from parent class when defined in child #748NickKelly1 wants to merge 5 commits intotypestack:developfrom
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 |
|
Hi everyone! I rebased and updated the PR so it can be merged more easily : #2641 |
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:checkpassesnpm run lint:checkpassesFixes
fixes #633 , fixes #622