Skip to content

Commit

Permalink
feat(nestjs): add MapPipe to transform Query and Body
Browse files Browse the repository at this point in the history
  • Loading branch information
nartc committed Mar 14, 2021
1 parent 746e94e commit d8198e0
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 14 deletions.
1 change: 1 addition & 0 deletions packages/nestjs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './lib/interfaces';
export * from './lib/di';
export * from './lib/abstracts';
export * from './lib/interceptors';
export * from './lib/pipes';
29 changes: 15 additions & 14 deletions packages/nestjs/src/lib/interceptors/map.interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { isEmpty } from '@automapper/core';
import type { MapOptions, Mapper } from '@automapper/types';
import type {
CallHandler,
Expand All @@ -9,7 +8,12 @@ import { mixin, Optional } from '@nestjs/common';
import type { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { InjectMapper } from '../di';
import { memoize } from './memoize.util';
import {
getTransformOptions,
memoize,
shouldSkipTransform,
transformArray,
} from '../utils';

export const MapInterceptor: (
to: unknown,
Expand All @@ -20,10 +24,11 @@ export const MapInterceptor: (
function createMapInterceptor(
to: unknown,
from: unknown,
options?: { isArray?: boolean; mapperName?: string }
options?: { isArray?: boolean; mapperName?: string } & MapOptions
): new (...args: any[]) => NestInterceptor {
const { isArray = false, mapperName, ...mapOptions } = options || {};
const transformedMapOptions = isEmpty(mapOptions) ? undefined : mapOptions;
const { isArray, mapperName, transformedMapOptions } = getTransformOptions(
options
);

class MixinMapInterceptor implements NestInterceptor {
constructor(
Expand All @@ -34,30 +39,26 @@ function createMapInterceptor(
context: ExecutionContext,
next: CallHandler
): Promise<Observable<unknown>> {
if (!this.mapper || !to || !from) {
if (shouldSkipTransform(this.mapper, to, from)) {
return next.handle();
}

try {
return next.handle().pipe(
map((response) => {
if (isArray) {
if (!Array.isArray(response)) return response;
return this.mapper?.mapArray(
return transformArray(
response,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
to as any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
from as any,
this.mapper,
to,
from,
transformedMapOptions
);
}

return this.mapper?.map(
response,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
to as any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
from as any,
transformedMapOptions
);
Expand Down
1 change: 1 addition & 0 deletions packages/nestjs/src/lib/pipes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './map.pipe';
64 changes: 64 additions & 0 deletions packages/nestjs/src/lib/pipes/map.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { MapOptions, Mapper } from '@automapper/types';
import type { ArgumentMetadata, PipeTransform } from '@nestjs/common';
import { mixin, Optional } from '@nestjs/common';
import { InjectMapper } from '../di';
import {
getTransformOptions,
memoize,
shouldSkipTransform,
transformArray,
} from '../utils';

export const MapPipe: (
to: unknown,
from: unknown,
options?: { isArray?: boolean; mapperName?: string } & MapOptions
) => PipeTransform = memoize(createMapPipe);

function createMapPipe(
to: unknown,
from: unknown,
options?: { isArray?: boolean; mapperName?: string }
): new (...args: any[]) => PipeTransform {
const { isArray, mapperName, transformedMapOptions } = getTransformOptions(
options
);

class MixinMapPipe implements PipeTransform {
constructor(
@Optional() @InjectMapper(mapperName) private readonly mapper?: Mapper
) {}

transform(value: any, { type }: ArgumentMetadata): any {
if (
shouldSkipTransform(this.mapper, to, from) ||
(type !== 'body' && type !== 'query')
) {
return value;
}

try {
if (isArray) {
return transformArray(
value,
this.mapper,
to,
from,
transformedMapOptions
);
}

return this.mapper.map(
value,
to as any,
from as any,
transformedMapOptions
);
} catch (e) {
return value;
}
}
}

return mixin(MixinMapPipe);
}
2 changes: 2 additions & 0 deletions packages/nestjs/src/lib/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './memoize.util';
export * from './transform.util';
33 changes: 33 additions & 0 deletions packages/nestjs/src/lib/utils/transform.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { isEmpty } from '@automapper/core';
import type { MapOptions, Mapper } from '@automapper/types';

export function shouldSkipTransform(
mapper: Mapper,
to: unknown,
from: unknown
): boolean {
return !mapper || !to || !from;
}

export function transformArray(
value: unknown,
mapper: Mapper,
to: any,
from: any,
options?: MapOptions
) {
if (!Array.isArray(value)) return value;
return mapper.mapArray(value, to, from, options);
}

export function getTransformOptions(
options?: { isArray?: boolean; mapperName?: string } & MapOptions
): {
mapperName: string;
isArray: boolean;
transformedMapOptions?: MapOptions;
} {
const { isArray = false, mapperName, ...mapOptions } = options || {};
const transformedMapOptions = isEmpty(mapOptions) ? undefined : mapOptions;
return { isArray, mapperName, transformedMapOptions };
}

0 comments on commit d8198e0

Please sign in to comment.