Skip to content

Commit

Permalink
feat: implement decorators
Browse files Browse the repository at this point in the history
  • Loading branch information
glebbash committed May 26, 2021
1 parent e7ab447 commit ad0f505
Show file tree
Hide file tree
Showing 15 changed files with 1,066 additions and 0 deletions.
218 changes: 218 additions & 0 deletions src/decorators/is-boolean.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import { Result } from 'true-myth';

import { createDto, transform } from '../../tests/helpers';
import { IsBoolean } from '../nestjs-swagger-dto';

describe('IsBoolean', () => {
describe('single', () => {
class Test {
@IsBoolean()
booleanField!: boolean;
}

it('accepts booleans', async () => {
expect(await transform(Test, { booleanField: true })).toStrictEqual(
Result.ok(createDto(Test, { booleanField: true }))
);
expect(await transform(Test, { booleanField: false })).toStrictEqual(
Result.ok(createDto(Test, { booleanField: false }))
);
});

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 transform(Test, testValue)).toStrictEqual(
Result.err('booleanField must be a boolean value')
);
}
});
});

describe('stringified', () => {
class Test {
@IsBoolean({ stringified: true })
booleanField!: boolean;
}

it('accepts boolean strings and booleans', async () => {
expect(await transform(Test, { booleanField: 'true' })).toStrictEqual(
Result.ok(createDto(Test, { booleanField: true }))
);
expect(await transform(Test, { booleanField: 'false' })).toStrictEqual(
Result.ok(createDto(Test, { booleanField: false }))
);
expect(await transform(Test, { booleanField: true })).toStrictEqual(
Result.ok(createDto(Test, { booleanField: true }))
);
expect(await transform(Test, { booleanField: false })).toStrictEqual(
Result.ok(createDto(Test, { booleanField: false }))
);
});

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

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

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

it('accepts boolean and undefined', async () => {
expect(await transform(Test, { booleanField: true })).toStrictEqual(
Result.ok(createDto(Test, { booleanField: true }))
);
expect(await transform(Test, { booleanField: false })).toStrictEqual(
Result.ok(createDto(Test, { booleanField: false }))
);
expect(await transform(Test, {})).toStrictEqual(Result.ok(createDto(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 transform(Test, testValue)).toStrictEqual(
Result.err('booleanField must be a boolean value')
);
}
});
});

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

it('accepts boolean and null', async () => {
expect(await transform(Test, { booleanField: true })).toStrictEqual(
Result.ok(createDto(Test, { booleanField: true }))
);
expect(await transform(Test, { booleanField: false })).toStrictEqual(
Result.ok(createDto(Test, { booleanField: false }))
);
expect(await transform(Test, { booleanField: null })).toStrictEqual(
Result.ok(createDto(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 transform(Test, testValue)).toStrictEqual(
Result.err('booleanField must be a boolean value')
);
}
});
});

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

it('accepts boolean and null and undefined', async () => {
expect(await transform(Test, { booleanField: true })).toStrictEqual(
Result.ok(createDto(Test, { booleanField: true }))
);
expect(await transform(Test, { booleanField: false })).toStrictEqual(
Result.ok(createDto(Test, { booleanField: false }))
);
expect(await transform(Test, { booleanField: null })).toStrictEqual(
Result.ok(createDto(Test, { booleanField: null }))
);
expect(await transform(Test, {})).toStrictEqual(Result.ok(createDto(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 transform(Test, testValue)).toStrictEqual(
Result.err('booleanField must be a boolean value')
);
}
});
});

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

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

it('rejects everything else', async () => {
expect(await transform(Test, { booleanField: true })).toStrictEqual(
Result.err('booleanField must be an array')
);
expect(await transform(Test, { booleanField: [1, 2, 3] })).toStrictEqual(
Result.err('each value in booleanField must be a boolean value')
);
});
});
});
17 changes: 17 additions & 0 deletions src/decorators/is-boolean.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Transform } from 'class-transformer';
import { IsBoolean as IsBooleanCV } from 'class-validator';

import { Base, compose, noop } from '../core';

export const IsBoolean = ({
stringified,
...base
}: Base<boolean> & { stringified?: true } = {}): PropertyDecorator =>
compose(
{ type: 'boolean' },
base,
IsBooleanCV({ each: !!base.isArray }),
stringified
? Transform(({ value }) => (value === 'true' ? true : value === 'false' ? false : value))
: noop
);
79 changes: 79 additions & 0 deletions src/decorators/is-constant.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Result } from 'true-myth';

import { createDto, transform } from '../../tests/helpers';
import { IsConstant } from '../nestjs-swagger-dto';

describe('IsConstant', () => {
describe('single', () => {
class Test {
@IsConstant({ value: 123 })
constantField!: 123;
}

it('accepts specified constant', async () => {
expect(await transform(Test, { constantField: 123 })).toStrictEqual(
Result.ok(createDto(Test, { constantField: 123 }))
);
});

it('accepts specified constant', async () => {
class Test {
@IsConstant({ value: [1, 2, 3] })
constantField!: [1, 2, 3];
}

expect(await transform(Test, { constantField: [1, 2, 3] })).toStrictEqual(
Result.err('constantField must be equal to 1, 2, 3')
);
});

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

for (const testValue of testValues) {
expect(await transform(Test, testValue)).toStrictEqual(
Result.err('constantField must be equal to 123')
);
}
});
});

describe('array', () => {
class Test {
@IsConstant({ value: 1, isArray: { length: 3 } })
constantField!: [1, 1, 1];
}

it('accepts specified constant array', async () => {
expect(await transform(Test, { constantField: [1, 1, 1] })).toStrictEqual(
Result.ok(createDto(Test, { constantField: [1, 1, 1] }))
);
});

it('rejects everything else', async () => {
expect(await transform(Test, { constantField: [1] })).toStrictEqual(
Result.err('constantField must contain at least 3 elements')
);
expect(await transform(Test, { constantField: ['1'] })).toStrictEqual(
Result.err('each value in constantField must be equal to 1')
);
expect(await transform(Test, { constantField: ['1', '1', '1'] })).toStrictEqual(
Result.err('each value in constantField must be equal to 1')
);
expect(await transform(Test, { constantField: [1, 2, 3] })).toStrictEqual(
Result.err('each value in constantField must be equal to 1')
);
expect(await transform(Test, { constantField: [2, 2, 2] })).toStrictEqual(
Result.err('each value in constantField must be equal to 1')
);
});
});
});
6 changes: 6 additions & 0 deletions src/decorators/is-constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Equals } from 'class-validator';

import { Base, compose } from '../core';

export const IsConstant = <T>({ value, ...base }: Base<T> & { value: T }): PropertyDecorator =>
compose({ enum: [value] }, base, Equals(value, { each: !!base.isArray }));
Loading

0 comments on commit ad0f505

Please sign in to comment.