Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,7 @@ isBoolean(value);
| `@ArrayUnique(identifier?: (o) => any)` | Checks if all array's values are unique. Comparison for objects is reference-based. Optional function can be speciefied which return value will be used for the comparsion. |
| **Object validation decorators** |
| `@IsInstance(value: any)` | Checks if the property is an instance of the passed value. |
| `@IsMutuallyExclusiveWith(property: string)` | Checks if the property and the given related property are not both provided at the same time. |
| **Other decorators** | |
| `@Allow()` | Prevent stripping off the property when no other constraint is specified for it. |

Expand Down
39 changes: 39 additions & 0 deletions src/decorator/common/IsMutuallyExclusiveWith.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ValidationArguments } from '../../validation/ValidationArguments';
import { ValidationOptions } from '../ValidationOptions';
import { ValidateBy, buildMessage } from './ValidateBy';

export const IS_MUTUALLY_EXCLUSIVE_WITH = 'isMutuallyExclusiveWith';

/**
* Checks if the value and the value of the related property are not both present at the same time.
*/
export function isMutuallyExclusiveWith(
value: unknown,
relatedPropertyName: string,
args?: ValidationArguments
): boolean {
const relatedValue = (args?.object as any)?.[relatedPropertyName];
const isValuePresent = value !== undefined && value !== null;
const isRelatedPresent = relatedValue !== undefined && relatedValue !== null;
return !(isValuePresent && isRelatedPresent);
}

/**
* Checks if the property and the given related property are not both provided at the same time.
*/
export function IsMutuallyExclusiveWith(property: string, validationOptions?: ValidationOptions): PropertyDecorator {
return ValidateBy(
{
name: IS_MUTUALLY_EXCLUSIVE_WITH,
constraints: [property],
validator: {
validate: (value, args): boolean => isMutuallyExclusiveWith(value, args?.constraints[0], args),
defaultMessage: buildMessage(
eachPrefix => eachPrefix + `$property and ${property} cannot both be provided`,
validationOptions
),
},
},
validationOptions
);
}
1 change: 1 addition & 0 deletions src/decorator/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export * from './common/IsEmpty';
export * from './common/IsNotEmpty';
export * from './common/IsIn';
export * from './common/IsNotIn';
export * from './common/IsMutuallyExclusiveWith';

// -------------------------------------------------------------------------
// Number checkers
Expand Down
48 changes: 48 additions & 0 deletions test/functional/validation-functions-and-decorators.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
IsNegative,
Contains,
Equals,
IsMutuallyExclusiveWith,
MinDate,
MaxDate,
IsAlpha,
Expand Down Expand Up @@ -122,6 +123,7 @@ import {
maxLength,
isFirebasePushId,
equals,
isMutuallyExclusiveWith,
notEquals,
isEmpty,
isNotEmpty,
Expand Down Expand Up @@ -356,6 +358,52 @@ describe('Equals', () => {
});
});

describe('IsMutuallyExclusiveWith', () => {
class MyClass {
@IsMutuallyExclusiveWith('propertyB')
someProperty: string;

propertyB: string;
}

it('should not fail if only one property is provided', () => {
const model1 = new MyClass();
const model2 = new MyClass();
model2.propertyB = 'bar';

return Promise.all([checkValidValues(model1, ['foo']), checkValidValues(model2, [undefined])]);
});

it('should not fail if neither property is provided', () => {
const model = new MyClass();
return checkValidValues(model, [undefined]);
});

it('should fail if both properties are provided', () => {
const model = new MyClass();
model.propertyB = 'bar';

return checkInvalidValues(model, ['foo']);
});

it('should not fail if method in validator said that its valid', () => {
expect(isMutuallyExclusiveWith('foo', 'propertyB', { object: {} } as any)).toBeTruthy();
});

it('should fail if method in validator said that its invalid', () => {
expect(isMutuallyExclusiveWith('foo', 'propertyB', { object: { propertyB: 'bar' } } as any)).toBeFalsy();
});

it('should return error object with proper data', () => {
const model = new MyClass();
model.propertyB = 'bar';

const validationType = 'isMutuallyExclusiveWith';
const message = 'someProperty and propertyB cannot both be provided';
return checkReturnedError(model, ['foo'], validationType, message);
});
});

describe('NotEquals', () => {
const constraint = 'Alex';
const validValues = ['Alexxx'];
Expand Down