Skip to content

Commit

Permalink
Merge pull request #3 from hodfords-solutions/feat/support-exclude-keys
Browse files Browse the repository at this point in the history
feat: support exclude keys
  • Loading branch information
hodfords-minhngo-be authored Jun 19, 2024
2 parents 1f3ca3b + dc54066 commit 5808e40
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 15 deletions.
1 change: 1 addition & 0 deletions libs/constants/provider-key.constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const NESTJS_RESPONSE_CONFIG_OPTIONS = 'NESTJS_RESPONSE_CONFIG_OPTIONS';
1 change: 1 addition & 0 deletions libs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './interceptors/response.interceptor';
export * from './types/response-metadata.type';
export * from './decorators/response-models.decorator';
export * from './types/handle-result.type';
export * from './modules/response.module';
58 changes: 47 additions & 11 deletions libs/interceptors/response.interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { NativeClassResponseNamesConstant } from './../constants/native-class-response-names.constant';
import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common';
import { CallHandler, ExecutionContext, Inject, Injectable, Logger, NestInterceptor } from '@nestjs/common';
import { plainToInstance } from 'class-transformer';
import { validateSync, isBoolean } from 'class-validator';
import { map, Observable } from 'rxjs';
import { isBoolean, validateSync } from 'class-validator';
import { NESTJS_RESPONSE_CONFIG_OPTIONS } from 'libs/constants/provider-key.constant';
import { ConfigOption } from 'libs/types/config-option.type';
import { Observable, map } from 'rxjs';
import { RESPONSE_METADATA_KEY, RESPONSE_METADATA_KEYS } from '../constants/metadata.constant';
import { ResponseValidateException } from '../exceptions/response-validate.exception';
import { ResponseMetadata } from '../types/response-metadata.type';
import { NativeValueResponse } from '../responses/native-value.response';
import { HandleResult } from '../types/handle-result.type';
import { ResponseMetadata } from '../types/response-metadata.type';
import { NativeClassResponseNamesConstant } from './../constants/native-class-response-names.constant';
import { ModuleRef } from '@nestjs/core';

let Metadata = null;

Expand All @@ -21,9 +24,20 @@ try {
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
private readonly logger = new Logger(ResponseInterceptor.name);
private configOption: ConfigOption;

constructor(private moduleRef: ModuleRef) {
this.configOption = this.moduleRef.get(NESTJS_RESPONSE_CONFIG_OPTIONS, { strict: false });
}

intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(map((data) => this.handleResponse(context, data)));
return next.handle().pipe(
map((data) => {
const res = this.handleResponse(context, data);

return this.excludeByKeys(res, this.configOption?.excludeKeys || []);
})
);
}

handleResponse(context: ExecutionContext, data: any) {
Expand All @@ -38,7 +52,29 @@ export class ResponseInterceptor implements NestInterceptor {
return data;
}

handleOneTypeResponse(context: ExecutionContext, data: any, responseMetadata: ResponseMetadata) {
private excludeByKeys(data: any, keys: string[]) {
for (const key of keys) {
data = this.excludeByKey(data, key);
}

return data;
}

private excludeByKey(data: any, key: string) {
for (const prop in data) {
if (typeof data[prop] === 'object') {
data[prop] = this.excludeByKey(data[prop], key);
} else if (Array.isArray(data[prop])) {
data[prop] = data[prop].map((item: any) => this.excludeByKey(item, key));
} else if (prop === key) {
delete data[prop];
}
}

return data;
}

private handleOneTypeResponse(context: ExecutionContext, data: any, responseMetadata: ResponseMetadata) {
if (!isBoolean(data) && !data) {
return this.handleEmptyResponse(responseMetadata, data);
}
Expand All @@ -49,7 +85,7 @@ export class ResponseInterceptor implements NestInterceptor {
return this.handleSingleResponse(responseMetadata, data);
}

handleMultiTypeResponse(context: ExecutionContext, data: any, responseMetadatas: ResponseMetadata[]) {
private handleMultiTypeResponse(context: ExecutionContext, data: any, responseMetadatas: ResponseMetadata[]) {
const results: HandleResult[] = [];
const newMetadatas = this.filterResponseMetadatas(responseMetadatas, data);
for (const metadata of newMetadatas) {
Expand All @@ -70,7 +106,7 @@ export class ResponseInterceptor implements NestInterceptor {
throw new ResponseValidateException(errors);
}

handleSingleResponse(responseMetadata: ResponseMetadata, data: any) {
private handleSingleResponse(responseMetadata: ResponseMetadata, data: any) {
if (NativeClassResponseNamesConstant.includes(responseMetadata.responseClass.name)) {
return this.handleNativeValueResponse(responseMetadata, data);
}
Expand All @@ -86,7 +122,7 @@ export class ResponseInterceptor implements NestInterceptor {
return plainToInstance(responseMetadata.responseClass, data);
}

handleListResponse(context: ExecutionContext, responseMetadata: ResponseMetadata, data: any) {
private handleListResponse(context: ExecutionContext, responseMetadata: ResponseMetadata, data: any) {
const newData: object[] = [];
for (const item of data) {
const newItem = this.handleSingleResponse(responseMetadata, item);
Expand All @@ -98,7 +134,7 @@ export class ResponseInterceptor implements NestInterceptor {
return newData;
}

handleEmptyResponse(responseMetadata: ResponseMetadata, data: any) {
private handleEmptyResponse(responseMetadata: ResponseMetadata, data: any) {
if (responseMetadata.isAllowEmpty) {
return data;
} else {
Expand Down
25 changes: 25 additions & 0 deletions libs/modules/response.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { DynamicModule, Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { ResponseInterceptor } from '../interceptors/response.interceptor';
import { NESTJS_RESPONSE_CONFIG_OPTIONS } from '../constants/provider-key.constant';
import { ConfigOption } from '../types/config-option.type';

@Module({})
export class ResponseModule {
static forRoot(option?: ConfigOption): DynamicModule {
return {
module: ResponseModule,
providers: [
{
provide: NESTJS_RESPONSE_CONFIG_OPTIONS,
useValue: option
},
{
provide: APP_INTERCEPTOR,
useClass: ResponseInterceptor
}
],
exports: []
};
}
}
3 changes: 3 additions & 0 deletions libs/types/config-option.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type ConfigOption = {
excludeKeys?: string[];
};
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hodfords/nestjs-response",
"version": "10.0.0",
"version": "10.1.0",
"description": "",
"author": "",
"license": "UNLICENSED",
Expand Down
7 changes: 7 additions & 0 deletions src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ export class AppController {
return { name: 'test' };
}

@Get('exclude')
@ResponseModel(UserResponse, false, true)
@HttpCode(HttpStatus.OK)
getExclude() {
return { name: 'test', secret: 'secret' };
}

@Get('undefined')
@ResponseModel(UserResponse, false, true)
@HttpCode(HttpStatus.OK)
Expand Down
8 changes: 7 additions & 1 deletion src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { ResponseModule } from '../libs/modules/response.module';

const nestjsResponseConfig = ResponseModule.forRoot({
excludeKeys: ['secret']
});

@Module({
imports: [],
imports: [nestjsResponseConfig],
providers: [],
controllers: [AppController]
})
export class AppModule {}
7 changes: 6 additions & 1 deletion src/responses/user.response.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { IsString } from 'class-validator';
import { IsOptional, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class UserResponse {
@ApiProperty()
@IsString()
name: string;

@ApiProperty()
@IsString()
@IsOptional()
secretKey: string;
}

0 comments on commit 5808e40

Please sign in to comment.