'use strict';
import { ERROR } from './Constants';
export default class Utils {
/**
* This class contains some useful utility methods.<br />
* The Utils class cannot be instantiated (its constructor will throw if called), all its methods are static.
*
* @constructor
*/
constructor() {
throw new Error('The Utils class cannot be instantiated.');
}
/**
* Check is the `parameter` is not undefined and not null.
*
* @param parameter {*} The parameter to check.
*
* @return {Boolean} True if `parameter` is not undefined and not null.
*/
static isDefined(parameter) {
return typeof parameter !== 'undefined' && parameter !== null;
}
/**
* Asserts that the given `parameter` is present (read: truthy).<br />
* This method is intended to be called when you need to validate input parameters of functions.
*
* @param parameter {*} The parameter to validate.
* @param name {String} The name of the parameter, as given to the calling function.
* This is used to format the error message contained by the thrown {@link Error}.
*
* @return {*} The validated parameter, as-is.
*
* @throws {Error} If the parameter is not defined.
*/
static assertRequiredParameterIsPresent(parameter, name) {
if (!Utils.isDefined(parameter)) {
throw new Error('The "' + name + '" parameter is required.');
}
return parameter;
}
/**
* Asserts that the given `parameter` is present and is an object.<br />
* This method is intended to be called when you need to validate input parameters of functions.
*
* @param parameter {*} The parameter to validate.
* @param name {String} The name of the parameter, as given to the calling function.
* This is used to format the error message contained by the thrown {@link Error}.
*
* @return {*} The validated parameter, as-is.
*
* @throws {Error} If the parameter is not defined or is not an object.
*/
static assertRequiredParameterIsObject(parameter, name) {
Utils.assertRequiredParameterIsPresent(parameter, name);
if (typeof parameter !== 'object' || Array.isArray(parameter)) {
throw new Error('The "' + name + '" parameter is not an object.');
}
return parameter;
}
/**
* Asserts that the given `parameter` is present and has the expected type.<br />
* This method is intended to be called when you need to validate input parameters of functions.
* Examples:
* assertRequiredParameterHasType(5, 'name', 'number') => returns 5
* assertRequiredParameterHasType({}, 'name', CustomClass) => throws an Error
*
* @param parameter {*} The parameter to validate.
* @param name {String} The name of the parameter, as given to the calling function.
* This is used to format the error message contained by the thrown {@link Error}.
* @param type {String|Type} The expected type of the parameter.
*
* @return {*} The validated parameter, as-is.
*
* @throws {Error} If the parameter is not defined or is not an object.
*/
static assertRequiredParameterHasType(parameter, name, type) {
Utils.assertRequiredParameterIsPresent(parameter, name);
if (typeof type === 'string') {
if (typeof parameter !== type) {
throw new Error('The "' + name + '" parameter has not the expected type: ' + type);
}
} else if (!(parameter instanceof type)) {
throw new Error('The "' + name + '" parameter has not the expected type: ' + type);
}
return parameter;
}
/**
* Asserts that the given `parameter` is an {@link Array} with a minimum length.<br />
* This method is intended to be called when you need to validate input parameters of functions.
*
* @param parameter {*} The parameter to validate.
* @param name {String} The name of the parameter, as given to the calling function.
* This is used to format the error message contained by the thrown {@link Error}.
* @param [length=0] {Number} The minimum required length of the array.
*
* @return {*} The validated parameter, as-is.
*
* @throws {Error} If the parameter is not an array of does not have the minimum length.
*/
static assertRequiredParameterIsArrayWithMinimumLength(parameter, name, length) {
if (!Array.isArray(parameter)) {
throw new Error('The "' + name + '" parameter must be an Array.');
}
if (length && parameter.length < length) {
throw new Error('The "' + name + '" parameter must have at least ' + length + ' element(s).');
}
return parameter;
}
/**
* Asserts that the given `data` is a valid JMAP response.<br />
* This method is intended to be called by instances of {@link Client}, or by any other object making JMAP requests,
* when validation of the response is required.<br />
* <br />
* The following checks are made by this method:
* * `data` is defined and is an array
* * `data` has one or more elements, and all elements are arrays
* * `data[0][0]` is either
* * the expected response string (computed with the help of the `request` parameter)
* * 'error'
* * an unknown response
* * `data[0][1]` exists
*
* @param request {String} The JMAP request to check the response for. This should be a valid JMAP request name.
* @param data {*} The JMAP response to validate.
*
* @return {*} The data, as-is, if it is detected as a valid JMAP response.
*
* @throws {Error} If the received data is not a valid JMAP response.
*/
static assertValidJMAPResponse(request, data) {
function allArrayElementsAreArray(array) {
return array.filter(function(element) {
return !Array.isArray(element);
}).length === 0;
}
if (!data || !Array.isArray(data)) {
throw new Error(`Expected an array as the JMAP response for a "${request}" request.`);
}
if (data.length === 0 || !allArrayElementsAreArray(data)) {
throw new Error(`Expected an array of exactly 1 array element as the JMAP response for a "${request}" request.`);
}
let response = data[0][0],
expectedResponse = Utils._expectedResponseFor(request);
if (response !== ERROR && (expectedResponse && response !== expectedResponse)) {
throw new Error(`Expected "${expectedResponse}" as the JMAP response for a "${request}" request, but got "${response}".`);
}
if (!data[0][1]) {
throw new Error(`The JMAP response for a "${request}" request should return some data.`);
}
return data;
}
/**
* Capitalizes the given {@link String}, that is, returns the same string with the first character in uppercase.<br />
* If `undefined`, `null`, the _empty string_ or something else that a string is given, the argument is returned as-is.
*
* @param str {String} The {@link String} to capitalize.
*
* @return {String} The capitalized {@link String}.
*/
static capitalize(str) {
if (!str || typeof str !== 'string') {
return str;
}
return str.charAt(0).toUpperCase() + str.substring(1);
}
/**
* Fills a URI template by substituting variables by their corresponding values.<br />
* This supports Level 1 URI templates *only*, as per [this RFC](https://tools.ietf.org/html/rfc6570#section-1.2).
*
* @param uri {String} The URI template to fill.
* @param parameters {Object} A hash of name/value pairs used for variables substitution.
*
* @return {String} The filled URI template.
*/
static fillURITemplate(uri, parameters) {
Utils.assertRequiredParameterIsPresent(uri, 'uri');
if (!parameters) {
return uri;
}
return uri.replace(/{(.+?)}/g, function(match, variable) {
let value = parameters[variable];
return value ? encodeURIComponent(value) : match;
});
}
/**
* Appends a query parameter to an existing URL, taking care of existing query parameters.<br />
* This method returns `uri` as-is if `key` or `value` is not defined.
*
* @param uri {String} The URI to modify.
* @param key {String} The name of the parameter to append.
* @param value {String} The value of the parameter to append.
*
* @returns {String} The modified URI.
*/
static appendQueryParameter(uri, key, value) {
if (!uri || !key || !value) {
return uri;
}
return uri + (uri.indexOf('?') > -1 ? '&' : '?') + encodeURIComponent(key) + '=' + encodeURIComponent(value);
}
/**
* Returns the value at _index_ in the given `Array`, or the default value if the array is undefined, null,
* if _index_ is negative or there is not enough elements in the array.
*
* @param array The `Array` to get the value from.
* @param index The index of the desired value.
* @param defaultValue The default value to return if the element cannot be found in the array.
*
* @returns {*} The found value or the given default.
*/
static nthElementOrDefault(array, index, defaultValue) {
if (Array.isArray(array) && index >= 0 && array.length > index) {
return array[index];
}
return defaultValue;
}
static _jsonArrayToModelList(jmap, Model, array, filter) {
if (!Array.isArray(array)) {
return [];
}
if (filter) {
array = array.filter(filter);
}
return array.map(Model.fromJSONObject.bind(null, jmap));
}
static _nullOrNewInstance(value, Model) {
return (value && new Model(value)) || null;
}
static _expectedResponseFor(request) {
return ({
getAccounts: 'accounts',
getMailboxes: 'mailboxes',
getMessageList: 'messageList',
getThreads: 'threads',
getMessages: 'messages',
setMessages: 'messagesSet',
setMailboxes: 'mailboxesSet',
getVacationResponse: 'vacationResponse',
setVacationResponse: 'vacationResponseSet'
})[request];
}
}