UNPKG

ts-deserializable

Version:

Decorator pattern for deserializing unverified data to an instance of a class in typescript.

106 lines (105 loc) 5.57 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const ds_prop_decorator_1 = require("./ds-prop.decorator"); const ds_reporter_1 = require("./ds-reporter"); const ds_attributes_1 = require("./ds-attributes"); const ds_operations_1 = require("./ds-operations"); class DsClassBuilder { constructor() { this.ignore = () => this.decoratorFactory(ds_reporter_1.ReportType.Ignore); this.warn = () => this.decoratorFactory(ds_reporter_1.ReportType.Warn); this.error = () => this.decoratorFactory(ds_reporter_1.ReportType.Error); this.throw = (errorCtor = Error) => this.decoratorFactory(ds_reporter_1.ReportType.Throw, errorCtor); this.getFallback = (fallback) => ((typeof fallback === 'function') ? fallback() : fallback); this.decoratorFactory = (reportType, errorCtor = Error) => ((target) => { // save a reference to the original constructor const originalConstructor = target; // a utility function to generate instances of a class function instanciate(constructor, ...args) { let instance = new constructor(...args); return instance; } // the new constructor behavior const newConstructor = function (...args) { return instanciate(originalConstructor, ...args); }; // deserialization function const deserializeFactory = (userDeserializer) => ((obj) => { let attributes = ds_attributes_1.DsAttributes.get(target.prototype); let operations = ds_operations_1.DsOperations.get(target.prototype); let ret = newConstructor(); let reporter = new ds_reporter_1.DeserializerReporter(reportType, originalConstructor.name, errorCtor); // map values and apply operations Object.keys(attributes).forEach((key) => { let attribute = attributes[key]; let value = (obj[key] !== undefined) ? obj[key] : undefined; // the breaker will stop the evaluation chain and // instead force us to return the fallback value. // TODO: how this is handled can be improved for better logging and debugging let breaker = false; let invalid = false; let invalidWith; // apply operations if (operations[key]) { value = operations[key].reduce((val, operation) => { if (breaker) { return val; } switch (operation.type) { case ds_prop_decorator_1.DsOperationType.Validator: if (!operation.func(val)) { breaker = true; invalid = true; invalidWith = val; } return val; case ds_prop_decorator_1.DsOperationType.Resolver: let res = operation.func(obj); breaker = (res === undefined); return res; case ds_prop_decorator_1.DsOperationType.Operator: default: let opRes = operation.func(val); breaker = (opRes === undefined); return opRes; } }, value); } // if breaker was tripped log invalid if (breaker && invalid) { value = this.getFallback(attribute.fallback); reporter.markInvalid(key, invalidWith, value); } // mark if breaker was tripped or value does not exist else if (breaker || value === undefined) { value = this.getFallback(attribute.fallback); reporter.mark(key, undefined, value); } ret[key] = value; }); // report issues reporter.report(); // call user deserializer function if it exists if (userDeserializer && typeof userDeserializer === 'function') { userDeserializer(obj, ret); } return ret; }); // copy prototype so instanceof operator still works newConstructor.prototype = originalConstructor.prototype; // set a deserializer that also calls the user declared function newConstructor.prototype.deserialize = deserializeFactory(newConstructor.prototype.deserialize); // return new constructor (will override original) return newConstructor; }); } } /** * Exported as a builder class instance purely for consistency sake. * Might be useful in the future for added functionality */ exports.DsClass = () => new DsClassBuilder();