Skip to content

Commit

Permalink
Merge branch 'glebbash:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
mdovhopo authored Oct 1, 2021
2 parents 4166b6c + 49232f4 commit fb6306b
Show file tree
Hide file tree
Showing 9 changed files with 419 additions and 185 deletions.
36 changes: 32 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,45 @@
[![Deploy](https://github.com/glebbash/nestjs-swagger-dto/workflows/build/badge.svg)](https://github.com/glebbash/nestjs-swagger-dto/actions)
[![Coverage Status](https://coveralls.io/repos/github/glebbash/nestjs-swagger-dto/badge.svg?branch=master)](https://coveralls.io/github/glebbash/nestjs-swagger-dto?branch=master)

Nestjs swagger dto decorators
## Nest.js Swagger DTO decorators

<!-- TODO: add extended examples -->
This library combines common `@nestjs/swagger`, `class-transformer` and `class-validator` decorators that are used together into one decorator for full Nest.js DTO lifecycle including OpenAPI schema descriptions.

Installation:
## Installation

```sh
npm i nestjs-swagger-dto
```

<!-- TODO: add usage examples -->
## Contents

This library contains the following decorators

| Name | Description |
| ---------- | ----------------------------- |
| IsBoolean | boolean |
| IsConstant | constant |
| IsDate | date / date-time |
| IsEnum | enum object / array of values |
| IsNested | nested DTO |
| IsNumber | numbers |
| IsObject | typed plain js objects |
| IsString | strings |

All of the decorators support the following parameters:

| Name | Description |
| ----------- | ----------------------------------------------------------- |
| description | adds description |
| example | adds example |
| name | sets the name for serialized property |
| optional | makes property optional |
| nullable | makes property nullable |
| isArray | changes the type of property to array of items of this type |

Also decorators have additional parameters like: `min`, `max` for `IsNumber`.

## Other

Bootstrapped with: [create-ts-lib-gh](https://github.com/glebbash/create-ts-lib-gh)

Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@
"@commitlint/config-conventional"
]
},
"peerDependencies": {
"@nestjs/swagger": ">= 4.8",
"class-transformer": ">= 0.4",
"class-validator": ">= 0.13"
},
"devDependencies": {
"@commitlint/cli": "11.0.0",
"@commitlint/config-conventional": "11.0.0",
Expand Down Expand Up @@ -71,10 +76,5 @@
"ts-node": "^9.1.1",
"typedoc": "^0.20.28",
"typescript": "^4.1.3"
},
"peerDependencies": {
"@nestjs/swagger": ">= 4.8",
"class-transformer": ">= 0.4",
"class-validator": ">= 0.13"
}
}
299 changes: 299 additions & 0 deletions src/core.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
import { IsBoolean } from 'class-validator';
import { Result } from 'true-myth';

import { generateSchemas, input, make, output } from '../tests/helpers';
import { Base, compose } from './core';

describe('core options', () => {
const DtoDecorator = ({ meta, ...base }: Base<boolean> & { meta?: string }): PropertyDecorator =>
compose(
{ type: 'boolean' },
base,
IsBoolean({ each: !!base.isArray }),
Reflect.metadata('meta', meta)
);

describe('support for custom options', () => {
class Test {
@DtoDecorator({ meta: 'hello' })
booleanField!: boolean;
}

it('generates correct schema', async () => {
expect(await generateSchemas([Test])).toStrictEqual({
Test: {
type: 'object',
properties: {
booleanField: {
type: 'boolean',
},
},
required: ['booleanField'],
},
});
});

it('transforms to plain', async () => {
const dto = make(Test, { booleanField: true });
expect(output(dto)).toStrictEqual({ booleanField: true });
});

it('applies property decorator correctly', async () => {
const testInstance = new Test();

expect(Reflect.getMetadata('meta', testInstance, 'booleanField')).toBe('hello');
});
});

describe('optional', () => {
class Test {
@DtoDecorator({ optional: true, meta: 'hello' })
booleanField?: boolean;
}

it('generates correct schema', async () => {
expect(await generateSchemas([Test])).toStrictEqual({
Test: {
type: 'object',
properties: {
booleanField: {
type: 'boolean',
},
},
},
});
});

it('transforms to plain', async () => {
const dto = make(Test, {});
expect(output(dto)).toStrictEqual({});
});

it('accepts value and undefined', async () => {
expect(await input(Test, { booleanField: true })).toStrictEqual(
Result.ok(make(Test, { booleanField: true }))
);
expect(await input(Test, { booleanField: false })).toStrictEqual(
Result.ok(make(Test, { booleanField: false }))
);
expect(await input(Test, {})).toStrictEqual(Result.ok(make(Test, {})));
});

it('rejects everything else', async () => {
const testValues: unknown[] = [
{ booleanField: 'true' },
{ booleanField: 'false' },
{ booleanField: 'abc' },
{ booleanField: 0 },
{ booleanField: [] },
{ booleanField: {} },
{ booleanField: null },
];

for (const testValue of testValues) {
expect(await input(Test, testValue)).toStrictEqual(
Result.err('booleanField must be a boolean value')
);
}
});
});

describe('default', () => {
class Test {
@DtoDecorator({ optional: true, default: false })
booleanField?: boolean;
}

it('generates correct schema', async () => {
expect(await generateSchemas([Test])).toStrictEqual({
Test: {
type: 'object',
properties: {
booleanField: {
type: 'boolean',
default: false,
},
},
},
});
});

it('transforms to plain', async () => {
const dto = make(Test, {});
expect(output(dto)).toStrictEqual({ booleanField: false });
});

it('returns specified defaults if value is undefined', async () => {
expect(await input(Test, { booleanField: true })).toStrictEqual(
Result.ok(make(Test, { booleanField: true }))
);
expect(await input(Test, { booleanField: false })).toStrictEqual(
Result.ok(make(Test, { booleanField: false }))
);
expect(await input(Test, {})).toStrictEqual(Result.ok(make(Test, { booleanField: false })));
});
});

describe('nullable', () => {
class Test {
@DtoDecorator({ nullable: true })
booleanField!: boolean | null;
}

it('generates correct schema', async () => {
expect(await generateSchemas([Test])).toStrictEqual({
Test: {
type: 'object',
properties: {
booleanField: {
type: 'boolean',
nullable: true,
},
},
required: ['booleanField'],
},
});
});

it('transforms to plain', async () => {
const dto = make(Test, { booleanField: null });
expect(output(dto)).toStrictEqual({ booleanField: null });
});

it('accepts value and null', async () => {
expect(await input(Test, { booleanField: true })).toStrictEqual(
Result.ok(make(Test, { booleanField: true }))
);
expect(await input(Test, { booleanField: false })).toStrictEqual(
Result.ok(make(Test, { booleanField: false }))
);
expect(await input(Test, { booleanField: null })).toStrictEqual(
Result.ok(make(Test, { booleanField: null }))
);
});

it('rejects everything else', async () => {
const testValues: unknown[] = [
{ booleanField: 'true' },
{ booleanField: 'false' },
{ booleanField: 'abc' },
{ booleanField: 0 },
{ booleanField: [] },
{ booleanField: {} },
{},
];

for (const testValue of testValues) {
expect(await input(Test, testValue)).toStrictEqual(
Result.err('booleanField must be a boolean value')
);
}
});
});

describe('optional and nullable', () => {
class Test {
@DtoDecorator({ optional: true, nullable: true })
booleanField?: boolean | null;
}

it('generates correct schema', async () => {
expect(await generateSchemas([Test])).toStrictEqual({
Test: {
type: 'object',
properties: {
booleanField: {
type: 'boolean',
nullable: true,
},
},
},
});
});

it('transforms to plain', async () => {
const dto = make(Test, { booleanField: null });
expect(output(dto)).toStrictEqual({ booleanField: null });

const dto2 = make(Test, {});
expect(output(dto2)).toStrictEqual({});
});

it('accepts value, null and undefined', async () => {
expect(await input(Test, { booleanField: true })).toStrictEqual(
Result.ok(make(Test, { booleanField: true }))
);
expect(await input(Test, { booleanField: false })).toStrictEqual(
Result.ok(make(Test, { booleanField: false }))
);
expect(await input(Test, { booleanField: null })).toStrictEqual(
Result.ok(make(Test, { booleanField: null }))
);
expect(await input(Test, {})).toStrictEqual(Result.ok(make(Test, {})));
});

it('rejects everything else', async () => {
const testValues: unknown[] = [
{ booleanField: 'true' },
{ booleanField: 'false' },
{ booleanField: 'abc' },
{ booleanField: 0 },
{ booleanField: [] },
{ booleanField: {} },
];

for (const testValue of testValues) {
expect(await input(Test, testValue)).toStrictEqual(
Result.err('booleanField must be a boolean value')
);
}
});
});

describe('array', () => {
class Test {
@DtoDecorator({ isArray: true })
booleanField!: boolean[];
}

it('generates correct schema', async () => {
expect(await generateSchemas([Test])).toStrictEqual({
Test: {
type: 'object',
properties: {
booleanField: {
type: 'array',
items: {
type: 'boolean',
},
},
},
required: ['booleanField'],
},
});
});

it('transforms to plain', async () => {
const dto = make(Test, { booleanField: [true, false] });
expect(output(dto)).toStrictEqual({ booleanField: [true, false] });
});

it('accepts value arrays', async () => {
expect(await input(Test, { booleanField: [true, false] })).toStrictEqual(
Result.ok(make(Test, { booleanField: [true, false] }))
);
expect(await input(Test, { booleanField: [] })).toStrictEqual(
Result.ok(make(Test, { booleanField: [] }))
);
});

it('rejects everything else', async () => {
expect(await input(Test, { booleanField: true })).toStrictEqual(
Result.err('booleanField must be an array')
);
expect(await input(Test, { booleanField: [1, 2, 3] })).toStrictEqual(
Result.err('each value in booleanField must be a boolean value')
);
});
});
});
4 changes: 2 additions & 2 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ export const compose = <T>(
def !== undefined ? Transform(({ value }) => (value === undefined ? def : value)) : noop,
ApiProperty({
...apiPropertyOptions,
minLength,
maxLength,
minItems: minLength,
maxItems: maxLength,
...(nullable && { nullable }),
isArray: !!isArray,
name,
Expand Down
Loading

0 comments on commit fb6306b

Please sign in to comment.