mirror of
https://github.com/nestjs/nest.git
synced 2026-02-22 15:31:40 +00:00
Compare commits
2 Commits
fix/instan
...
feat/parse
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
676461ff4e | ||
|
|
cd7079bcc0 |
@@ -1,9 +1,10 @@
|
||||
export * from './default-value.pipe';
|
||||
export * from './file';
|
||||
export * from './parse-array.pipe';
|
||||
export * from './parse-bool.pipe';
|
||||
export * from './parse-int.pipe';
|
||||
export * from './parse-float.pipe';
|
||||
export * from './parse-date.pipe';
|
||||
export * from './parse-enum.pipe';
|
||||
export * from './parse-float.pipe';
|
||||
export * from './parse-int.pipe';
|
||||
export * from './parse-uuid.pipe';
|
||||
export * from './validation.pipe';
|
||||
export * from './file';
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
PipeTransform,
|
||||
} from '../interfaces/features/pipe-transform.interface';
|
||||
import { HttpErrorByCode } from '../utils/http-error-by-code.util';
|
||||
import { isNil, isUndefined, isString } from '../utils/shared.utils';
|
||||
import { isNil, isString, isUndefined } from '../utils/shared.utils';
|
||||
import { ValidationPipe, ValidationPipeOptions } from './validation.pipe';
|
||||
|
||||
const VALIDATION_ERROR_MESSAGE = 'Validation failed (parsable array expected)';
|
||||
@@ -21,9 +21,26 @@ export interface ParseArrayOptions
|
||||
ValidationPipeOptions,
|
||||
'transform' | 'validateCustomDecorators' | 'exceptionFactory'
|
||||
> {
|
||||
/**
|
||||
* Type for items to be converted into
|
||||
*/
|
||||
items?: Type<unknown>;
|
||||
/**
|
||||
* Items separator to split string by
|
||||
* @default ','
|
||||
*/
|
||||
separator?: string;
|
||||
/**
|
||||
* If true, the pipe will return null or undefined if the value is not provided
|
||||
* @default false
|
||||
*/
|
||||
optional?: boolean;
|
||||
/**
|
||||
* A factory function that returns an exception object to be thrown
|
||||
* if validation fails.
|
||||
* @param error Error message or object
|
||||
* @returns The exception object
|
||||
*/
|
||||
exceptionFactory?: (error: any) => any;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,21 @@ import { isNil } from '../utils/shared.utils';
|
||||
* @publicApi
|
||||
*/
|
||||
export interface ParseBoolPipeOptions {
|
||||
/**
|
||||
* The HTTP status code to be used in the response when the validation fails.
|
||||
*/
|
||||
errorHttpStatusCode?: ErrorHttpStatusCode;
|
||||
/**
|
||||
* A factory function that returns an exception object to be thrown
|
||||
* if validation fails.
|
||||
* @param error Error message
|
||||
* @returns The exception object
|
||||
*/
|
||||
exceptionFactory?: (error: string) => any;
|
||||
/**
|
||||
* If true, the pipe will return null or undefined if the value is not provided
|
||||
* @default false
|
||||
*/
|
||||
optional?: boolean;
|
||||
}
|
||||
|
||||
|
||||
74
packages/common/pipes/parse-date.pipe.ts
Normal file
74
packages/common/pipes/parse-date.pipe.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Injectable } from '../decorators/core/injectable.decorator';
|
||||
import { HttpStatus } from '../enums/http-status.enum';
|
||||
import { PipeTransform } from '../interfaces/features/pipe-transform.interface';
|
||||
import {
|
||||
ErrorHttpStatusCode,
|
||||
HttpErrorByCode,
|
||||
} from '../utils/http-error-by-code.util';
|
||||
import { isNil } from '../utils/shared.utils';
|
||||
|
||||
export interface ParseDatePipeOptions {
|
||||
/**
|
||||
* If true, the pipe will return null or undefined if the value is not provided
|
||||
* @default false
|
||||
*/
|
||||
optional?: boolean;
|
||||
/**
|
||||
* Default value for the date
|
||||
*/
|
||||
default?: () => Date;
|
||||
/**
|
||||
* The HTTP status code to be used in the response when the validation fails.
|
||||
*/
|
||||
errorHttpStatusCode?: ErrorHttpStatusCode;
|
||||
/**
|
||||
* A factory function that returns an exception object to be thrown
|
||||
* if validation fails.
|
||||
* @param error Error message
|
||||
* @returns The exception object
|
||||
*/
|
||||
exceptionFactory?: (error: string) => any;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ParseDatePipe
|
||||
implements PipeTransform<string | number | undefined | null>
|
||||
{
|
||||
protected exceptionFactory: (error: string) => any;
|
||||
|
||||
constructor(private readonly options: ParseDatePipeOptions = {}) {
|
||||
const { exceptionFactory, errorHttpStatusCode = HttpStatus.BAD_REQUEST } =
|
||||
options;
|
||||
|
||||
this.exceptionFactory =
|
||||
exceptionFactory ||
|
||||
(error => new HttpErrorByCode[errorHttpStatusCode](error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that accesses and performs optional transformation on argument for
|
||||
* in-flight requests.
|
||||
*
|
||||
* @param value currently processed route argument
|
||||
* @param metadata contains metadata about the currently processed route argument
|
||||
*/
|
||||
transform(value: string | number | undefined | null): Date {
|
||||
if (this.options.optional && isNil(value)) {
|
||||
return this.options.default
|
||||
? this.options.default()
|
||||
: (value as undefined | null);
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
throw this.exceptionFactory('Validation failed (no Date provided)');
|
||||
}
|
||||
|
||||
const transformedValue = new Date(value);
|
||||
|
||||
if (isNaN(transformedValue.getTime())) {
|
||||
throw this.exceptionFactory('Validation failed (invalid date format)');
|
||||
}
|
||||
|
||||
return transformedValue;
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,21 @@ import { isNil } from '../utils/shared.utils';
|
||||
* @publicApi
|
||||
*/
|
||||
export interface ParseEnumPipeOptions {
|
||||
/**
|
||||
* If true, the pipe will return null or undefined if the value is not provided
|
||||
* @default false
|
||||
*/
|
||||
optional?: boolean;
|
||||
/**
|
||||
* The HTTP status code to be used in the response when the validation fails.
|
||||
*/
|
||||
errorHttpStatusCode?: ErrorHttpStatusCode;
|
||||
/**
|
||||
* A factory function that returns an exception object to be thrown
|
||||
* if validation fails.
|
||||
* @param error Error message
|
||||
* @returns The exception object
|
||||
*/
|
||||
exceptionFactory?: (error: string) => any;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,21 @@ import { isNil } from '../utils/shared.utils';
|
||||
* @publicApi
|
||||
*/
|
||||
export interface ParseFloatPipeOptions {
|
||||
/**
|
||||
* The HTTP status code to be used in the response when the validation fails.
|
||||
*/
|
||||
errorHttpStatusCode?: ErrorHttpStatusCode;
|
||||
/**
|
||||
* A factory function that returns an exception object to be thrown
|
||||
* if validation fails.
|
||||
* @param error Error message
|
||||
* @returns The exception object
|
||||
*/
|
||||
exceptionFactory?: (error: string) => any;
|
||||
/**
|
||||
* If true, the pipe will return null or undefined if the value is not provided
|
||||
* @default false
|
||||
*/
|
||||
optional?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,21 @@ import { isNil } from '../utils/shared.utils';
|
||||
* @publicApi
|
||||
*/
|
||||
export interface ParseIntPipeOptions {
|
||||
/**
|
||||
* The HTTP status code to be used in the response when the validation fails.
|
||||
*/
|
||||
errorHttpStatusCode?: ErrorHttpStatusCode;
|
||||
/**
|
||||
* A factory function that returns an exception object to be thrown
|
||||
* if validation fails.
|
||||
* @param error Error message
|
||||
* @returns The exception object
|
||||
*/
|
||||
exceptionFactory?: (error: string) => any;
|
||||
/**
|
||||
* If true, the pipe will return null or undefined if the value is not provided
|
||||
* @default false
|
||||
*/
|
||||
optional?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,25 @@ import { isNil, isString } from '../utils/shared.utils';
|
||||
* @publicApi
|
||||
*/
|
||||
export interface ParseUUIDPipeOptions {
|
||||
/**
|
||||
* UUID version to validate
|
||||
*/
|
||||
version?: '3' | '4' | '5' | '7';
|
||||
/**
|
||||
* The HTTP status code to be used in the response when the validation fails.
|
||||
*/
|
||||
errorHttpStatusCode?: ErrorHttpStatusCode;
|
||||
/**
|
||||
* A factory function that returns an exception object to be thrown
|
||||
* if validation fails.
|
||||
* @param error Error message
|
||||
* @returns The exception object
|
||||
*/
|
||||
exceptionFactory?: (errors: string) => any;
|
||||
/**
|
||||
* If true, the pipe will return null or undefined if the value is not provided
|
||||
* @default false
|
||||
*/
|
||||
optional?: boolean;
|
||||
}
|
||||
|
||||
|
||||
71
packages/common/test/pipes/parse-date.pipe.spec.ts
Normal file
71
packages/common/test/pipes/parse-date.pipe.spec.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { expect } from 'chai';
|
||||
import { BadRequestException } from '../../exceptions';
|
||||
import { ParseDatePipe } from '../../pipes/parse-date.pipe';
|
||||
|
||||
describe('ParseDatePipe', () => {
|
||||
let target: ParseDatePipe;
|
||||
|
||||
beforeEach(() => {
|
||||
target = new ParseDatePipe();
|
||||
});
|
||||
|
||||
describe('transform', () => {
|
||||
describe('when validation passes', () => {
|
||||
it('should return a valid date object', () => {
|
||||
const date = new Date().toISOString();
|
||||
|
||||
const transformedDate = target.transform(date);
|
||||
expect(transformedDate).to.be.instanceOf(Date);
|
||||
expect(transformedDate.toISOString()).to.equal(date);
|
||||
|
||||
const asNumber = transformedDate.getTime();
|
||||
const transformedNumber = target.transform(asNumber);
|
||||
expect(transformedNumber).to.be.instanceOf(Date);
|
||||
expect(transformedNumber.getTime()).to.equal(asNumber);
|
||||
});
|
||||
|
||||
it('should not throw an error if the value is undefined/null and optional is true', () => {
|
||||
const target = new ParseDatePipe({ optional: true });
|
||||
const value = target.transform(undefined);
|
||||
expect(value).to.equal(undefined);
|
||||
});
|
||||
});
|
||||
describe('when default value is provided', () => {
|
||||
it('should return the default value if the value is undefined/null', () => {
|
||||
const defaultValue = new Date();
|
||||
const target = new ParseDatePipe({
|
||||
optional: true,
|
||||
default: () => defaultValue,
|
||||
});
|
||||
const value = target.transform(undefined);
|
||||
expect(value).to.equal(defaultValue);
|
||||
});
|
||||
});
|
||||
describe('when validation fails', () => {
|
||||
it('should throw an error', () => {
|
||||
try {
|
||||
target.transform('123abc');
|
||||
expect.fail();
|
||||
} catch (error) {
|
||||
expect(error).to.be.instanceOf(BadRequestException);
|
||||
expect(error.message).to.equal(
|
||||
'Validation failed (invalid date format)',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('when empty value', () => {
|
||||
it('should throw an error', () => {
|
||||
try {
|
||||
target.transform('');
|
||||
expect.fail();
|
||||
} catch (error) {
|
||||
expect(error).to.be.instanceOf(BadRequestException);
|
||||
expect(error.message).to.equal(
|
||||
'Validation failed (no Date provided)',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -39,36 +39,25 @@ export class ListenerMetadataExplorer {
|
||||
const instancePrototype = Object.getPrototypeOf(instance);
|
||||
return this.metadataScanner
|
||||
.getAllMethodNames(instancePrototype)
|
||||
.map(method =>
|
||||
this.exploreMethodMetadata(instance, instancePrototype, method),
|
||||
)
|
||||
.map(method => this.exploreMethodMetadata(instancePrototype, method))
|
||||
.filter(metadata => metadata);
|
||||
}
|
||||
|
||||
public exploreMethodMetadata(
|
||||
instance: Controller,
|
||||
instancePrototype: object,
|
||||
methodKey: string,
|
||||
): EventOrMessageListenerDefinition {
|
||||
const prototypeCallback = instancePrototype[methodKey];
|
||||
const targetCallback = instancePrototype[methodKey];
|
||||
const handlerType = Reflect.getMetadata(
|
||||
PATTERN_HANDLER_METADATA,
|
||||
prototypeCallback,
|
||||
targetCallback,
|
||||
);
|
||||
if (isUndefined(handlerType)) {
|
||||
return;
|
||||
}
|
||||
const patterns = Reflect.getMetadata(PATTERN_METADATA, prototypeCallback);
|
||||
const transport = Reflect.getMetadata(
|
||||
TRANSPORT_METADATA,
|
||||
prototypeCallback,
|
||||
);
|
||||
const extras = Reflect.getMetadata(
|
||||
PATTERN_EXTRAS_METADATA,
|
||||
prototypeCallback,
|
||||
);
|
||||
|
||||
const targetCallback = instance[methodKey];
|
||||
const patterns = Reflect.getMetadata(PATTERN_METADATA, targetCallback);
|
||||
const transport = Reflect.getMetadata(TRANSPORT_METADATA, targetCallback);
|
||||
const extras = Reflect.getMetadata(PATTERN_EXTRAS_METADATA, targetCallback);
|
||||
return {
|
||||
methodKey,
|
||||
targetCallback,
|
||||
|
||||
@@ -71,7 +71,6 @@ describe('ListenerMetadataExplorer', () => {
|
||||
});
|
||||
it(`should return undefined when "handlerType" metadata is undefined`, () => {
|
||||
const metadata = instance.exploreMethodMetadata(
|
||||
test,
|
||||
Object.getPrototypeOf(test),
|
||||
'noPattern',
|
||||
);
|
||||
@@ -81,7 +80,6 @@ describe('ListenerMetadataExplorer', () => {
|
||||
describe('@MessagePattern', () => {
|
||||
it(`should return pattern properties when "handlerType" metadata is not undefined`, () => {
|
||||
const metadata = instance.exploreMethodMetadata(
|
||||
test,
|
||||
Object.getPrototypeOf(test),
|
||||
'testMessage',
|
||||
);
|
||||
@@ -98,7 +96,6 @@ describe('ListenerMetadataExplorer', () => {
|
||||
});
|
||||
it(`should return multiple patterns when more than one is declared`, () => {
|
||||
const metadata = instance.exploreMethodMetadata(
|
||||
test,
|
||||
Object.getPrototypeOf(test),
|
||||
'testMultipleMessage',
|
||||
);
|
||||
@@ -119,7 +116,6 @@ describe('ListenerMetadataExplorer', () => {
|
||||
describe('@EventPattern', () => {
|
||||
it(`should return pattern properties when "handlerType" metadata is not undefined`, () => {
|
||||
const metadata = instance.exploreMethodMetadata(
|
||||
test,
|
||||
Object.getPrototypeOf(test),
|
||||
'testEvent',
|
||||
);
|
||||
@@ -136,7 +132,6 @@ describe('ListenerMetadataExplorer', () => {
|
||||
});
|
||||
it(`should return multiple patterns when more than one is declared`, () => {
|
||||
const metadata = instance.exploreMethodMetadata(
|
||||
test,
|
||||
Object.getPrototypeOf(test),
|
||||
'testMultipleEvent',
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user