diff --git a/README.md b/README.md index e77a5c1..fa6114f 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ This library contains the following decorators | IsNumber | numbers | | IsObject | typed plain js objects | | IsString | strings | +| IsUnknown | any json value | All of the decorators support the following parameters: diff --git a/src/decorators/is-unknown.spec.ts b/src/decorators/is-unknown.spec.ts new file mode 100644 index 0000000..b2bcd2f --- /dev/null +++ b/src/decorators/is-unknown.spec.ts @@ -0,0 +1,66 @@ +import { Result } from 'true-myth'; + +import { generateSchemas, input, make, output } from '../../tests/helpers'; +import { IsUnknown } from '../nestjs-swagger-dto'; + +describe('IsUnknown', () => { + describe('single', () => { + class Test { + @IsUnknown() + unknownField!: unknown; + } + + it('generates correct schema', async () => { + expect(await generateSchemas([Test])).toStrictEqual({ + Test: { + type: 'object', + properties: { + unknownField: { + oneOf: [ + { type: 'string' }, + { type: 'number' }, + { type: 'integer' }, + { type: 'boolean' }, + { type: 'array' }, + { type: 'object' }, + ], + }, + }, + required: ['unknownField'], + }, + }); + }); + + it('accepts anything except null and undefined', async () => { + expect(await input(Test, { unknownField: false })).toStrictEqual( + Result.ok(make(Test, { unknownField: false })) + ); + expect(await input(Test, { unknownField: 123 })).toStrictEqual( + Result.ok(make(Test, { unknownField: 123 })) + ); + expect(await input(Test, { unknownField: 'abc' })).toStrictEqual( + Result.ok(make(Test, { unknownField: 'abc' })) + ); + expect(await input(Test, { unknownField: [] })).toStrictEqual( + Result.ok(make(Test, { unknownField: [] })) + ); + expect(await input(Test, { unknownField: {} })).toStrictEqual( + Result.ok(make(Test, { unknownField: {} })) + ); + }); + + it('transforms to plain', async () => { + const dto = make(Test, { unknownField: true }); + expect(output(dto)).toStrictEqual({ unknownField: true }); + }); + + it('rejects null and undefined by default', async () => { + expect(await input(Test, { unknownField: null })).toStrictEqual( + Result.err('unknownField should not be null or undefined') + ); + expect(await input(Test, {})).toStrictEqual( + Result.err('unknownField should not be null or undefined') + ); + }); + }); +}); diff --git a/src/decorators/is-unknown.ts b/src/decorators/is-unknown.ts new file mode 100644 index 0000000..26aa872 --- /dev/null +++ b/src/decorators/is-unknown.ts @@ -0,0 +1,24 @@ +import { IsDefined } from 'class-validator'; + +import { BasePropertyOptions, compose } from '../core'; + +export const IsUnknown = ({ + ...base +}: BasePropertyOptions & { + example?: unknown; + default?: unknown; +} = {}): PropertyDecorator => + compose( + { + oneOf: [ + { type: 'string' }, + { type: 'number' }, + { type: 'integer' }, + { type: 'boolean' }, + { type: 'array' }, + { type: 'object' }, + ], + }, + base, + IsDefined() + ); diff --git a/src/nestjs-swagger-dto.ts b/src/nestjs-swagger-dto.ts index ac1c604..63bd07b 100644 --- a/src/nestjs-swagger-dto.ts +++ b/src/nestjs-swagger-dto.ts @@ -12,4 +12,5 @@ export * from './decorators/is-nested'; export * from './decorators/is-number'; export * from './decorators/is-object'; export * from './decorators/is-string'; +export * from './decorators/is-unknown'; export * from './decorators/typed-headers.decorator';