types/OneOf.js

const TranscodableType = require('../Type');
const Exceptions = require('../exceptions');
const Varint = require('./Varint');

/**
 * @class
 * An encoder that allows only one definition to be encoded at a time.
 * @description Encodes a _Varint_ with an identifier and encoded data.
 * @augments TranscodableType
 */
class OneOf extends TranscodableType {
	/**
	 * @constructor
	 * @param {Object} descriptor A map of keys and {@link TranscodableType}. When encoding only one key can be set.
	 */
	constructor(descriptor){
		super();
		this.descriptor = descriptor;
		this.id_map = new Array(Object.keys(descriptor).length);
		this.rev_map = new Array(this.id_map.length);
		Object.keys(descriptor).forEach((name,index) => {
			this.id_map[name] = index;
			this.rev_map[index] = name;
		});
		this.Varint = new Varint();
	}
	compiledEncoder(source_var, alloc_tmp){
		return `
		const keys = Object.keys(${source_var});
		if(keys.length > 1)
			throw new Exceptions.AmbiguousObject('OneOf fields only allow a single key, provided '+keys.length);
		const key = keys[0];
		switch(key){
			${Object.keys(this.descriptor).map(desc => {
				return `
				case '${desc}':
					${this.Varint.compiledEncoder(this.id_map[desc])}
					${this.descriptor[desc].compiledEncoder(`${source_var}.${desc}`, alloc_tmp)}
				break;
				`
			}).join('')}
			default:
				throw new Exceptions.InvalidEncodeValue('Unknown OneOf key'+key);
		}
		`
	}
	compiledDecoder(target_var, alloc_tmp){
		const tmp = alloc_tmp();
		return `
		${this.Varint.compiledDecoder(tmp)}
		${target_var} = {};
		switch(${tmp}){
			${Object.keys(this.descriptor).map(desc => {
				return `
				case ${this.id_map[desc]}:
					${this.descriptor[desc].compiledDecoder(`${target_var}.${desc}`, alloc_tmp)}
				break;
				`
			}).join('')}
			default:
				throw new Exceptions.InvalidDecodeBuffer('Unknown OneOf id '+${tmp});
		}
		`
	}
}

module.exports = OneOf;