enolib
Version:
The eno standard library
216 lines (193 loc) • 8.34 kB
JavaScript
const { errors } = require('../errors/validation.js');
const { DOCUMENT, LIST_ITEM } = require('../constants.js');
class ElementBase {
constructor(context, instruction, parent = null) {
this._context = context;
this._instruction = instruction;
this._parent = parent;
}
_comment(loader, required) {
this._touched = true;
const comment = this._context.comment(this._instruction);
if(comment === null) {
if(required)
throw errors.missingComment(this._context, this._instruction);
return null;
}
if(loader === null)
return comment;
try {
return loader(comment);
} catch(message) {
throw errors.commentError(this._context, message, this._instruction);
}
}
_key() {
switch(this._instruction.type) {
case DOCUMENT: return null;
case LIST_ITEM: return this._instruction.parent.key;
default: return this._instruction.key;
}
}
/**
* Constructs and returns a {@link ValidationError} with the supplied message in the context of this element's comment.
*
* Note that this only *returns* an error, whether you want to just use its
* metadata, pass it on or actually throw the error is up to you.
*
* @param {string|function(comment: string): string} message A message or a function that receives the element's comment and returns the message.
* @return {ValidationError} The requested error.
*/
commentError(message) {
return errors.commentError(
this._context,
typeof message === 'function' ? message(this._context.comment(this._instruction)) : message,
this._instruction
);
}
/**
* Constructs and returns a {@link ValidationError} with the supplied message in the context of this element.
*
* Note that this only *returns* an error, whether you want to just use its
* metadata, pass it on or actually throw the error is up to you.
*
* @param {string|function(element: Element): string} message A message or a function that receives the element and returns the message.
* @return {ValidationError} The requested error.
*/
error(message) {
return errors.elementError(
this._context,
typeof message === 'function' ? message(this) : message, // TODO: *this* is problematic in this context - what is it?
this._instruction
);
}
/**
* Passes the key of this {@link Element} through the provided loader, returns the result and touches the element.
* Throws a {@link ValidationError} if an error is intercepted from the loader.
*
* @example
* // Given a field with the key 'foo' ...
*
* field.key(key => key.toUpperCase()); // returns 'FOO'
* field.key(key => { throw 'You shall not pass!'; }); // throws an error based on the intercepted error message
*
* @param {function(key: string): any} loader A loader function taking the key as a `string` and returning any other type or throwing a `string` message.
* @return {any} The result of applying the provided loader to this {@link Element}'s key.
* @throws {ValidationError} Thrown when an error from the loader is intercepted.
*/
key(loader) {
this._touched = true;
try {
return loader(this._key());
} catch(message) {
throw errors.keyError(this._context, message, this._instruction);
}
}
/**
* Constructs and returns a {@link ValidationError} with the supplied message in the context of this element's key.
*
* Note that this only *returns* an error, whether you want to just use its
* metadata, pass it on or actually throw the error is up to you.
*
* @param {string|function(key: string): string} message A message or a function that receives the element's key and returns the message.
* @return {ValidationError} The requested error.
*/
keyError(message) {
return errors.keyError(
this._context,
typeof message === 'function' ? message(this._key()) : message,
this._instruction
);
}
/**
* Passes the associated comment of this {@link Element} through the provided loader, returns the result and touches the element.
* The loader is only invoked if there is an associated comment, otherwise `null` is returned directly.
* Throws a {@link ValidationError} if an error is intercepted from the loader.
*
* @example
* // Given a field with an associated comment 'foo' ...
*
* field.optionalComment(comment => comment.toUpperCase()); // returns 'FOO'
* field.optionalComment(comment => { throw 'You shall not pass!'; }); // throws an error based on the intercepted error message
*
* // Given a field with no associated comment ...
*
* field.optionalComment(comment => comment.toUpperCase()); // returns null
* field.optionalComment(comment => { throw 'You shall not pass!'; }); // returns null
*
* @param {function(value: string): any} loader A loader function taking the comment as `string` and returning any other type or throwing a `string` message.
* @return {?any} The result of applying the provided loader to this {@link Element}'s comment, or `null` when none exists.
* @throws {ValidationError} Thrown when an error from the loader is intercepted.
*/
optionalComment(loader) {
return this._comment(loader, false);
}
/**
* Returns the associated comment of this {@link Element} as a `string` and touches the element.
* Returns `null` if there is no associated comment.
*
* @return {?string} The associated comment of this {@link Element} as a `string`, or `null`.
*/
optionalStringComment() {
return this._comment(null, false);
}
/**
* TODO: Adapt this documentation for the new generic one fits all implementation on Element
*
* For fields and fieldset entries returns an `object` of the form `{ key: 'value' }`, for list items returns the value as a `string` or null when empty.
*
* @return {object|string|null} The value of this {@link Field} as a `string` or the whole element represented as an `object`.
*/
raw() {
return this._context.raw(this._instruction);
}
/**
* Passes the associated comment of this {@link Element} through the provided loader, returns the result and touches the element.
* The loader is only invoked if there is an associated comment, otherwise a {@link ValidationError} is thrown directly.
* Also throws a {@link ValidationError} if an error is intercepted from the loader.
*
* @example
* // Given a field with an associated comment 'foo' ...
*
* field.requiredComment(comment => comment.toUpperCase()); // returns 'FOO'
* field.requiredComment(comment => { throw 'You shall not pass!'; }); // throws an error based on the intercepted error message
*
* // Given a field with no associated comment ...
*
* field.requiredComment(comment => comment.toUpperCase()); // throws an error stating that a required comment is missing
* field.requiredComment(comment => { throw 'You shall not pass!'; }); // throws an error stating that a required comment is missing
*
* @param {function(value: string): any} loader A loader function taking the comment as `string` and returning any other type or throwing a `string` message.
* @return {any} The result of applying the provided loader to this {@link Element}'s comment.
* @throws {ValidationError} Thrown when there is no associated comment or an error from the loader is intercepted.
*/
requiredComment(loader) {
return this._comment(loader, true);
}
/**
* Returns the associated comment of this {@link Element} as a `string` and touches the element.
* Throws a {@link ValidationError} if there is no associated comment.
*
* @return {string} The associated comment of this {@link Element} as a `string`.
* @throws {ValidationError} Thrown when there is no associated comment.
*/
requiredStringComment() {
return this._comment(null, true);
}
/**
* Returns the key of this {@link Element} as a `string` and touches the element.
*
* @return {string} The key of this {@link Element} as a `string`.
*/
stringKey() {
this._touched = true;
return this._key();
}
/**
* Touches this {@link Element} and all elements below it.
*/
touch() {
this._touched = true;
}
}
exports.ElementBase = ElementBase;