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
38 changes: 34 additions & 4 deletions src/decorator/string/Length.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,32 @@ export function length(value: unknown, min: number, max?: number): boolean {
return typeof value === 'string' && isLengthValidator(value, { min, max });
}

/**
* Character length used to choose the default Length message.
* With `{ each: true }`, `args.value` is the whole collection, so collection.size would pick the wrong bound.
*/
function characterLengthForMessage(value: unknown, min: number, max?: number): number | undefined {
if (typeof value === 'string') {
return value.length;
}

let items: unknown[] | undefined;
if (Array.isArray(value)) {
items = value;
} else if (value instanceof Set) {
items = Array.from(value);
} else if (value instanceof Map) {
items = Array.from(value.values());
}

if (items) {
const failing = items.find(item => typeof item === 'string' && !length(item, min, max));
return typeof failing === 'string' ? failing.length : undefined;
}

return value != null ? (value as { length?: number }).length : undefined;
}

/**
* Checks if the string's length falls in a range. Note: this function takes into account surrogate pairs.
* If given value is not a string, then it returns false.
Expand All @@ -24,11 +50,15 @@ export function Length(min: number, max?: number, validationOptions?: Validation
validator: {
validate: (value, args): boolean => length(value, args?.constraints[0], args?.constraints[1]),
defaultMessage: buildMessage((eachPrefix, args) => {
const isMinLength = args?.constraints[0] !== null && args?.constraints[0] !== undefined;
const isMaxLength = args?.constraints[1] !== null && args?.constraints[1] !== undefined;
if (isMinLength && (!args.value || args.value.length < args?.constraints[0])) {
const min = args?.constraints[0];
const max = args?.constraints[1];
const isMinLength = min !== null && min !== undefined;
const isMaxLength = max !== null && max !== undefined;
const charLength = characterLengthForMessage(args?.value, min, max);

if (isMinLength && (charLength === undefined || charLength < min)) {
return eachPrefix + '$property must be longer than or equal to $constraint1 characters';
} else if (isMaxLength && args.value.length > args?.constraints[1]) {
} else if (isMaxLength && charLength !== undefined && charLength > max) {
return eachPrefix + '$property must be shorter than or equal to $constraint2 characters';
}
return (
Expand Down
27 changes: 27 additions & 0 deletions test/functional/validation-functions-and-decorators.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4267,6 +4267,33 @@ describe('Length', () => {
const message = 'someProperty must be shorter than or equal to ' + constraintToString(constraint2) + ' characters';
return checkReturnedError(new MyClass(), ['aaaa', 'azzazza'], validationType, message);
});

describe('with each: true', () => {
class TagsClass {
@Length(constraint1, constraint2, { each: true })
someProperty: string[];
}

it('should not fail for valid items', () => {
return checkValidValues(new TagsClass(), [['ab', 'abc'], ['de']]);
});

it('should report max-length when an item is too long', () => {
const validationType = 'isLength';
const message =
'each value in someProperty must be shorter than or equal to ' +
constraintToString(constraint2) +
' characters';
return checkReturnedError(new TagsClass(), [['abcd'], ['aaaa']], validationType, message);
});

it('should report min-length when items are too short', () => {
const validationType = 'isLength';
const message =
'each value in someProperty must be longer than or equal to ' + constraintToString(constraint1) + ' characters';
return checkReturnedError(new TagsClass(), [['a', 'a', 'a', 'a']], validationType, message);
});
});
});

describe('MinLength', () => {
Expand Down