UNPKG

lodash-fantasy

Version:

Fantasy Land compatible types built with lodash.

1,188 lines (1,106 loc) 36.5 kB
"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;