"use strict";
// Third Party
const include = require("include")(__dirname);
const stream = require("lodash/fp");
// Third Party Aliases
const concat = stream.concat;
const curry = stream.curry;
const each = stream.each;
const filter = stream.filter;
const find = stream.find;
const flow = stream.flow;
const get = stream.get;
const isEqual = stream.isEqual;
const isUndefined = stream.isUndefined;
const map = stream.map;
const negate = stream.negate;
const reduce = stream.reduce;
// Project
const invokeIn = include("src/invokeIn");
const invokeWith = include("src/invokeWith");
/**
* The {@link Validation} type is intended for validating values and aggregating failures. It is a disjunction
* similar to <code>Either</code>. The key difference of the {@link Validation} type is the focus on failure
* aggregation as opposed to failing once and ignoring all other failures. Much like <code>Either</code>,
* {@link Validation} is right-biased.
* @param {*} value - Value to wrap.
* @return {Validation} {@link Validation} wrapped <code>value</code>.
* @example <caption>Via <code>new</code></caption>
*
* const v1 = new Success(value);
* const v2 = new Failure(message);
*
* @example <caption>Via function</caption>
*
* const v3 = Success.from(value);
* const v4 = Failure.from(message);
*
* @example <caption>Via validation function</caption>
*
* const isEmpty = require("lodash/fp/isEmpty");
* const isString = require("lodash/fp/isString");
* const Validation = require("lodash-fantasy/data/Validation");
*
* function validateStringPresence(value) {
* return isString(value) && !isEmpty(value) ?
* Validation.Success.from(value) :
* Validation.Failure.from("value should be a non-empty string");
* }
*
* module.exports = validateStringPresence;
*
* @example <caption>Via abstract validation rule</caption>
*
* const Validation = require("lodash-fantasy/data/Validation");
*
* module.exports = (condition, value, message) => condition(value) ?
* Validation.Success.from(value) :
* Validation.Failure.from(message(value)); // Pass value into the message for possible reference
*/
class Validation {
/**
* @static
* @property {Failure} Failure - Validation failure.
*/
static get Failure() {
return Failure;
}
/**
* @static
* @property {Success} Success - Validation success.
*/
static get Success() {
return Success;
}
/**
* Returns a {@link Validation} that resolves all of the validations in the collection into a single validation.
* Unlike <code>Promise</code>, {@link Validation.all} aggregates all of the failures into a single instance of
* {@link Validation}. However, like <code>Promise</code>, {@link Validation.all} collects all of the values
* for successes when <em>all</em> items in the collection are a {@link Success}.
* @static
* @member
* @param {Validation[]} validations - Collection of validations.
* @return {Validation} A {@link Validation} representing all {@link Success} or {@link Failure}
* values.
* @example
*
* const v1 = validationPropertyAIn(context1);
* // => Success(context1)
*
* const v2 = validationPropertyBIn(context2);
* // => Success(context2)
*
* const v3 = validationPropertyCIn(context3);
* // => Failure("A failure.")
*
* const v4 = validationPropertyDIn(context4);
* // => Failure("B failure.")
*
* Validation.all([v1, v2]);
* // => Success([context1, context2])
*
* Validation.all([v1, v2, v3]);
* // => Failure(["A failure"])
*
* Validation.all([v1, v2, v3, v4]);
* // => Failure(["A failure", "B failure"])
*/
static all(validations) {
return find(Validation.isFailure, validations) ?
reduce(Validation.concat, Success.empty(), validations) :
Success.of(stream(validations).map(get("value")).reduce(concat, []));
}
/**
* Returns the first {@link Success} in the collection or a single {@link Failure} for all failures.
* @static
* @member
* @param {Validation[]} validations - Collection of validations.
* @return {Validation} First {@link Success} or reduced {@link Failure}s.
* @example
*
* const v1 = validationPropertyAIn(context1);
* // => Success(context1)
*
* const v2 = validationPropertyBIn(context2);
* // => Success(context2)
*
* const v3 = validationPropertyCIn(context3);
* // => Failure("A failure.")
*
* const v4 = validationPropertyDIn(context4);
* // => Failure("B failure.")
*
* Validation.any([v1, v2]);
* // => Success(context1)
*
* Validation.any([v1, v2, v3]);
* // => Failure(["A failure"])
*
* Validation.any([v1, v2, v3, v4]);
* // => Failure(["A failure", "B failure"])
*/
static any(validations) {
return find(Validation.isSuccess, validations) ||
reduce(Validation.concat, Success.empty(), validations);
}
/**
* Creates an empty {@link Success}.
* @static
* @member
* @return {Success} Empty {@link Success} instance.
* @example
*
* const v1 = Validation.empty();
* // => Success()
*/
static empty() {
return new Success();
}
/**
* Creates a new {@link Validation} from a <code>value</code>. If the <code>value</code> is already a
* {@link Validation} instance, the <code>value</code> is returned unchanged. Otherwise, a new {@link Success} is
* created with the <code>value</code>.
* @static
* @member
* @param {*} value - Value to wrap in a {@link Validation}.
* @return {Validation} {@link Validation} when is the <code>value</code> already wrapped or
* {@link Success} wrapped <code>value</code>.
*
* Validation.from();
* // => Success()
*
* Validation.from(true);
* // => Success(true)
*
* Validation.from(Success.from(value));
* // => Success(value)
*
* Validation.from(Failure.from("Error message"));
* // => Failure(["Error message"])
*/
static from(value) {
return this.isValidation(value) ? value : this.of(value);
}
/**
* Determines whether or not the value is a {@link Failure}.
* @static
* @member
* @param {*} value - Value to check.
* @return {Boolean} <code>true</code> for {@link Failure}; <code>false</code> for {@link Success}.
* @example
*
* isFailure();
* // => false
*
* isFailure(null);
* // => false
*
* isFailure(Success.from(0));
* // => false
*
* isFailure(Failure.from("Error"));
* // => true
*/
static isFailure(value) {
return value instanceof Failure;
}
/**
* Determines whether or not the value is a {@link Success}.
* @static
* @member
* @param {*} value - Value to check.
* @return {Boolean} <code>true</code> for {@link Success}; <code>false</code> for {@link Failure}.
* @example
*
* isSuccess();
* // => false
*
* isSuccess(null);
* // => false
*
* isSuccess(Success.from(0));
* // => true
*
* isSuccess(Failure.from("Error"));
* // => false
*/
static isSuccess(value) {
return value instanceof Success;
}
/**
* Determines whether or not the value is a {@link Validation}.
* @static
* @member
* @param {*} value - Value to check.
* @return {Boolean} <code>true</code> for {@link Validation}; <code>false</code> for anything else.
* @example
*
* isValidation();
* // => false
*
* isValidation(null);
* // => false
*
* isValidation(Success.from(0));
* // => true
*
* isValidation(Failure.from("Error"));
* // => true
*/
static isValidation(value) {
return value instanceof Validation;
}
/**
* Wraps the <code>value</code> in a {@link Success}. No parts of <code>value</code> are checked.
* @static
* @member
* @param {*} value - Value to wrap.
* @return {Success} {@link Success} wrapped <code>value</code>.
* @example
*
* Validation.of();
* // => Success()
*
* Validation.of(true);
* // => Success(true)
*
* Validation.of(Success.from(value));
* // => Success(Success(value))
*
* Validation.of(Failure.from("Error message"));
* // => Success(Failure(["Error message"]))
*/
static of(value) {
return new Success(value);
}
/**
* Tries to invoke a <code>supplier</code>. The result of the <code>supplier</code> is returned in a {@link Success}.
* If an exception is thrown, the error is returned in a {@link Failure}. The <code>function</code> takes no
* arguments.
* @static
* @member
* @param {Supplier} supplier - Function to invoke.
* @return {Validation} {@link Success} wrapped supplier result or {@link Failure} wrapped <code>error</code>.
* @example
*
* Validation.try(normalFunction);
* // => Success(returnValue)
*
* Validation.try(throwableFunction);
* // => Failure([error])
*/
static try(method) {
try {
return Success.from(method());
} catch (error) {
return Failure.from(error);
}
}
constructor(value) {
this.value = value;
}
/**
* Applies the function contained in the instance of a {@link Success} to the value contained in the provided
* {@link Success}, producing a {@link Success} containing the result. If the instance is a {@link Failure}, the
* result is the {@link Failure} instance. If the instance is a {@link Success} and the provided validation is
* {@link Failure}, the result is the provided {@link Failure}.
* @abstract
* @function ap
* @memberof Validation
* @instance
* @param {Validation} other - Value to apply to the function wrapped in the {@link Success}.
* @return {Validation} {@link Success} wrapped applied function or {@link Failure}.
* @example <caption>Success#ap</caption>
*
* const createPerson = curryN(4, Person.create); // Person.create(name, birthdate, address, email)
*
* Success.from(createPerson) // => Success(createPerson)
* .ap(validate(name)) // => Success(name)
* .ap(validate(birthdate)) // => Success(birthdate)
* .ap(validate(address)) // => Success(address)
* .ap(validate(email)) // => Success(email)
* .ifSuccess(console.log) // => Log Person.create() response
* .orElse(each(console.error)) // => Logs first error since #ap short circuits after the first Failure
*/
/**
* Transforms a {@link Validation} by applying the first function to the contained value for a {@link Failure} or the
* second function for a {@link Success}. The result of each map is wrapped in the corresponding type.
* @abstract
* @function bimap
* @memberof Validation
* @instance
* @param {Function} failureMap - Map to apply to the {@link Failure}.
* @param {Function} successMap - Map to apply to the {@link Success}.
* @return {Validation} {@link Validation} wrapped value mapped with the corresponding mapping function.
* @example
*
* validateRequest(request)
* .bimap(toBadRequestResponse, PersonModel.create)
* // ... other actions in workflow
*/
/**
* Applies the provided function to the value contained for a {@link Success}. The function should return the value
* wrapped in a {@link Validation}. If the instance is a {@link Failure}, the function is ignored and then instance is
* returned unchanged.
* @abstract
* @function chain
* @memberof Validation
* @instance
* @param {Chain.<Validation>} method - The function to invoke with the value.
* @return {Validation} {@link Validation} wrapped value returned by the provided <code>method</code>.
* @example <caption>Success#chain</caption>
*
* const person = { ... };
* const validateResponse = response => HttpStatus.isSuccess(response.statusCode) ?
* Success(response) :
* Failure(response.statusMessage);
*
* const createPerson = flow(Person.create, validateResponse); // Expects instance of Person
*
* const validations = [
* validatePersonName(person), // => Success(person)
* validatePersonBirthdate(person), // => Success(person)
* validatePersonAddress(person), // => Failure([error1])
* validatePersonEmail(person) // => Failure([error2])
* ];
*
* Validation.reduce(Validation.concat, Success.empty(), validations) // => Validation<Person>
* .chain(createPerson) // => Validation<Response>
* .ifSuccess(doSomethingWithResponse)
* .orElse(each(console.error)); // Log all errors
*/
/**
* Concatenates another {@link Validation} instance with the current instance.
* @abstract
* @function concat
* @memberof Validation
* @instance
* @param {Validation} other - Other {@link Validation} to concatenation.
* @return {Validation} Concatenated validations.
* @example <caption>Empty Success with Empty Success</caption>
*
* Success.empty().concat(Success.empty());
* // => Success.empty()
*
* @example <caption>Empty Success with Success</caption>
*
* Success.empty().concat(Success.from(value));
* // => Success(value)
*
* @example <caption>Success with Empty Success</caption>
*
* Success.from(value).concat(Success.empty());
* // => Success(value)
*
* @example <caption>Success1 with Success2</caption>
*
* Success.from(value1).concat(Success.from(value2));
* // => Success(value1)
*
* @example <caption>Any Success with Failure</caption>
*
* anySuccess.concat(Failure.from(error));
* // => Failure([error])
*
* @example <caption>Empty Failure with Any Success</caption>
*
* Failure.from().concat(anySuccess);
* // => Failure([])
*
* @example <caption>Failure with Any Success</caption>
*
* Failure.from(error).concat(Success);
* // => Failure([error])
*
* @example <caption>Empty Failure with Empty Failure</caption>
*
* Failure.from().concat(Failure.from());
* // => Failure([])
*
* @example <caption>Empty Failure with Failure</caption>
*
* Failure.from().concat(Failure.from(error));
* // => Failure([error])
*
* @example <caption>Failure with Failure</caption>
*
* Failure.from(error1).concat(Failure.from(error2));
* // => Failure([error1, error2])
*/
/**
* Determines whether or not the <code>other</code> is equal in value to the current (<code>this</code>). This is
* <strong>not</strong> a reference check.
* @param {*} other - Other value to check.
* @return {Boolean} <code>true</code> if the two validations are equal; <code>false</code> if not equal.
* @example <caption>Reflexivity</caption>
*
* v1.equals(v1) === true;
* // => true
*
* @example <caption>Symmetry</caption>
*
* v1.equals(v2) === v2.equals(v1);
* // => true
*
* @example <caption>Transitivity</caption>
*
* (v1.equals(v2) === v2.equals(v3)) && v1.equals(v3)
* // => true
*/
equals(other) {
return isEqual(this, other);
}
/**
* Extends the validation. This is used for workflow continuation where the context has shifted.
* @abstract
* @function extend
* @memberof Validation
* @instance
* @param {Extend.<Validation>} - method - The function to invoke with the value.
* @return {Validation}
* @example <caption>Workflow continuation</caption>
*
* // Workflow from makeRequest.js
* const makeRequest = requestOptions => requestAsPromise(requestOptions)
* .then(Success.from)
* .catch(Failure.from);
*
* // Workflow from savePerson.js
* const savePerson = curry((requestOptions, validatedPerson) => {
* return validatedPerson
* .map(Person.from)
* .map(person => set("body", person, requestOptions))
* .map(makeRequest);
* });
*
* // Workflow from processResponse.js
* const processResponse = validatedResponse => validatedResponse
* .ifFailure(console.error)
* .ifSuccess(console.log);
*
* validatePerson(person)
* .extend(savePerson({ method: "POST" }))
* .extend(processResponse);
*/
/**
* Applies the provided function to the value contain for a {@link Failure}. Any return value from the function is
* ignored. If the instance is a {@link Success}, the function is ignored and the instance is returned.
* @abstract
* @function ifFailure
* @memberof Validation
* @instance
* @param {Consumer} method - The function to invoke with the value.
* @return {Validation} Current instance.
* @example <caption>Success#ifFailure</caption>
*
* Success.from(value).ifFailure(doSomething); // void
* // => Success(value)
*
* @example <caption>Failure#ifFailure</caption>
*
* Failure.from(error).ifFailure(doSomething); // doSomething([error])
* // => Failure([error])
*/
/**
* Applies the provided function to the value contain for a {@link Success}. Any return value from the function is
* ignored. If the instance is a {@link Failure}, the function is ignored and the instance is returned.
* @abstract
* @function ifSuccess
* @memberof Validation
* @instance
* @param {Consumer} method - The function to invoke with the value.
* @return {Validation} Current instance.
* @example <caption>Success#ifSuccess</caption>
*
* Success.from(value).ifSuccess(doSomething); // doSomething(value)
* // => Success(value)
*
* @example <caption>Failure#ifSuccess</caption>
*
* Failure.from(error).ifSuccess(doSomething); // void
* // => Failure([error])
*/
/**
* Determines whether or not the instance is a {@link Failure}.
* @return {Boolean} <code>true</code> if the instance is a {@link Failure}; <code>false</code> is not.
* @example <caption>Success#isFailure</caption>
*
* Success.from(value).isFailure();
* // => false
*
* @example <caption>Failure#isFailure</caption>
*
* Failure.from(error).isFailure();
* // => true
*/
isFailure() {
return this instanceof Failure;
}
/**
* Determines whether or not the instance is a {@link Success}.
* @return {Boolean} <code>true</code> if the instance is a {@link Success}; <code>false</code> is not.
* @example <caption>Success</caption>
*
* Success.from(value).isFailure();
* // => true
*
* @example <caption>Failure#isSuccess</caption>
*
* Failure.from(error).isFailure();
* // => false
*/
isSuccess() {
return this instanceof Success;
}
/**
* Applies the provided function to the value contained for a {@link Success} which is, in turn, wrapped in a
* {@link Success}. If the instance is a {@link Failure}, the function is ignored and then instance is returned
* unchanged.
* @abstract
* @function map
* @memberof Validation
* @instance
* @param {Function} method - The function to invoke with the value.
* @return {Validation} {@link Validation} wrapped value mapped with the provided <code>method</code>.
* @example
*
* // Using lodash/fp/flow and sort
* Success.from([1, 3, 2]).map(flow(sort, join(", ")));
* // => Success("1, 2, 3")
*
* Failure.from(error).map(flow(sort, join(", ")));
* // => Failure([error])
*/
/**
* @see Validation.of
*/
of(value) {
return Validation.of(value);
}
/**
* Returns the value if the instance is a {@link Success} otherwise returns the value supplied if the instance is a
* {@link Failure}.
* @abstract
* @function orElse
* @memberof Validation
* @instance
* @param {*} value - Value to use if the instace is a {@link Failure}.
* @return {*}
* @example <caption>Success#orElse</caption>
*
* Success.from(value).orElse(otherValue);
* // => value
*
* @example <caption>Failure#orElse</caption>
*
* Failure.from(error).orElse(otherValue);
* // => otherValue
*/
/**
* Return the value if the instance is a {@link Success} otherwise returns the value from the function provided.
* @abstract
* @function orElseGet
* @memberof Validation
* @instance
* @param {Supplier} method - The function supplying the optional value.
* @return {*}
* @example <caption>Success#orElseGet</caption>
*
* Success.from(value).orElseGet(getOtherValue);
* // => value
*
* @example <caption>Failure#orElseGet</caption>
*
* Failure.from().orElseGet(getOtherValue);
* // => otherValue
*/
/**
* Applies the provided function to the value contain for a {@link Failure} and throws the resulting
* <code>Error</code>. If the instance is a {@link Success}, the function is ignored and the instance is returned.
* @abstract
* @function orElseThrow
* @memberof Validation
* @instance
* @param {Function} method - The function to invoke with the value.
* @throws {Error} returned by the provided function.
* @example <caption>Success#orElseThrow</caption>
*
* Success.from(value).orElseThrow(createException); // void
* // => Success(value)
*
* @example <caption>Failure#orElseThrow</caption>
*
* Failure.from(error).orElseThrow(createException); // throw createException([error])
*/
/**
* Converts the validation to an {@link Either} using the provided <code>Either</code> implementation. {@link Success}
* becomes a {@link Right} and {@link Failure} becomes a {@link Left}.
* @abstract
* @function toEither
* @memberof Validation
* @instance
* @param {Either} either - Either implementation.
* @return {Either} {@link Either} wrapped <code>value</code>.
* @example <caption>Success#toEither</caption>
*
* Success.from(value).toEither(Either);
* // => Either.Right(value);
*
* @example <caption>Failure#toEither</caption>
*
* Failure.from(error).toEither(Either);
* // => Either.Left([error]);
*/
/**
* Converts the validation to an {@link Maybe} using the provided <code>Maybe</code> implementation. {@link Success}
* becomes a {@link Just} and {@link Failure} becomes a {@link Nothing}.
* @abstract
* @function toMaybe
* @memberof Validation
* @instance
* @param {Maybe} maybe - Maybe implementation.
* @return {Maybe} {@link Maybe} wrapped <code>value</code>.
* @example <caption>Success#toMaybe</caption>
*
* Success.from(value).toMaybe(Maybe);
* // => Maybe.Just(value);
*
* @example <caption>Failure#toMaybe</caption>
*
* Failure.from(error).toMaybe(Maybe);
* // => Maybe.Nothing();
*/
/**
* Converts the validation to a <code>Promise</code> using the provided <code>Promise</code> implementation.
* @abstract
* @function toPromise
* @memberof Validation
* @instance
* @param {Promise} promise - Promise implementation.
* @return {Promise} <code>Promise</code> wrapped <code>value</code>.
* @example <caption>Success#toPromise</caption>
*
* const Bluebird = require("bluebird");
*
* Success.from(value).toPromise(Bluebird);
* // => Promise.resolve(value);
*
* @example <caption>Failure#toPromise</caption>
*
* const Bluebird = require("bluebird");
*
* Failure.from(error).toPromise(Bluebird);
* // => Promise.reject([error]);
*/
/**
* Returns a <code>String</code> representation of the {@link Validation}.
* @abstract
* @function toString
* @memberof Validation
* @instance
* @return {String} <code>String</code> representation.
* @example <caption>Success#toString</caption>
*
* Success.from(1).toString();
* // => "Validation.Success(1)"
*
* @example <caption>Failure#toString</caption>
*
* Failure.from("Error message").toString();
* // => "Validation.Failure('Error message')"
*/
}
/**
* Concatenates two {@link Validation} instances together.
* @static
* @member
* @param {Validation} left - Left concatenation value.
* @param {Validation} right - Right concatenation value.
* @return {Validation} Concatenated validations.
* @example <caption>Concatenating distinct validations</caption>
*
* const validations = [
* validatePersonName(person), // => Success(person)
* validatePersonBirthdate(person), // => Success(person)
* validatePersonAddress(person), // => Failure([error1])
* validatePersonEmail(person) // => Failure([error2])
* ];
*
* Validation.reduce(Validation.concat, Success.empty(), validations);
* // => Failure([error1, error2])
*/
Validation.concat = invokeWith("concat");
/**
* Iterates over a collection of validations and invokes the <code>iteratee</code> for each {@link Validation}. The
* <code>iteratee</code> is invoked with one argument: <code>(validation)</code>. Iteratee functions may exit iteration
* early by explicitly returning a {@link Failure}.
* @static
* @member
* @param {Consumer} iteratee - The function to invoke per iteration.
* @param {Validation[]} validations - Collection of validations over which to iterate.
* @return {Validation[]} Current {@link Validation} collection.
* @example
*
* const validations = [
* validatePerson(person1), // => Success(person1)
* validatePerson(person2), // => Success(person2)
* validatePerson(person3), // => Failure([error1, error2])
* validatePerson(person4) // => Failure([error2])
* ];
*
* Validation.each(validation => validation.orElse(flow(join(", "), console.error)), validations);
* // => Logs 'error1, error2' then 'error2'
* //
* // => validations
*/
Validation.each = curry((iteratee, validations) => each(
flow(iteratee, negate(Validation.isFailure)),
validations
));
/**
* Determines whether or not the <code>other</code> is equal in value to the current (<code>this</code>). This is
* <strong>not</strong> a reference check.
* @static
* @member
* @param {*} other - Other value to check.
* @return {Boolean} <code>true</code> if the two validations are equal; <code>false</code> if not equal.
* @example <caption>Reflexivity</caption>
*
* Validation.equals(v1, v1) === true;
* // => true
*
* @example <caption>Symmetry</caption>
*
* Validation(v1, v2) === Validation.equals(v2, v1);
* // => true
*
* @example <caption>Transitivity</caption>
*
* (Validation.equals(v1, v2) === Validation.equals(v2, v3)) && Validation.equals(v1, v3)
* // => true
*/
Validation.equals = isEqual;
/**
* Iterates over a collection of validations, returning an array of all validations the <code>predicate</code> for which
* returns truthy. The <code>predicate</code> is invoked with one argument: <code>(validation)</code>.
* @static
* @member
* @param {Predicate} predicate - The function to invoke per iteration.
* @param {Validations[]} validations - Collection of validations over which to iterate.
* @return {Validations[]} Filtered {@link Validation} collection.
* @example <caption>Filter and log failures</caption>
*
* const validations = [
* validatePerson(person1), // => Success(person1)
* validatePerson(person2), // => Success(person2)
* validatePerson(person3), // => Failure([error1, error2])
* validatePerson(person4) // => Failure([error2])
* ];
*
* // Log failures, return successes.
* Validation.filter(validation => validation.orElse(flow(join(", "), console.error)).isSuccess(), validations);
* // => Logs 'error1, error2' then 'error2'
* //
* // => [Success(person1), Success(person2)]
*/
Validation.filter = filter;
/**
* Creates an array of values by invoking {@link Validation#map} with the <code>iteratee</code> for each
* {@link Validation} in the collection. The iteratee is invoked with one argument: <code>(value)</code>.
* @static
* @member
* @param {Function} iteratee - The function to invoke per iteration.
* @param {Validation[]} validations - Collection of validations over which to iterate.
* @return {Validation[]} Mapped {@link Validation} collection.
* @example <caption>Mapping each Validation's value</caption>
*
* const validations = [
* validatePrice(2.10), // => Success(price1)
* validatePrice(2.25), // => Success(price2)
* validatePrice("2.50"), // => Failure([error1])
* validatePrice("Three dollars") // => Failure([error1])
* ];
*
* Validation.mapIn(Math.floor, validations);
* // => [Success(2), Success(2), Failure([error1]), Failure([error2])]
*/
Validation.mapIn = curry((iteratee, validations) => map(invokeIn("map", iteratee), validations));
/**
* Creates an array of values by running each {@link Validation} in collection through the <code>iteratee</code>. The
* iteratee is invoked with one argument: <code>(validation)</code>.
* @static
* @member
* @param {Function} iteratee - The function to invoke per iteration.
* @param {Validation[]} validations - Collection of validations over which to iterate.
* @return {Validation[]} Mapped collection.
* @example <caption>Mapping all validations to promises</caption>
*
* const validations = [
* validatePrice(2.10), // => Success(price1)
* validatePrice(2.25), // => Success(price2)
* validatePrice("2.50"), // => Failure([error1])
* validatePrice("Three dollars") // => Failure([error1])
* ];
*
* Validation.map(Validation.toPromise, validations);
* // => [Promise.resolve(price1), Promise.resolve(price2), Promise.reject([error1]), Promise.reject([error2])]
*/
Validation.map = map;
/**
* Reduces collection to a value which is the accumulated result of running each validation in the
* <code>validations</code> collection through the <code>iteratee</code>, where each successive invocation is supplied
* the return value of the previous. The iteratee is invoked with two arguments: <code>(accumulator, value)</code>.
* @static
* @member
* @param {Reduction} iteratee - The function to invoke per iteration.
* @param {*} accumulator - The initial value.
* @param {Validation[]} validations - Collection of validations over which to iterate.
* @return {*} Accumulator.
* @example
*
* const validations = [
* validatePersonName(person), // => Success(person)
* validatePersonBirthdate(person), // => Success(person)
* validatePersonAddress(person), // => Failure([error1])
* validatePersonEmail(person) // => Failure([error2])
* ];
*
* Validation.reduce(Validation.concat, Success.empty(), validations);
* // => Failure([error1, error2])
*/
Validation.reduce = reduce;
/**
* Converts a {@link Validation} to an {@link Either}. {@link Success} becomes a {@link Right} and {@link Failure}
* becomes {@link Left}.
* @static
* @member
* @param {Either} either - Either implementation.
* @param {Validation} value - Validation to convert.
* @return {Either} {@link Either} wrapped <code>value</code>.
* @example <caption>Success to Resolved</caption>
*
* Validation.toEither(Either, Success.from(value));
* // => Either.Right(value);
*
* @example <caption>Failure to Rejected</caption>
*
* Validation.toEither(Either, Failure.from(error));
* // => Either.Left([error]);
*/
Validation.toEither = invokeIn("toEither");
/**
* Converts a {@link Validation} to an {@link Maybe}. {@link Success} becomes a {@link Just} and {@link Failure}
* becomes {@link Nothing}.
* @static
* @member
* @param {Maybe} maybe - Maybe implementation.
* @param {Validation} value - Validation to convert.
* @return {Maybe} {@link Maybe} wrapped <code>value</code>.
* @example <caption>Success to Resolved</caption>
*
* Validation.toMaybe(Maybe, Success.from(value));
* // => Maybe.Just(value);
*
* @example <caption>Failure to Rejected</caption>
*
* Validation.toMaybe(Maybe, Failure.from(error));
* // => Maybe.Nothing();
*/
Validation.toMaybe = invokeIn("toMaybe");
/**
* Converts a validation to a <code>Promise</code> using the provided <code>Promise</code> implementation.
* @static
* @member
* @param {Promise} promise - Promise implementation.
* @param {Validation} value - Validation to convert.
* @return {Promise} <code>Promise</code> wrapped <code>value</code>.
* @example <caption>Convert with bluebird's implementation of Promise</caption>
*
* const toBluebird = Validation.toPromise(require("bluebird"));
*
* toBluebird(Success.from(value));
* // => Promise.resolve(value);
*
* toBluebird(Failure.from(error));
* // => Promise.reject([error]);
*/
Validation.toPromise = invokeIn("toPromise");
/**
* @extends Validation
* @inheritdoc
*/
class Failure extends Validation {
/**
* Creates a new {@link Failure} from a <code>value</code>. If the <code>value</code> is already a {@link Validation}
* instance, the <code>value</code> is returned unchanged. Otherwise, a new {@link Failure} is made with the
* <code>value</code>.
* @static
* @param {*} value - Value to wrap in a {@link Failure}.
* @return {Validation} {@link Validation} when is the <code>value</code> already wrapped or
* {@link Failure} wrapped <code>value</code>.
* @example <caption>Failure from nothing</caption>
*
* Failure.from();
* // => Failure([])
*
* @example <caption>Failure from arbitrary value</caption>
*
* Failure.from(true);
* // => Failure([true])
*
* @example <caption>Failure from Success</caption>
*
* Failure.from(Success.from(value));
* // => Success.from(value)
*
* @example <caption>Failure from another Failure</caption>
*
* Failure.from(Failure.from("Error message"));
* // => Failure(["Error message"])
*/
static from(value) {
return Validation.isValidation(value) ?
value :
new Failure(value);
}
constructor(value) {
super(isUndefined(value) ? [] : [].concat(value));
}
ap() {
return this;
}
bimap(failureMap) {
return Failure.from(failureMap(this.value));
}
chain() {
return this;
}
concat(other) {
return other.isSuccess() ?
this :
new Failure(this.value.concat(other.value));
}
extend() {
return this;
}
ifFailure(method) {
method(this.value);
return this;
}
ifSuccess() {
return this;
}
map() {
return this;
}
orElse(value) {
return value;
}
orElseGet(method) {
return method();
}
orElseThrow(method) {
throw method(this.value);
}
toEither(either) {
return new either.Left(this.value);
}
toMaybe(maybe) {
return new maybe.Nothing();
}
toPromise(promise) {
return promise.reject(this.value);
}
toString() {
return `Validation.Failure(${this.value.join("; ")})`;
}
}
/**
* @extends Validation
* @inheritdoc
*/
class Success extends Validation {
/**
* Creates a new {@link Success} from a <code>value</code>. If the <code>value</code> is already a
* {@link Validation} instance, the <code>value</code> is returned unchanged. Otherwise, a new
* {@link Success} is made with the <code>value</code>.
* @static
* @param {*} value - Value to wrap in a {@link Success}.
* @return {Validation} {@link Validation} when is the <code>value</code> already wrapped or
* {@link Success} wrapped <code>value</code>.
* @example <caption>Success from nothing</caption>
*
* Success.from();
* // => Success()
*
* @example <caption>Success from arbitrary value</caption>
*
* Success.from(true);
* // => Success(true)
*
* @example <caption>Success from another Success</caption>
*
* Success.from(Success.from(value));
* // => Success(value)
*
* @example <caption>Success from Failure</caption>
*
* Success.from(Failure.from("Error message"));
* // => Failure(["Error message"])
*/
static from(value) {
return Validation.isValidation(value) ?
value :
new Success(value);
}
constructor(value) {
super(value);
}
ap(other) {
return other.map(this.value);
}
bimap(leftMap, rightMap) {
return Success.from(rightMap(this.value));
}
chain(method) {
return Validation.from(method(this.value));
}
concat(other) {
return (other.isSuccess() && !isUndefined(this.value)) ? this : other;
}
extend(method) {
return Validation.from(method(this));
}
ifFailure() {
return this;
}
ifSuccess(method) {
method(this.value);
return this;
}
map(method) {
return Success.of(method(this.value));
}
orElse() {
return this.value;
}
orElseGet() {
return this.value;
}
orElseThrow() {
return this.value;
}
toEither(either) {
return new either.Right(this.value);
}
toMaybe(maybe) {
return new maybe.Just(this.value);
}
toPromise(promise) {
return promise.resolve(this.value);
}
toString() {
return `Validation.Success(${this.value})`;
}
}
module.exports = Validation;