const utils = require('./../performer/utils/utils.js')
const unroot = utils.unroot
const cvtError = require('./../error.js')
const LISTOFERRORS = cvtError.LISTOFERRORS
const FORMAT_INVALID = cvtError.FORMAT_INVALID
/**
* SchemaNode is rules for property
*
* @class
*/
function SchemaNode(rawSchema) {
const schema = { ...rawSchema }
this._private = {
origin: undefined,
validate: () => {},
coerce: (v) => v
}
/**
* Is the public object given in the second argument of [validateCallback](./SchemaNode.html#.validateCallback)
*
* @memberOf SchemaNode
* @type {SchemaNode.schema}
*/
this.attributes = schema
/**
* Is the public object given in the second argument of [validateCallback](./SchemaNode.html#.validateCallback)
*
* @name schema
* @memberOf SchemaNode
* @type {SchemaNode.schema}
*
* @class
*/
/**
* Attributes are: schema.default, schema.format, schema.doc, schema.env, schema.arg...
*
* @name {attributes}
* @memberOf SchemaNode.schema
*
* @instance
*/
// this.attributes.schema
/**
* Gets origin
*
* @name _cvtGetOrigin
* @memberOf SchemaNode.schema
*
* @instance
* @see SchemaNode.getOrigin
*
* @returns {string} value Getter name which origin of the value
*/
Object.defineProperty(this.attributes, '_cvtGetOrigin', {
value: () => {
return this.getOrigin()
}
})
/**
* Validates config property
*
* @name _cvtValidateFormat
* @memberOf SchemaNode.schema
*
* @see SchemaNode.validate
*
* @param {*} value Value of the property to validate
*/
Object.defineProperty(this.attributes, '_cvtValidateFormat', {
value: (value) => {
this.validate(value)
}
})
/**
* Coerces config property
*
* @name _cvtCoerce
* @memberOf SchemaNode.schema
*
* @instance
* @see SchemaNode.coerce
*
* @param {*} value Value to coerce
*
* @returns {*} value Returns coerced value
*
*/
Object.defineProperty(this.attributes, '_cvtCoerce', {
value: (value) => {
return this.coerce(value)
}
})
}
module.exports = SchemaNode
/**
* Validates value, will call [validateCallback](./SchemaNode.html#.validateCallback)
*
* @param {*} value Value of the property to validate
*/
SchemaNode.prototype.validate = function(value) {
const schema = this.attributes
const fullpath = unroot(this._private.fullpath)
try {
/**
* Validates function, should throw when value is not valid. Throws [LISTOFERRORS](./ZCUSTOMERROR.LISTOFERRORS.html)
* if you have several error (see [LISTOFERRORS](./ZCUSTOMERROR.LISTOFERRORS.html) example)
*
* Validate functions are declared with `addFormat`, and corresponding to `format` property.
*
* @callback SchemaNode.validateCallback
*
* @example
* Blueconfig.addFormat({
* name: 'int',
* coerce: (value) => (typeof value !== 'undefined') ? parseInt(value, 10) : value,
* validate: function(value, schema, fullpath) {
* if (Number.isInteger(value)) {
* throw new Error('must be an integer')
* }
* }
* })
*
*
* @param {*} value Value of the property to validate
* @param {schemaNode} schema schemaNode (= rules) of the property
* @param {string} fullpath Full property path
*
* @throws {Error} Throw [Error()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Error), `Error`s are transformed to `FORMAT_INVALID`.
* @throws {ZCUSTOMERROR.LISTOFERRORS} Throw [LISTOFERRORS()](./ZCUSTOMERROR.LISTOFERRORS.html) usefull if you validate a children key.
*
* @this {ConfigObjectModel}
*
* @see ZCUSTOMERROR.LISTOFERRORS
*/
this._private.validate(value, schema, fullpath)
} catch (err) {
if (err instanceof LISTOFERRORS) {
err.message = `${fullpath || 'root'}: Custom format "${schema.format}" tried to validate something and failed:`
err.errors.forEach((error, i) => {
err.message += `\n ${i + 1}) ${unroot(error.parent)}:` + ('\n' + error.why).replace(/(\n)/g, '$1 ')
})
throw err
} else {
// Origin of the value, is getter {name}.
const name = this._private.origin
const keyname = (schema[this._private.origin]) ? schema[name] : ''
const getter = { name, keyname }
throw new FORMAT_INVALID(fullpath, err.message, getter, value)
}
}
}
/**
* Converts a value to a specified function. Coerce functions are declared with `addFormat`, and corresponding to `format` property.
*
* @example
* Blueconfig.addFormat({
* name: 'int',
* coerce: (value) => (typeof value !== 'undefined') ? parseInt(value, 10) : value,
* validate: function(value) {
* assert(Number.isInteger(value), 'must be an integer')
* }
* })
*
* @param {*} value Value to coerce
*
* @returns {*} value Returns coerced value
*
* @memberof SchemaNode
*/
SchemaNode.prototype.coerce = function(value) {
return this._private.coerce(value)
}
/**
* Returns the name of the getter which gets the current value.
*
* @memberof SchemaNode
*
* @returns {string} value Getter name which origin of the value
*/
SchemaNode.prototype.getOrigin = function() {
return this._private.origin
}