Skip to content

Instantly share code, notes, and snippets.

@tbhaxor
Created March 22, 2024 19:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tbhaxor/0d509fbe07fcf10e61df8a92e3f28150 to your computer and use it in GitHub Desktop.
Save tbhaxor/0d509fbe07fcf10e61df8a92e3f28150 to your computer and use it in GitHub Desktop.
Nestjs length validation pipe
import { BadRequestException, InternalServerErrorException } from '@nestjs/common';
import { ValidateLengthPipe } from './validate-length.pipe';
import { faker } from '@faker-js/faker';
describe('ValidateLengthPipe', () => {
it('should throw InternalServerErrorException on empty object', () => {
expect(() => new ValidateLengthPipe({})).toThrow(InternalServerErrorException);
});
it('should throw InternalServerErrorException when min < 0', () => {
const number = faker.number.int({ min: -100, max: -1 });
expect(() => new ValidateLengthPipe({ min: number })).toThrow(InternalServerErrorException);
});
it('should throw InternalServerErrorException when max < min', () => {
const minNumber = faker.number.int({ min: 0, max: 10 });
const maxNumber = faker.number.int({ min: -10, max: -1 });
expect(() => new ValidateLengthPipe({ min: minNumber, max: maxNumber })).toThrow(InternalServerErrorException);
});
it('should throw InternalServerErrorException when either value is not integer', () => {
expect(() => new ValidateLengthPipe({ min: faker.number.float() })).toThrow(InternalServerErrorException);
expect(() => new ValidateLengthPipe({ max: faker.number.float() })).toThrow(InternalServerErrorException);
});
it('should be defined when either of field is defined', () => {
expect(new ValidateLengthPipe({ min: 0 })).toBeDefined();
expect(new ValidateLengthPipe({ max: 0 })).toBeDefined();
});
it('should be defined when both length constraints are valid', () => {
const minNumber = faker.number.int({ min: 0 });
const maxNumber = faker.number.int({ min: minNumber });
expect(new ValidateLengthPipe({ min: minNumber, max: maxNumber })).toBeDefined();
});
it('should return isExact true if min == max', () => {
const minMax = faker.number.int();
expect(new ValidateLengthPipe({ min: minMax, max: faker.number.int({ min: minMax }) }).isExact).toBe(false);
expect(new ValidateLengthPipe({ min: minMax, max: minMax }).isExact).toBe(true);
});
it('should throw BadRequestException if length property on object is not defined', () => {
const validator = new ValidateLengthPipe({ min: 0 });
expect(() => validator.transform(null, { type: 'body' })).toThrow(BadRequestException);
expect(() => validator.transform(undefined, { type: 'body' })).toThrow(BadRequestException);
expect(() => validator.transform({}, { type: 'body' })).toThrow(BadRequestException);
});
it('should throw BadRequestException when length is less than min', () => {
const minNumber = faker.number.int({ max: 50 });
const str = faker.string.sample(minNumber - 1);
const validator = new ValidateLengthPipe({ min: minNumber });
expect(() => validator.transform(str, { type: 'body', data: 'body' })).toThrow(BadRequestException);
});
it('should throw BadRequestException when length is greater than max', () => {
const maxNumber = faker.number.int({ max: 50 });
const str = faker.string.sample(maxNumber + 1);
const validator = new ValidateLengthPipe({ max: maxNumber });
expect(() => validator.transform(str, { type: 'body', data: 'body' })).toThrow(BadRequestException);
});
it('should return unchanged value if length constraints matches', () => {
const minNumber = faker.number.int({ max: 50 });
const maxNumber = faker.number.int({ min: minNumber, max: minNumber * 2 });
const str = faker.string.sample({ min: minNumber, max: maxNumber });
const validator = new ValidateLengthPipe({ min: minNumber, max: maxNumber });
expect(validator.transform(str, { type: 'body' })).toBe(str);
});
});
import { ArgumentMetadata, BadRequestException, Injectable, InternalServerErrorException, PipeTransform } from '@nestjs/common';
export type ValidateLengthPipeOptions = {
min?: number;
max?: number;
};
@Injectable()
export class ValidateLengthPipe implements PipeTransform {
private readonly options: ValidateLengthPipeOptions;
constructor(options: ValidateLengthPipeOptions) {
this.options = options;
if (typeof options.max != 'number' && typeof options.min != 'number') {
throw new InternalServerErrorException("Both `min` and `max` can't be left optional at same time.");
}
if (typeof options.max === 'number' && !Number.isInteger(options.max)) {
throw new InternalServerErrorException('Maximum length must be integeral value.');
}
if (typeof options.min === 'number' && !Number.isInteger(options.min)) {
throw new InternalServerErrorException('Minimum length must be integeral value.');
}
if (options.min < 0) {
throw new InternalServerErrorException("Minimum value can't be less than 0.");
}
if (options.max < options.min) {
throw new InternalServerErrorException("Minimum value can't be greater than maximum value.");
}
}
transform(value: any, metadata: ArgumentMetadata) {
if (typeof value?.length === 'undefined') {
throw new BadRequestException(`${metadata.data || metadata.type} doesn't have length property.`);
}
if (this.isExact && value.length != this.options.min) {
throw new BadRequestException(`Length of ${metadata.data || metadata.type} should be exactly ${this.options.min}.`);
}
if (this.options.min > value.length) {
throw new BadRequestException(`Length of ${metadata.data || metadata.type} should greater than ${this.options.min}.`);
}
if (this.options.max < value.length) {
throw new BadRequestException(`Length of ${metadata.data || metadata.type} should less than ${this.options.max}.`);
}
return value;
}
get isExact() {
return this.options.min == this.options.max;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment