import BaseValue from './BaseValue.js';
import ByteArray from './ByteArray.js';
import TransactionDescriptorProcessor from './TransactionDescriptorProcessor.js';
const buildEnumStringToValueMap = EnumClass => new Map(Object.getOwnPropertyNames(EnumClass)
.filter(name => name.toUpperCase() === name)
.map(name => [name.toLowerCase(), EnumClass[name]]));
const nameToEnumValue = (mapping, enumType, enumValueName) => {
if (!mapping.has(enumValueName))
throw RangeError(`unknown value ${enumValueName} for type ${enumType}`);
return mapping.get(enumValueName);
};
const buildTypeHintsMap = structValue => {
/** @type {{[key: string]: string}} */
const typeHints = {};
const rawTypeHints = structValue.constructor.TYPE_HINTS || {};
Object.getOwnPropertyNames(rawTypeHints).forEach(key => {
const hint = rawTypeHints[key];
let ruleName;
if (0 === hint.indexOf('array['))
ruleName = hint;
else if (0 === hint.indexOf('enum:'))
ruleName = hint.substring('enum:'.length);
else if (0 === hint.indexOf('pod:'))
ruleName = hint.substring('pod:'.length);
else if (0 === hint.indexOf('struct:'))
ruleName = hint;
if (ruleName)
typeHints[key] = ruleName;
});
return typeHints;
};
const typeConverterFactory = (module, customTypeConverter, value) => {
if (customTypeConverter && customTypeConverter(value))
return customTypeConverter(value);
if (value instanceof ByteArray) {
/** @type object */
const ByteArrayClass = value.constructor;
if (ByteArrayClass.NAME in module)
return new module[ByteArrayClass.NAME](value.bytes);
return new ByteArrayClass(value.bytes);
}
return value;
};
const autoEncodeStrings = entity => {
Object.getOwnPropertyNames(entity).forEach(key => {
const value = entity[key];
if ('string' === typeof (value))
entity[key] = new TextEncoder().encode(value);
});
};
/**
* Rule based transaction factory.
* @note This class is not intended to be used directly.
*/
export default class RuleBasedTransactionFactory {
/**
* Creates a rule based transaction factory for use with catbuffer generated code.
* @param {object} module Catbuffer generated module.
* @param {function|undefined} typeConverter Type converter.
* @param {Map<string, function>|undefined} typeRuleOverrides Type rule overrides.
*/
constructor(module, typeConverter = undefined, typeRuleOverrides = undefined) {
/**
* @private
*/
this._module = module;
/**
* Tries to coerce a value to a more appropriate type.
* @param {object} value Original value.
* @returns {object} Type converted value.
* @private
*/
this._typeConverter = value => typeConverterFactory(this._module, typeConverter, value);
/**
* @private
*/
this._typeRuleOverrides = typeRuleOverrides || new Map();
/**
* Map of rule names to transform functions.
* @type Map<string, function>
*/
this.rules = new Map();
}
/**
* Looks up a class in the wrapped module.
* @param {string} name Class name.
* @returns {Constructable} Class type.
* @private
*/
_getModuleClass(name) {
return this._module[name];
}
/**
* Creates wrapper for SDK POD types.
* @param {string} name Class name.
* @param {Constructable} PodClass Class type.
*/
addPodParser(name, PodClass) {
if (this._typeRuleOverrides.has(PodClass)) {
this.rules.set(name, this._typeRuleOverrides.get(PodClass));
return;
}
this.rules.set(name, value => (value instanceof PodClass ? value : new PodClass(value)));
}
/**
* Creates flag type parser.
* @param {string} name Class name.
*/
addFlagsParser(name) {
const FlagsClass = this._getModuleClass(name);
const stringToEnum = buildEnumStringToValueMap(FlagsClass);
this.rules.set(name, flags => {
if ('string' === typeof (flags)) {
const enumArray = flags.split(' ').map(flagName => nameToEnumValue(stringToEnum, name, flagName));
return new FlagsClass(enumArray.map(flag => flag.value).reduce((x, y) => x | y));
}
if ('number' === typeof (flags) && Number.isInteger(flags))
return new FlagsClass(flags);
return flags;
});
}
/**
* Creates enum type parser.
* @param {string} name Class name.
*/
addEnumParser(name) {
const EnumClass = this._getModuleClass(name);
const stringToEnum = buildEnumStringToValueMap(EnumClass);
this.rules.set(name, enumValue => {
if ('string' === typeof (enumValue))
return nameToEnumValue(stringToEnum, name, enumValue);
if ('number' === typeof (enumValue) && Number.isInteger(enumValue))
return new EnumClass(enumValue);
return enumValue;
});
}
/**
* Creates struct parser (to allow nested parsing).
* @param {string} name Class name.
*/
addStructParser(name) {
const StructClass = this._getModuleClass(name);
this.rules.set(`struct:${name}`, structDescriptor => {
const structProcessor = this._createProcessor(structDescriptor);
const structValue = new StructClass();
const allTypeHints = buildTypeHintsMap(structValue);
structProcessor.setTypeHints(allTypeHints);
structProcessor.copyTo(structValue);
return structValue;
});
}
/**
* Creates array type parser, based on some existing element type parser.
* @param {string} name Class name.
*/
addArrayParser(name) {
const elementRule = this.rules.get(name);
if (!elementRule)
throw Error(`cannot create array type parser because element rule "${name}" is unknown`);
const elementName = name.replace(/^struct:/, '');
this.rules.set(`array[${elementName}]`, values => values.map(value => elementRule(value)));
}
/**
* Autodetects rules using reflection.
*/
autodetect() {
Object.getOwnPropertyNames(this._module).forEach(key => {
const cls = this._module[key];
if (Object.prototype.isPrototypeOf.call(BaseValue.prototype, cls.prototype))
this.addPodParser(key, cls);
});
}
/**
* Creates an entity from a descriptor using a factory.
* @param {function} factory Factory function.
* @param {object} descriptor Entity descriptor.
* @returns {object} Newly created entity.
*/
createFromFactory(factory, descriptor) {
const processor = this._createProcessor(descriptor);
const entityType = processor.lookupValue('type');
const entity = factory(entityType);
const allTypeHints = buildTypeHintsMap(entity);
processor.setTypeHints(allTypeHints);
processor.copyTo(entity, ['type']);
autoEncodeStrings(entity);
return entity;
}
/**
* Creates a transaction descriptor processor around a descriptor.
* @param {object} descriptor Transaction descriptor.
* @returns {TransactionDescriptorProcessor} Transaction descriptor processor.
* @private
*/
_createProcessor(descriptor) {
return new TransactionDescriptorProcessor(descriptor, this.rules, this._typeConverter);
}
}
// region type declarations
/**
* Constructable class type.
* @class
* @typedef {{new(...args: any[]): object}} Constructable
*/
// endregion