diff --git a/src/decorators/strong.ts b/src/decorators/strong.ts index cb62a75..591f30e 100644 --- a/src/decorators/strong.ts +++ b/src/decorators/strong.ts @@ -1,9 +1,15 @@ export const STRONG_CLASS_KEY = 'serde:strong_class'; +export const STRONG_ERROR_MESSAGE = 'Strong Type mismatch'; -export function Strong(): Function { - return function(target: any) { - if (!Reflect.hasMetadata(STRONG_CLASS_KEY, target)) { - Reflect.defineMetadata(STRONG_CLASS_KEY, 'is_strong_typed', target); - } +export function Strong(type: string | T): PropertyDecorator { + return function(target: Object, propertyKey: PropertyKey) { + Reflect.defineMetadata( + STRONG_CLASS_KEY, + { + ...Reflect.getMetadata(STRONG_CLASS_KEY, target) || {}, + [propertyKey]: type + }, + target + ); } } \ No newline at end of file diff --git a/src/serde.ts b/src/serde.ts index 30e1f0f..97fb8fa 100644 --- a/src/serde.ts +++ b/src/serde.ts @@ -1,4 +1,4 @@ -import { EXCLUDED_PROPERTIES_KEY, PLUCK_PROPERTIES_KEY } from './decorators'; +import { EXCLUDED_PROPERTIES_KEY, PLUCK_PROPERTIES_KEY, STRONG_CLASS_KEY, STRONG_ERROR_MESSAGE } from './decorators'; export abstract class Serde extends Object { protected removeableProperty(key: string): boolean { @@ -12,7 +12,27 @@ export abstract class Serde extends Object { return value instanceof Serde ? value.serialize() : value; } + private checkStrongTypes(input: Partial): boolean { + const strongTyped = Reflect.getMetadata(STRONG_CLASS_KEY, this); + if (!strongTyped) { + return true; + } + return Object.entries(strongTyped).every(([key, type]) => { + if (typeof type === 'string') { + return typeof input[key] === type; + } + if (typeof type === 'object') { + const clazz = type as any; + return input[key] instanceof clazz; + } + return false; + }); + } + copyWith(input: { [P in keyof Partial]: any }): this { + if (!this.checkStrongTypes(input)) { + throw Error(STRONG_ERROR_MESSAGE); + } return Object.assign( Object.create(Object.getPrototypeOf(this)), this, @@ -25,8 +45,11 @@ export abstract class Serde extends Object { customSerialize?(input: { [P in keyof Partial]: any }): any; deserialize(input: Partial): this { - Object.assign(this, input); - return this; + if (this.checkStrongTypes(input)) { + Object.assign(this, input); + return this; + } + throw Error(STRONG_ERROR_MESSAGE); } serialize() { diff --git a/tests/decorators/strong.spec.ts b/tests/decorators/strong.spec.ts new file mode 100644 index 0000000..f0d11a3 --- /dev/null +++ b/tests/decorators/strong.spec.ts @@ -0,0 +1,67 @@ +import 'reflect-metadata'; +import { Serde } from "src/serde"; +import { Strong } from 'src/decorators'; + +class StrongTestModel extends Serde { + name: string; + date: Date; + @Strong('string') description: string; +} + +class TestModel extends Serde { + key: string; + value: string; +} + +class NestedStrongTestModel extends Serde { + name: string; + @Strong(TestModel) nested: TestModel; +} + +describe('@Strong() Decorator', () => { + describe('simple strong model', () => { + it('should set value nicely', () => { + const testData: any = { + name: 'test model', + description: 'this is a test model', + } + expect(() => { + new StrongTestModel().deserialize(testData) + }).not.toThrow(); + expect(new StrongTestModel().deserialize(testData).description).toBe('this is a test model'); + }); + + it('should throw', () => { + const testData: any = { + name: 'test model', + description: 1234, + } + expect(() => { + new StrongTestModel().deserialize(testData) + }).toThrow('Strong Type mismatch'); + }); + }); + + describe('nested strong model', () => { + it('should set value nicely', () => { + const testData: any = { + name: 'test model', + nested: new TestModel().deserialize({ key: 'foo', value: 'bar' }) + } + expect(() => { + new NestedStrongTestModel().deserialize(testData) + }).not.toThrow(); + expect(new NestedStrongTestModel().deserialize(testData).name).toBe('test model'); + }); + + it('should throw', () => { + const testData: any = { + name: 'test model', + nested: false + } + expect(() => { + new StrongTestModel().deserialize(testData) + }).toThrow('Strong Type mismatch'); + }); + }) +}); \ No newline at end of file