const Utils = require('./utils');
/**
* A parser an error cases can use to format a value extracted from an error.
*/
class CaseParser {
/**
* @param {string} name The name of the parser.
* @param {Object.<string, any> | Function} parser A function to parse a value or an
* `object` to map the value to
* something else.
* @throws {TypeError} If the `name` is not a `string`.
* @throws {TypeError} If the `parser` is not a `function` nor an `object`.
* @throws {Error} If the `parser` is an empty `object`.
*/
constructor(name, parser) {
/**
* The name of the parser.
*
* @type {string}
* @access protected
* @ignore
*/
this._name = this._validateName(name);
/**
* If the parser is a map, this is where the object will be stored.
*
* @type {?Object.<string, any>}
* @access protected
* @ignore
*/
this._map = null;
/**
* If the parser is a function, this is where the function will be saved.
*
* @type {?Function}
* @access protected
* @ignore
*/
this._function = null;
/**
* An object with properties to validate the parser type.
*
* @type {CaseParserType}
* @access protected
* @ignore
*/
this._parserType = this._validateParserType(parser);
if (this._parserType.map) {
/**
* @ignore
*/
this._map = parser;
} else {
/**
* @ignore
*/
this._function = parser;
}
}
/**
* Parse a value with the class parser.
* If the parser is a map and the value is an object with a `raw` property, which means
* it comes from another map parser, instead of generating a new value, the parser will
* merge the new value in top of the previous one.
*
* @param {*} value The value to parse.
* @returns {*} The result of the parsing.
*/
parse(value) {
let result;
if (this.is.map) {
const extend = Utils.isObject(value) && typeof value.raw !== 'undefined';
const useValue = extend ? value.raw : value;
if (this._map[useValue]) {
if (extend) {
result = { ...value, ...this._map[useValue] };
} else {
result = {
raw: useValue,
...this._map[useValue],
};
}
}
} else {
result = this._function(value);
}
return result || value;
}
/**
* An object with properties to validate the parser type.
*
* @type {CaseParserType}
*/
get is() {
return this._parserType;
}
/**
* The name of the parser.
*
* @type {string}
*/
get name() {
return this._name;
}
/**
* Validate the name of the parser.
*
* @param {string} name The name the class intends to use.
* @returns {string}
* @throws {TypeError} If the `name` is not a `string`.
* @access protected
* @ignore
*/
_validateName(name) {
if (typeof name !== 'string') {
throw new TypeError("The 'name' can only be a 'string'");
}
return name;
}
/**
* Validate the parser and generated an object with flags for the type.
*
* @param {Object.<string, any> | Function} parser The parser the class intends to
* save.
* @returns {CaseParserType}
* @throws {TypeError} If the parser is not a `function` nor an `object`.
* @throws {Error} If the `parser` is an empty `object`.
* @access protected
* @ignore
*/
_validateParserType(parser) {
const result = {
map: false,
function: false,
};
const isObject = Utils.isObject(parser);
if (isObject) {
const mapKeys = Object.keys(parser);
if (mapKeys.length) {
result.map = true;
} else {
throw new Error(
`'${this._name}': the parser is empty. It should include at least one item to map`,
);
}
} else if (typeof parser === 'function') {
result.function = true;
} else {
throw new TypeError(
`'${this._name}': the 'parser' parameter can only be a 'string' ` +
"or a 'function'",
);
}
return result;
}
}
module.exports = CaseParser;