1
0

Updates to @Strong()

This commit is contained in:
2021-02-10 14:50:45 -06:00
parent 0ce0e943f5
commit 00dc4b1cdc
3 changed files with 104 additions and 8 deletions

View File

@@ -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<T>(type: string | T): PropertyDecorator {
return function(target: Object, propertyKey: PropertyKey) {
Reflect.defineMetadata(
STRONG_CLASS_KEY,
{
...Reflect.getMetadata(STRONG_CLASS_KEY, target) || {},
[propertyKey]: type
},
target
);
}
}

View File

@@ -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<S> extends Object {
protected removeableProperty(key: string): boolean {
@@ -12,7 +12,27 @@ export abstract class Serde<S> extends Object {
return value instanceof Serde ? value.serialize() : value;
}
private checkStrongTypes(input: Partial<S>): 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<C extends S>(input: { [P in keyof Partial<C>]: 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<S> extends Object {
customSerialize?<C extends S>(input: { [P in keyof Partial<C>]: any }): any;
deserialize(input: Partial<S>): this {
Object.assign(this, input);
return this;
if (this.checkStrongTypes(input)) {
Object.assign(this, input);
return this;
}
throw Error(STRONG_ERROR_MESSAGE);
}
serialize() {

View File

@@ -0,0 +1,67 @@
import 'reflect-metadata';
import { Serde } from "src/serde";
import { Strong } from 'src/decorators';
class StrongTestModel extends Serde<StrongTestModel> {
name: string;
date: Date;
@Strong('string') description: string;
}
class TestModel extends Serde<TestModel> {
key: string;
value: string;
}
class NestedStrongTestModel extends Serde<NestedStrongTestModel> {
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');
});
})
});