Compare commits

...

6 Commits

Author SHA1 Message Date
Kamil Myśliwiec
676461ff4e test: add unit tests, minor updates 2024-11-08 15:49:24 +01:00
Kamil Myśliwiec
cd7079bcc0 feat(common): add parse date pipe, add tsdoc to other pipes 2024-11-08 15:28:13 +01:00
Kamil Mysliwiec
bc4667c15a Update Readme.md 2024-11-05 14:19:52 +01:00
Kamil Myśliwiec
a544552f72 chore(@nestjs) publish v10.4.7 release 2024-11-05 09:57:20 +01:00
Kamil Mysliwiec
954616da7d Merge pull request #14108 from nestjs/chore/undeprecate-listen-fastify
chore(fastify): undeprecate listen method
2024-11-05 09:49:24 +01:00
Kamil Myśliwiec
c6b95f54a7 chore(fastify): undeprecate listen method 2024-11-05 09:37:22 +01:00
21 changed files with 257 additions and 40 deletions

View File

@@ -94,7 +94,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<td><a href="https://www.mercedes-benz.com/" target="_blank"><img src="https://nestjs.com/img/logos/mercedes-logo.png" width="100" valign="middle" /></a></td>
<td><a href="https://www.dinii.jp/" target="_blank"><img src="https://nestjs.com/img/logos/dinii-logo.png" width="65" valign="middle" /></a></td>
<td><a href="https://bloodycase.com/?promocode=NEST" target="_blank"><img src="https://nestjs.com/img/logos/bloodycase-logo.png" width="65" valign="middle" /></a></td>
<td><a href="https://handsontable.com/docs/react-data-grid/?utm_source=NestJS_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024" target="_blank"><img src="https://nestjs.com/img/logos/handsontable-logo.svg" width="150" valign="middle" /></a></td>
<td><a href="https://handsontable.com/docs/react-data-grid/?utm_source=NestJS_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024" target="_blank"><img src="https://nestjs.com/img/logos/handsontable-dark-logo.svg#2" width="150" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.itflashcards.com/" target="_blank"><img src="https://nestjs.com/img/logos/it_flashcards-logo.png" width="170" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://arcjet.com/?ref=nestjs" target="_blank"><img src="https://nestjs.com/img/logos/arcjet-logo.svg" width="170" valign="middle" /></a></td>
</tr>

View File

@@ -3,5 +3,5 @@
"packages": [
"packages/*"
],
"version": "10.4.6"
"version": "10.4.7"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/common",
"version": "10.4.6",
"version": "10.4.7",
"description": "Nest - modern, fast, powerful node.js web framework (@common)",
"author": "Kamil Mysliwiec",
"homepage": "https://nestjs.com",

View File

@@ -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';

View 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;
}

View File

@@ -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;
}

View 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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View 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)',
);
}
});
});
});
});

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/core",
"version": "10.4.6",
"version": "10.4.7",
"description": "Nest - modern, fast, powerful node.js web framework (@core)",
"author": "Kamil Mysliwiec",
"license": "MIT",
@@ -36,7 +36,7 @@
"uid": "2.0.2"
},
"devDependencies": {
"@nestjs/common": "10.4.6"
"@nestjs/common": "10.4.7"
},
"peerDependencies": {
"@nestjs/common": "^10.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/microservices",
"version": "10.4.6",
"version": "10.4.7",
"description": "Nest - modern, fast, powerful node.js web framework (@microservices)",
"author": "Kamil Mysliwiec",
"license": "MIT",
@@ -22,8 +22,8 @@
"tslib": "2.7.0"
},
"devDependencies": {
"@nestjs/common": "10.4.6",
"@nestjs/core": "10.4.6"
"@nestjs/common": "10.4.7",
"@nestjs/core": "10.4.7"
},
"peerDependencies": {
"@grpc/grpc-js": "*",

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/platform-express",
"version": "10.4.6",
"version": "10.4.7",
"description": "Nest - modern, fast, powerful node.js web framework (@platform-express)",
"author": "Kamil Mysliwiec",
"license": "MIT",
@@ -25,8 +25,8 @@
"tslib": "2.7.0"
},
"devDependencies": {
"@nestjs/common": "10.4.6",
"@nestjs/core": "10.4.6"
"@nestjs/common": "10.4.7",
"@nestjs/core": "10.4.7"
},
"peerDependencies": {
"@nestjs/common": "^10.0.0",

View File

@@ -1,4 +1,4 @@
import { INestApplication, HttpServer } from '@nestjs/common';
import { HttpServer, INestApplication } from '@nestjs/common';
import {
FastifyBodyParser,
FastifyInstance,
@@ -7,14 +7,14 @@ import {
FastifyPluginCallback,
FastifyPluginOptions,
FastifyRegisterOptions,
FastifyRequest,
FastifyReply,
FastifyRequest,
RawServerBase,
RawServerDefault,
} from 'fastify';
import {
Chain as LightMyRequestChain,
InjectOptions,
Chain as LightMyRequestChain,
Response as LightMyRequestResponse,
} from 'light-my-request';
import { FastifyStaticOptions, FastifyViewOptions } from './external';
@@ -98,33 +98,19 @@ export interface NestFastifyApplication<
opts: FastifyListenOptions,
callback?: (err: Error | null, address: string) => void,
): Promise<TServer>;
listen(
opts?: FastifyListenOptions,
): Promise<TServer>;
listen(opts?: FastifyListenOptions): Promise<TServer>;
listen(
callback?: (err: Error | null, address: string) => void,
): Promise<TServer>;
/**
* @deprecated Variadic listen method is deprecated. Please use `.listen(optionsObject, callback)` instead. The variadic signature will be removed in `fastify@5`
* @see https://github.com/fastify/fastify/pull/3712
*/
listen(
port: number | string,
callback?: (err: Error | null, address: string) => void,
): Promise<TServer>;
/**
* @deprecated Variadic listen method is deprecated. Please use `.listen(optionsObject, callback)` instead. The variadic signature will be removed in `fastify@5`
* @see https://github.com/fastify/fastify/pull/3712
*/
listen(
port: number | string,
address: string,
callback?: (err: Error | null, address: string) => void,
): Promise<TServer>;
/**
* @deprecated Variadic listen method is deprecated. Please use `.listen(optionsObject, callback)` instead. The variadic signature will be removed in `fastify@5`
* @see https://github.com/fastify/fastify/pull/3712
*/
listen(
port: number | string,
address: string,

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/platform-fastify",
"version": "10.4.6",
"version": "10.4.7",
"description": "Nest - modern, fast, powerful node.js web framework (@platform-fastify)",
"author": "Kamil Mysliwiec",
"license": "MIT",

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/platform-socket.io",
"version": "10.4.6",
"version": "10.4.7",
"description": "Nest - modern, fast, powerful node.js web framework (@platform-socket.io)",
"author": "Kamil Mysliwiec",
"license": "MIT",

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/platform-ws",
"version": "10.4.6",
"version": "10.4.7",
"description": "Nest - modern, fast, powerful node.js web framework (@platform-ws)",
"author": "Kamil Mysliwiec",
"license": "MIT",

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/testing",
"version": "10.4.6",
"version": "10.4.7",
"description": "Nest - modern, fast, powerful node.js web framework (@testing)",
"author": "Kamil Mysliwiec",
"license": "MIT",

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/websockets",
"version": "10.4.6",
"version": "10.4.7",
"description": "Nest - modern, fast, powerful node.js web framework (@websockets)",
"author": "Kamil Mysliwiec",
"license": "MIT",
@@ -18,8 +18,8 @@
"tslib": "2.7.0"
},
"devDependencies": {
"@nestjs/common": "10.4.6",
"@nestjs/core": "10.4.6"
"@nestjs/common": "10.4.7",
"@nestjs/core": "10.4.7"
},
"peerDependencies": {
"@nestjs/common": "^10.0.0",