Skip to content

Commit

Permalink
feat(): add type helpers functions
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed Mar 26, 2020
1 parent 2820cb9 commit 8f68926
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from './decorators';
export * from './document-builder';
export * from './interfaces';
export * from './swagger-module';
export * from './type-helpers';
export * from './utils';
3 changes: 3 additions & 0 deletions lib/type-helpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './omit-type.helper';
export * from './partial-type.helper';
export * from './pick-type.helper';
27 changes: 27 additions & 0 deletions lib/type-helpers/omit-type.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Type } from '@nestjs/common';
import { DECORATORS } from '../constants';
import { ApiProperty } from '../decorators';
import { ModelPropertiesAccessor } from '../services/model-properties-accessor';

const modelPropertiesAccessor = new ModelPropertiesAccessor();

export function OmitType<T, K extends keyof T>(
classRef: Type<T>,
keys: K[]
): Type<Omit<T, typeof keys[number]>> {
const fields = modelPropertiesAccessor
.getModelProperties(classRef.prototype)
.filter(item => !keys.includes(item as K));

abstract class OmitTypeClass {}

fields.forEach(key => {
const metadata = Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
classRef.prototype,
key
);
ApiProperty(metadata)(OmitTypeClass.prototype, key);
});
return OmitTypeClass as Type<Omit<T, typeof keys[number]>>;
}
28 changes: 28 additions & 0 deletions lib/type-helpers/partial-type.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Type } from '@nestjs/common';
import { DECORATORS } from '../constants';
import { ApiProperty } from '../decorators';
import { ModelPropertiesAccessor } from '../services/model-properties-accessor';

const modelPropertiesAccessor = new ModelPropertiesAccessor();

export function PartialType<T>(classRef: Type<T>): Type<Partial<T>> {
const fields = modelPropertiesAccessor.getModelProperties(classRef.prototype);

abstract class PartialTypeClass {}

fields.forEach(key => {
const metadata =
Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
classRef.prototype,
key
) || {};

ApiProperty({
...metadata,
required: false
})(PartialTypeClass.prototype, key);
});

return PartialTypeClass as Type<Partial<T>>;
}
28 changes: 28 additions & 0 deletions lib/type-helpers/pick-type.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Type } from '@nestjs/common';
import { DECORATORS } from '../constants';
import { ApiProperty } from '../decorators';
import { ModelPropertiesAccessor } from '../services/model-properties-accessor';

const modelPropertiesAccessor = new ModelPropertiesAccessor();

export function PickType<T, K extends keyof T>(
classRef: Type<T>,
keys: K[]
): Type<Pick<T, typeof keys[number]>> {
const fields = modelPropertiesAccessor
.getModelProperties(classRef.prototype)
.filter(item => keys.includes(item as K));

abstract class PickTypeClass {}

fields.forEach(key => {
const metadata = Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
classRef.prototype,
key
);
ApiProperty(metadata)(PickTypeClass.prototype, key);
});

return PickTypeClass as Type<Pick<T, typeof keys[number]>>;
}
30 changes: 30 additions & 0 deletions test/type-helpers/omit-type.helper.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Type } from '@nestjs/common';
import { ApiProperty } from '../../lib/decorators';
import { ModelPropertiesAccessor } from '../../lib/services/model-properties-accessor';
import { OmitType } from '../../lib/type-helpers';

describe('OmitType', () => {
class CreateUserDto {
@ApiProperty({ required: true })
login: string;

@ApiProperty({ minLength: 10 })
password: string;
}

class UpdateUserDto extends OmitType(CreateUserDto, ['login']) {}

let modelPropertiesAccessor: ModelPropertiesAccessor;

beforeEach(() => {
modelPropertiesAccessor = new ModelPropertiesAccessor();
});

it('should omit "login" property', () => {
expect(
modelPropertiesAccessor.getModelProperties(
(UpdateUserDto.prototype as any) as Type<any>
)
).toEqual(['password']);
});
});
54 changes: 54 additions & 0 deletions test/type-helpers/partial-type.helper.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Type } from '@nestjs/common';
import { DECORATORS } from '../../lib/constants';
import { ApiProperty } from '../../lib/decorators';
import { ModelPropertiesAccessor } from '../../lib/services/model-properties-accessor';
import { PartialType } from '../../lib/type-helpers';

describe('PartialType', () => {
class CreateUserDto {
@ApiProperty({ required: true })
login: string;

@ApiProperty({ minLength: 10 })
password: string;
}

class UpdateUserDto extends PartialType(CreateUserDto) {}

let modelPropertiesAccessor: ModelPropertiesAccessor;

beforeEach(() => {
modelPropertiesAccessor = new ModelPropertiesAccessor();
});

it('should return partial class', () => {
expect(
modelPropertiesAccessor.getModelProperties(
(UpdateUserDto.prototype as any) as Type<any>
)
).toEqual(['login', 'password']);
});

it('should set "required" option to "false" for each property', () => {
const classRef = (UpdateUserDto.prototype as any) as Type<any>;
const keys = modelPropertiesAccessor.getModelProperties(classRef);
const metadata = keys.map(key => {
return Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
classRef,
key
);
});
expect(metadata[0]).toEqual({
isArray: false,
required: false,
type: String
});
expect(metadata[1]).toEqual({
isArray: false,
required: false,
minLength: 10,
type: String
});
});
});
30 changes: 30 additions & 0 deletions test/type-helpers/pick-type.helper.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Type } from '@nestjs/common';
import { ApiProperty } from '../../lib/decorators';
import { ModelPropertiesAccessor } from '../../lib/services/model-properties-accessor';
import { PickType } from '../../lib/type-helpers';

describe('PickType', () => {
class CreateUserDto {
@ApiProperty({ required: true })
login: string;

@ApiProperty({ minLength: 10 })
password: string;
}

class UpdateUserDto extends PickType(CreateUserDto, ['login']) {}

let modelPropertiesAccessor: ModelPropertiesAccessor;

beforeEach(() => {
modelPropertiesAccessor = new ModelPropertiesAccessor();
});

it('should pick "login" property', () => {
expect(
modelPropertiesAccessor.getModelProperties(
(UpdateUserDto.prototype as any) as Type<any>
)
).toEqual(['login']);
});
});

0 comments on commit 8f68926

Please sign in to comment.