From 96c44dc2005767c8db0c7ec65e048d67e96aaeae Mon Sep 17 00:00:00 2001 From: yifanwww Date: Sat, 8 Jun 2024 02:21:50 +0800 Subject: [PATCH 1/4] feat: add ResultAsync and OkAsync, ErrAsync factories --- .eslintrc.js | 2 +- src/Result.ts | 10 +- src/ResultAsync.ts | 598 +++++++++ src/RustlikeResultAsync.ts | 195 +++ src/__tests__/RustlikeResult.test.ts | 56 +- src/__tests__/RustlikeResultAsync.test.ts | 1390 +++++++++++++++++++++ src/__tests__/_helpers.ts | 48 +- src/__tests__/factoryAsync.test.ts | 30 + src/factoryAsync.ts | 40 + src/index.ts | 2 + 10 files changed, 2330 insertions(+), 41 deletions(-) create mode 100644 src/ResultAsync.ts create mode 100644 src/RustlikeResultAsync.ts create mode 100644 src/__tests__/RustlikeResultAsync.test.ts create mode 100644 src/__tests__/factoryAsync.test.ts create mode 100644 src/factoryAsync.ts diff --git a/.eslintrc.js b/.eslintrc.js index 66f14ce..c36bcaf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -11,7 +11,7 @@ const naming = [ { selector: 'enumMember', format: ['UPPER_CASE'], leadingUnderscore: 'forbid' }, - { selector: 'function', format: null, filter: { regex: '^(Ok|Err)$', match: true } }, + { selector: 'function', format: null, filter: { regex: '^(Ok|OkAsync|Err|ErrAsync)$', match: true } }, { selector: 'function', format: ['camelCase'], leadingUnderscore: 'allow' }, { selector: 'method', modifiers: ['static'], format: null, filter: { regex: '^(Ok|Err)$', match: true } }, diff --git a/src/Result.ts b/src/Result.ts index 7367bfc..980df78 100644 --- a/src/Result.ts +++ b/src/Result.ts @@ -663,7 +663,7 @@ export interface Result { /** * Calls `op` if itself is `Ok`, otherwise returns the `Err` value of itself. * - * This function can be used for control flow based on Result values. + * This function can be used for control flow based on `Result` values. * * Examples: * @@ -690,7 +690,7 @@ export interface Result { /** * Asynchronously calls `op` if itself is `Ok`, otherwise returns the `Err` value of itself. * - * This function can be used for control flow based on Result values. + * This function can be used for control flow based on `Result` values. * * Examples: * @@ -746,7 +746,7 @@ export interface Result { * * x = Ok(2); * y = Ok(100); - * assert(x.and(y).equal(Ok('different result type'))); + * assert(x.or(y).equal(Ok(2))); * ``` * * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.or @@ -756,7 +756,7 @@ export interface Result { /** * Calls `op` if the result is `Err`, otherwise returns the `Ok` value of self. * - * This function can be used for control flow based on result values. + * This function can be used for control flow based on `Result` values. * * Examples: * @@ -779,7 +779,7 @@ export interface Result { /** * Asynchronously calls `op` if the result is `Err`, otherwise returns the `Ok` value of self. * - * This function can be used for control flow based on result values. + * This function can be used for control flow based on `Result` values. * * Examples: * diff --git a/src/ResultAsync.ts b/src/ResultAsync.ts new file mode 100644 index 0000000..683f1ed --- /dev/null +++ b/src/ResultAsync.ts @@ -0,0 +1,598 @@ +import type { Result } from './Result'; +import type { Optional } from './types.internal'; + +/** + * The interface of async `Result` that defines the methods that async `Result` should support. + * + * This package includes a default implementation of async `Result` and factory functions `OkAsync` and `ErrAsync`, + * which should meet your requirements in most cases. + * + * ref: + * - https://doc.rust-lang.org/std/result/index.html + * - https://doc.rust-lang.org/std/result/enum.Result.html + */ +export interface ResultAsync extends PromiseLike> { + /** + * Asynchornously returns `true` if the result is `Ok`. + * + * Examples: + * + * ``` + * import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * const x: ResultAsync = OkAsync(2); + * assert((await x.isOk()) === true); + * + * const y: ResultAsync = ErrAsync('Some error message'); + * assert((await y.isOk()) === false); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.is_ok + */ + isOk(): Promise; + + /** + * Asynchronously returns `true` if the result is `Ok` and the value inside of it matches a predicate. + * + * Examples: + * + * ``` + * import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * const x: ResultAsync = OkAsync(2); + * assert((await x.isOkAnd((value) => Promise.resolve(value > 1))) === true); + * + * const y: ResultAsync = OkAsync(0); + * assert((await y.isOkAnd((value) => Promise.resolve(value > 1))) === false); + * + * const z: ResultAsync = ErrAsync('Some error message'); + * assert((await z.isOkAnd((value) => Promise.resolve(value > 1))) === false); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.is_ok_and + */ + isOkAnd(fn: (value: T) => boolean | Promise): Promise; + + /** + * Asynchornously returns `true` if the result is `Err`. + * + * Examples: + * + * ``` + * import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * const x: ResultAsync = OkAsync(-3); + * assert((await x.isErr()) === false); + * + * const y: ResultAsync = ErrAsync('Some error message'); + * assert((await y.isErr()) === true); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.is_err + */ + isErr(): Promise; + + /** + * Asynchronously returns `true` if the result is `Err` and the value inside of it matches a predicate. + * + * Examples: + * + * ``` + * import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * enum ErrorKind { + * NOT_FOUND, + * PERMISSION_DENIED, + * } + * + * const x: ResultAsync = ErrAsync(ErrorKind.NOT_FOUND); + * assert((await x.isErrAnd((value) => Promise.resolve(value === ErrorKind.NOT_FOUND))) === true); + * + * const y: ResultAsync = ErrAsync(ErrorKind.PERMISSION_DENIED); + * assert((await y.isErrAnd((value) => Promise.resolve(value === ErrorKind.NOT_FOUND))) === false); + * + * const z: ResultAsync = OkAsync(123); + * assert((await z.isErrAnd((value) => Promise.resolve(value === ErrorKind.NOT_FOUND))) === false); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.is_err_and + */ + isErrAnd(fn: (err: E) => boolean | Promise): Promise; + + /** + * Asynchornously converts from `ResultAsync` to `Optional` and discarding the error, if any. + * + * Examples: + * + * ``` + * import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * const x: ResultAsync = OkAsync(2); + * assert((await x.ok()) === 2); + * + * const y: ResultAsync = ErrAsync('Some error message'); + * assert((await y.ok()) === undefined); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.ok + */ + ok(): Promise>; + + /** + * Asynchornously converts from `ResultAsync` to `Optional` and discarding the success value, if any. + * + * Examples: + * + * ``` + * import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * const x: ResultAsync = OkAsync(2); + * assert((await x.err()) === undefined); + * + * const y: ResultAsync = ErrAsync('Some error message'); + * assert((await y.err()) === 'Some error message'); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.err + */ + err(): Promise>; + + /** + * Asynchronously maps a `ResultAsync` to `ResultAsync` by applying a function to a contained `Ok` + * value, leaving an `Err` value untouched. + * + * This function can be used to compose the results of two functions. + * + * Examples: + * + * ``` + * import { OkAsync } from 'rustlike-result'; + * + * const x = OkAsync('foo').map((value) => Promise.resolve(value.length)); + * assert((await x.ok()) === 3); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.map + */ + map(op: (value: T) => U | Promise): ResultAsync; + + /** + * Asynchronously returns the provided `fallback` (if `Err`), + * or applies a function to the contained value (if `Ok`). + * + * Arguments passed to `mapOr` are eagerly evaluated; + * if you are passing the result of a function call, + * it is recommended to use `mapOrElse`, which is lazily evaluated. + * + * Examples: + * + * ``` + * import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * const x: ResultAsync = OkAsync('foo'); + * assert((await x.mapOr(42, (value) => value.length)) === 3); + * + * const y: ResultAsync = ErrAsync('bar'); + * assert((await y.mapOr(42, (value) => value.length)) === 42); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.map_or + */ + mapOr(fallback: U, map: (value: T) => U | Promise): Promise; + + /** + * Asynchronously maps a `ResultAsync` to `U` by applying fallback function `fallback` to a contained `Err` + * value, or function `map` to a contained `Ok` value. + * + * This function can be used to unpack a successful result while handling an error. + * + * Examples: + * + * ``` + * import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * const k = 21; + * + * const x: ResultAsync = OkAsync('foo'); + * assert((await x.mapOrElse(() => Promise.resolve(k * 2), (value) => Promise.resolve(value.length))) === 3); + * + * const y: ResultAsync = ErrAsync('bar'); + * assert((await y.mapOrElse(() => Promise.resolve(k * 2), (value) => Promise.resolve(value.length))) === 42); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.map_or_else + */ + mapOrElse(fallback: (err: E) => U | Promise, map: (value: T) => U | Promise): Promise; + + /** + * Asynchronously maps a `ResultAsync` to `ResultAsync` by applying a function to a contained `Err` + * value, leaving an `Ok` value untouched. + * + * This function can be used to pass through a successful result while handling an error. + * + * Examples: + * + * ``` + * import { ErrAsync } from 'rustlike-result'; + * + * const x = ErrAsync(new Error('Some error message')).mapErr((err) => Promise.resolve(err.message)); + * assert((await x.err()) === 'Some error message'); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.map_err + */ + mapErr(op: (err: E) => F | Promise): ResultAsync; + + /** + * Asynchronously calls the provided closure with a reference to the contained value if `Ok`. + * + * Examples: + * + * ``` + * import { resultify } from 'rustlike-result'; + * + * const num = await OkAsync(4) + * .inspect((value) => { + * console.log(`original: ${value}`); + * }) + * .map((value) => value ** 3) + * .expect('Some error message'); + * assert(num === 64); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect + */ + inspect(fn: (value: T) => void | Promise): ResultAsync; + + /** + * Asynchronously calls the provided closure with a reference to the contained value if `Err`. + * + * Examples: + * + * ``` + * import { resultify } from 'rustlike-result'; + * + * const result = ErrAsync(new SyntaxError('Some error message')).inspectErr((err) => { + * console.log(`failed to do something: ${err.message}`); + * }); + * assert((await result.err()) instanceof SyntaxError); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect_err + */ + inspectErr(fn: (err: E) => void | Promise): ResultAsync; + + /** + * Asynchronously returns the contained `Ok` value. + * + * Because this function may throw an error, its use is generally discouraged. + * Instead, prefer to call `unwrapOr`, `unwrapOrElse`. + * + * Throws an Error if itself is `Err`, + * with an error message including the passed message, and the content of the `Err`. + * + * Examples: + * + * ``` + * import { ErrAsync, type ResultAsync } from 'rustlike-result'; + * + * const x: ResultAsync = ErrAsync('emergency failure'); + * await x.expect('Failed to operate'); // throws Error('Failed to operate: emergency failure') + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.expect + */ + expect(msg: string): Promise; + + /** + * Asynchronously returns the contained `Ok` value. + * + * Because this function may throw an error, its use is generally discouraged. + * Instead, prefer to call `unwrapOr`, `unwrapOrElse`. + * + * Throws an Error if itself is `Err`, with an error message provided by the `Err`'s value. + * + * Examples: + * + * ``` + * import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * const x: ResultAsync = OkAsync(2); + * assert((await x.unwrap()) === 2); + * + * const y: ResultAsync = ErrAsync('emergency failure'); + * await y.unwrap(); // throws Error('emergency failure') + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap + */ + unwrap(): Promise; + + /** + * Asynchronously returns the contained `Err` value. + * + * Throws an Error if itself is `Err`, with an error message provided by the `Ok`'s value. + * + * Examples: + * + * ``` + * import { OkAsync, type ResultAsync } from 'rustlike-result'; + * + * const x: ResultAsync = OkAsync(10); + * await x.expectErr('Testing expectErr'); // throws Error('Testing expectErr: 10') + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.expect_err + */ + expectErr(msg: string): Promise; + + /** + * Asynchronously returns the contained `Err` value. + * + * Throws an Error if itself is `Ok`, with an error message provided by the `Ok`'s value. + * + * Examples: + * + * ``` + * import { OkAsync, type ResultAsync } from 'rustlike-result'; + * + * const x: ResultAsync = Err('emergency failure'); + * assert((await x.unwrapErr()) === 'emergency failure'); + * + * const y: ResultAsync = OkAsync(2); + * await y.unwrapErr(); // throws Error(2) + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_err + */ + unwrapErr(): Promise; + + /** + * Asynchronously returns the contained `Ok` value or a provided default. + * + * Arguments passed to `unwrapOr` are eagerly evaluated; + * if you are passing the result of a function call, + * it is recommended to use `unwrapOrElse`, which is lazily evaluated. + * + * Examples: + * + * ``` + * import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * const $default = 2; + * const x: ResultAsync = OkAsync(9); + * assert((await x.unwrapOr($default)) === 9); + * + * const y: ResultAsync = ErrAsync('error'); + * assert((await y.unwrapOr($default)) === $default); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_or + */ + unwrapOr(fallback: T): Promise; + + /** + * Asynchronously returns the contained `Ok` value or computes it from a closure. + * + * Examples: + * + * ``` + * import { ErrAsync, OkAsync } from 'rustlike-result'; + * + * const count = (err: string) => Promise.resolve(err.length); + * assert((await OkAsync(2).unwrapOrElse(count)) === 2); + * assert((await ErrAsync('foo').unwrapOrElse(count)) === 3); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_or_else + */ + unwrapOrElse(op: (err: E) => T | Promise): Promise; + + /** + * Asynchronously returns the contained `Ok` value, without checking that the value is not an `Err`. + * + * **SAFETY**: Calling this method on an `Err` is undefined behavior. + * The safety contract must be upheld by the caller. + * + * Examples: + * + * ``` + * import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * const x: ResultAsync = OkAsync(2); + * assert((await x.unwrapUnchecked()) === 2); + * + * const y: ResultAsync = ErrAsync('emergency failure'); + * await y.unwrapUnchecked(); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_unchecked + */ + unwrapUnchecked(): Promise; + + /** + * Asynchronously returns the contained `Err` value, without checking that the value is not an `Ok`. + * + * **SAFETY**: Calling this method on an `Ok` is undefined behavior. + * The safety contract must be upheld by the caller. + * + * Examples: + * + * ``` + * import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * const x: ResultAsync = OkAsync(2); + * await x.unwrapErrUnchecked(); + * + * const y: ResultAsync = ErrAsync('emergency failure'); + * assert((await y.unwrapErrUnchecked()) === 'emergency failure'); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_err_unchecked + */ + unwrapErrUnchecked(): Promise; + + /** + * Asynchronously returns `res` if itself is `Ok`, otherwise returns the `Err` value of itself. + * + * Arguments passed to `and` are eagerly evaluated; + * if you are passing the result of a function call, it is recommended to use `andThen`, which is lazily evaluated. + * + * Examples: + * + * ``` + * import { Err, ErrAsync, Ok, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * let x: ResultAsync; + * let y: ResultAsync; + * + * x = OkAsync(2); + * y = ErrAsync('late error'); + * assert(await x.and(y).equal(Err('late error'))); + * + * x = ErrAsync('early error'); + * y = OkAsync('foo'); + * assert(await x.and(y).equal(Err('early error'))); + * + * x = ErrAsync('not a 2'); + * y = ErrAsync('late error'); + * assert(await x.and(y).equal(Err('not a 2'))); + * + * x = OkAsync(2); + * y = OkAsync('different result type'); + * assert(await x.and(y).equal(Ok('different result type'))); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.and + */ + and(res: Result | Promise> | ResultAsync): ResultAsync; + + /** + * Asynchronously calls `op` if itself is `Ok`, otherwise returns the `Err` value of itself. + * + * This function can be used for control flow based on `ResultAsync` values. + * + * Examples: + * + * ``` + * import { Err, ErrAsync, Ok, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * const sq = (num: number): ResultAsync => OkAsync(num * num); + * const err = (num: number): ResultAsync => ErrAsync(num); + * + * const x = OkAsync(2).andThen(sq).andThen(sq); + * assert(await x.equal(Ok(16))); + * + * const y = OkAsync(2).andThen(sq).andThen(err); + * assert(await y.equal(Err(4))); + * + * const z = OkAsync(2).andThen(err).andThen(err); + * assert(await z.equal(Err(2))); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.and_then + */ + andThen(op: (value: T) => Result | Promise> | ResultAsync): ResultAsync; + + /** + * Asynchronously returns `res` if itself is `Err`, otherwise returns the `Ok` value of itself. + * + * Arguments passed to `or` are eagerly evaluated; + * if you are passing the result of a function call, it is recommended to use `orElse`, which is lazily evaluated. + * + * Examples: + * + * ``` + * import { Err, ErrAsync, Ok, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * let x: ResultAsync; + * let y: ResultAsync; + * + * x = OkAsync(2); + * y = ErrAsync('late error'); + * assert(await x.or(y).equal(Ok(2))); + * + * x = ErrAsync('early error'); + * y = OkAsync(2); + * assert(await x.or(y).equal(Ok(2))); + * + * x = ErrAsync('not a 2'); + * y = ErrAsync('late error'); + * assert(await x.or(y).equal(Err('late error'))); + * + * x = OkAsync(2); + * y = OkAsync(100); + * assert(await x.or(y).equal(Ok(2))); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.or + */ + or(res: Result | Promise> | ResultAsync): ResultAsync; + + /** + * Asynchronously calls `op` if the result is `Err`, otherwise returns the `Ok` value of self. + * + * This function can be used for control flow based on `ResultAsync` values. + * + * Examples: + * + * ``` + * import { Err, ErrAsync, Ok, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * const sq = (num: number): ResultAsync => OkAsync(num * num); + * const err = (num: number): ResultAsync => ErrAsync(num); + * + * const x = OkAsync(2).orElse(sq).orElse(sq); + * assert(await x.equal(Ok(2))); + * + * const y = ErrAsync(3).orElse(sq).orElse(err); + * assert(await y.equal(Ok(9))); + * + * const z = ErrAsync(3).orElse(err).orElse(err); + * assert(await z.equal(Err(3))); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.or_else + */ + orElse(op: (err: E) => Result | Promise> | ResultAsync): ResultAsync; + + /** + * Asynchronously transposes a `ResultAsync` of an optional value into an optional of a `ResultAsync`. + * + * `OkAsync(undefined | null)` will be mapped to `Promise`. + * `OkAsync(_)` and `ErrAsync(_)` will be mapped to `OkAsync(_)` and `ErrAsync(_)`. + * + * Examples: + * + * ``` + * import { OkAsync, type ResultAsync } from 'rustlike-result'; + * + * type SomeErr = unknown; + * + * let x: ResultAsync; + * let y: ResultAsync | undefined; + * + * x = OkAsync(5); + * y = OkAsync(5); + * assert(await x.transpose()!.equal(y)); + * + * x = OkAsync(undefined); + * y = undefined; + * assert((await x.transpose()) === y); + * + * x = OkAsync(null); + * y = undefined; + * assert((await x.transpose()) === y); + * ``` + * + * ref: https://doc.rust-lang.org/std/result/enum.Result.html#method.transpose + */ + transpose(): Promise, E>>>; + + /** + * Asynchronously returns `true` if `self` equals to `other`. + */ + equal( + other: Result | Promise> | ResultAsync, + ): Promise; +} diff --git a/src/RustlikeResultAsync.ts b/src/RustlikeResultAsync.ts new file mode 100644 index 0000000..1ce27f1 --- /dev/null +++ b/src/RustlikeResultAsync.ts @@ -0,0 +1,195 @@ +import { Err, Ok } from './factory'; +import type { Result } from './Result'; +import type { ResultAsync } from './ResultAsync'; +import { RustlikeResult } from './RustlikeResult'; +import type { Optional } from './types.internal'; + +/** + * The default implementation of interface `ResultAsync`. + */ +export class RustlikeResultAsync implements ResultAsync { + private readonly _promise: Promise>; + + constructor(promise: Result | Promise> | ResultAsync) { + this._promise = Promise.resolve(promise); + } + + // async type predicate issue: https://github.com/microsoft/TypeScript/issues/37681 + isOk(): Promise { + return this._promise.then((result) => result.isOk()); + } + + isOkAnd(fn: (value: T) => boolean | Promise): Promise { + return this._promise.then((result) => result.isOk() && fn(result.unwrapUnchecked())); + } + + // async type predicate issue: https://github.com/microsoft/TypeScript/issues/37681 + isErr(): Promise { + return this._promise.then((result) => result.isErr()); + } + + isErrAnd(fn: (err: E) => boolean | Promise): Promise { + return this._promise.then((result) => result.isErr() && fn(result.unwrapErrUnchecked())); + } + + ok(): Promise> { + return this._promise.then((result) => result.ok()); + } + + err(): Promise> { + return this._promise.then((result) => result.err()); + } + + map(op: (value: T) => U | Promise): ResultAsync { + return new RustlikeResultAsync( + this._promise.then(async (result) => + result.isOk() ? Ok(await op(result.unwrapUnchecked())) : Err(result.unwrapErrUnchecked()), + ), + ); + } + + mapOr(fallback: U, map: (value: T) => U | Promise): Promise { + return this._promise.then((result) => (result.isOk() ? map(result.unwrapUnchecked()) : fallback)); + } + + mapOrElse(fallback: (err: E) => U | Promise, map: (value: T) => U | Promise): Promise { + return this._promise.then((result) => + result.isOk() ? map(result.unwrapUnchecked()) : fallback(result.unwrapErrUnchecked()), + ); + } + + mapErr(op: (err: E) => F | Promise): ResultAsync { + return new RustlikeResultAsync( + this._promise.then(async (result) => + result.isOk() ? Ok(result.unwrapUnchecked()) : Err(await op(result.unwrapErrUnchecked())), + ), + ); + } + + inspect(fn: (value: T) => void | Promise): ResultAsync { + return new RustlikeResultAsync( + this._promise.then(async (result) => { + if (result.isOk()) { + await fn(result.unwrapUnchecked()); + } + return result; + }), + ); + } + + inspectErr(fn: (err: E) => void | Promise): ResultAsync { + return new RustlikeResultAsync( + this._promise.then(async (result) => { + if (result.isErr()) { + await fn(result.unwrapErrUnchecked()); + } + return result; + }), + ); + } + + expect(msg: string): Promise { + return this._promise.then((result) => result.expect(msg)); + } + + unwrap(): Promise { + return this._promise.then((result) => result.unwrap()); + } + + expectErr(msg: string): Promise { + return this._promise.then((result) => result.expectErr(msg)); + } + + unwrapErr(): Promise { + return this._promise.then((result) => result.unwrapErr()); + } + + unwrapOr(fallback: T): Promise { + return this._promise.then((result) => result.unwrapOr(fallback)); + } + + unwrapOrElse(op: (err: E) => T | Promise): Promise { + return this._promise.then((result) => + result.isOk() ? result.unwrapUnchecked() : op(result.unwrapErrUnchecked()), + ); + } + + // TODO: find a way to do the check in debug/development mode. + unwrapUnchecked(): Promise { + return this._promise.then((result) => result.unwrapUnchecked()); + } + + // TODO: find a way to do the check in debug/development mode. + unwrapErrUnchecked(): Promise { + return this._promise.then((result) => result.unwrapErrUnchecked()); + } + + and(res: Result | Promise> | ResultAsync): ResultAsync { + return new RustlikeResultAsync( + this._promise.then((result) => (result.isOk() ? res : Err(result.unwrapErrUnchecked()))), + ); + } + + andThen(op: (value: T) => Result | Promise> | ResultAsync): ResultAsync { + return new RustlikeResultAsync( + this._promise.then((result) => + result.isOk() ? op(result.unwrapUnchecked()) : Err(result.unwrapErrUnchecked()), + ), + ); + } + + or(res: Result | Promise> | ResultAsync): ResultAsync { + return new RustlikeResultAsync( + this._promise.then((result) => (result.isOk() ? Ok(result.unwrapUnchecked()) : res)), + ); + } + + orElse(op: (err: E) => Result | Promise> | ResultAsync): ResultAsync { + return new RustlikeResultAsync( + this._promise.then((result) => + result.isOk() ? Ok(result.unwrapUnchecked()) : op(result.unwrapErrUnchecked()), + ), + ); + } + + transpose(): Promise, E>>> { + return this._promise.then((result) => result.transpose()); + } + + private static async _equal(self: unknown, other: unknown): Promise { + const isSelfResult = self instanceof RustlikeResult || self instanceof RustlikeResultAsync; + const isOtherResult = other instanceof RustlikeResult || other instanceof RustlikeResultAsync; + + if (isSelfResult && isOtherResult) { + const _self: Result = await self; + const _other: Result = await other; + + const isOk = _self.isOk(); + if (isOk !== _other.isOk()) return false; + return isOk + ? RustlikeResultAsync._equal(_self.unwrapUnchecked(), _other.unwrapUnchecked()) + : RustlikeResultAsync._equal(_self.unwrapErrUnchecked(), _other.unwrapErrUnchecked()); + } + + return self === other || (Number.isNaN(self) && Number.isNaN(other)); + } + + async equal( + other: Result | Promise> | ResultAsync, + ): Promise { + const _self = await this._promise; + const _other = await other; + const isOk = _self.isOk(); + if (isOk !== _other.isOk()) return false; + return isOk + ? RustlikeResultAsync._equal(_self.unwrapUnchecked(), _other.unwrapUnchecked()) + : RustlikeResultAsync._equal(_self.unwrapErrUnchecked(), _other.unwrapErrUnchecked()); + } + + then, TResult2 = never>( + onfulfilled?: ((value: Result) => TResult1 | PromiseLike) | null | undefined, + onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null | undefined, + ): PromiseLike { + return this._promise.then(onfulfilled, onrejected); + } +} diff --git a/src/__tests__/RustlikeResult.test.ts b/src/__tests__/RustlikeResult.test.ts index c7f9162..f9c4b31 100644 --- a/src/__tests__/RustlikeResult.test.ts +++ b/src/__tests__/RustlikeResult.test.ts @@ -1500,12 +1500,10 @@ describe(`Test method \`${RustlikeResult.name}.prototype.${RustlikeResult.protot describe(`Test method \`${RustlikeResult.name}.prototype.${RustlikeResult.prototype.transpose.name}\``, () => { it('should transpose itself to an optional of a `Result`', () => { - expect(Ok(1).transpose()).toStrictEqual(Ok(1)); - expect(Ok(undefined).transpose()).toBeUndefined(); - expect(Ok(null).transpose()).toBeUndefined(); - expect(Err('Some error message').transpose()).toStrictEqual( - Err('Some error message'), - ); + expect(Ok(1).transpose()).toStrictEqual(Ok(1)); + expect(Ok(undefined).transpose()).toBeUndefined(); + expect(Ok(null).transpose()).toBeUndefined(); + expect(Err('Some error message').transpose()).toStrictEqual(Err('Some error message')); }); it('should have correct examples doc', () => { @@ -1534,35 +1532,43 @@ describe(`Test method \`${RustlikeResult.name}.prototype.${RustlikeResult.protot describe(`Test method \`${RustlikeResult.name}.prototype.${RustlikeResult.prototype.equal.name}\``, () => { it('should check if itself equals to another result', () => { + // simple true + expect(Ok(1).equal(Ok(1))).toBe(true); expect(Ok(NaN).equal(Ok(NaN))).toBe(true); + expect(Err('err').equal(Err('err'))).toBe(true); + + // simple false + + expect(Ok(1).equal(Ok(2))).toBe(false); + expect(Ok(1).equal(Ok('hello world'))).toBe(false); + expect(Ok(1).equal(Err('err'))).toBe(false); + expect(Err('err 1').equal(Err('err 2'))).toBe(false); + expect(Err('err 1').equal(Err(-1))).toBe(false); + expect(Err('error').equal(Ok(1))).toBe(false); + + // nested true + expect(Ok(Ok(1)).equal(Ok(Ok(1)))).toBe(true); - expect(Ok(Err('Some error message')).equal(Ok(Err('Some error message')))).toBe(true); - expect(Err('Some error message').equal(Err('Some error message'))).toBe(true); - expect(Err(Err('Some error message')).equal(Err(Err('Some error message')))).toBe(true); + expect(Ok(Err('err')).equal(Ok(Err('err')))).toBe(true); + expect(Err(Err('err')).equal(Err(Err('err')))).toBe(true); expect(Err(Ok(1)).equal(Err(Ok(1)))).toBe(true); - expect(Ok(1).equal(Ok(2))).toBe(false); + // nested false + expect(Ok(Ok(1)).equal(Ok(Ok(2)))).toBe(false); - expect(Ok(Err('Some error message')).equal(Ok(Ok(1)))).toBe(false); - expect(Ok(Ok(1)).equal(Ok(Err('Some error message')))).toBe(false); - expect(Err('Some error message 1').equal(Err('Some error message 2'))).toBe(false); - expect(Err(Err('Some error message 1')).equal(Err(Err('Some error message 2')))).toBe(false); - expect(Err(Ok(1)).equal(Err(Err('Some error message')))).toBe(false); - expect(Err(Err('Some error message')).equal(Err(Ok(1)))).toBe(false); - expect(Ok(1).equal(Err('Some error message'))).toBe(false); + expect(Ok(Ok(1)).equal(Ok(Err('err')))).toBe(false); + expect(Ok(Err('err')).equal(Ok(Ok(1)))).toBe(false); + expect(Err(Err('err')).equal(Err(Ok(1)))).toBe(false); + expect(Err(Ok(1)).equal(Err(Err('err')))).toBe(false); + + // object equality expect(Ok([1]).equal(Ok([1]))).toBe(false); expect(Ok({ foo: 1 }).equal(Ok({ foo: 1 }))).toBe(false); + expect(Err({ message: 'err' }).equal(Err({ message: 'err' }))).toBe(false); expect(Ok(Ok([1])).equal(Ok(Ok([1])))).toBe(false); expect(Ok(Ok({ foo: 1 })).equal(Ok(Ok({ foo: 1 })))).toBe(false); - expect(Err({ message: 'Some error message' }).equal(Err({ message: 'Some error message' }))).toBe(false); - expect(Err(Err({ message: 'Some error message' })).equal(Err(Err({ message: 'Some error message' })))).toBe( - false, - ); - - expect(Ok(1).equal(Ok('hello world'))).toBe(false); - expect(Ok(1).equal(Err('error'))).toBe(false); - expect(Err('error').equal(Ok(1))).toBe(false); + expect(Err(Err({ message: 'err' })).equal(Err(Err({ message: 'err' })))).toBe(false); }); }); diff --git a/src/__tests__/RustlikeResultAsync.test.ts b/src/__tests__/RustlikeResultAsync.test.ts new file mode 100644 index 0000000..0693e01 --- /dev/null +++ b/src/__tests__/RustlikeResultAsync.test.ts @@ -0,0 +1,1390 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import assert from 'node:assert'; + +import { Err, Ok } from '../factory'; +import { ErrAsync, OkAsync } from '../factoryAsync'; +import type { Result } from '../Result'; +import type { ResultAsync } from '../ResultAsync'; +import { RustlikeResultAsync } from '../RustlikeResultAsync'; + +function panicFn1(): never { + throw new Error('error'); +} + +function panicFn2() { + return Promise.reject(new Error('error')); +} + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.isOk.name}\``, () => { + it('should return if itself is `Ok`', async () => { + await expect(OkAsync(1).isOk()).resolves.toBe(true); + await expect(ErrAsync('Some error message').isOk()).resolves.toBe(false); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const x: ResultAsync = OkAsync(2); + assert((await x.isOk()) === true); + + const y: ResultAsync = ErrAsync('Some error message'); + assert((await y.isOk()) === false); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.isOkAnd.name}\``, () => { + const fnFactory1 = () => jest.fn((num: number) => num > 1); + const fnFactory2 = () => jest.fn((num: number) => Promise.resolve(num > 1)); + + it('should return if itself is `Ok` and the value inside of it matches a predicate', async () => { + const _it = async (fn: (num: number) => boolean | Promise) => { + await expect(OkAsync(2).isOkAnd(fn)).resolves.toBe(true); + await expect(OkAsync(0).isOkAnd(fn)).resolves.toBe(false); + await expect(ErrAsync('Some error message').isOkAnd(fn)).resolves.toBe(false); + }; + + await _it(fnFactory1()); + await _it(fnFactory2()); + }); + + it('should call fn only once if itself is `Ok`', async () => { + const _it = async (fn: (num: number) => boolean | Promise) => { + expect(fn).toHaveBeenCalledTimes(0); + await OkAsync(2).isOkAnd(fn); + expect(fn).toHaveBeenCalledTimes(1); + }; + + await _it(fnFactory1()); + await _it(fnFactory2()); + }); + + it('should not call fn if itself is `Err`', async () => { + const _it = async (fn: (num: number) => boolean | Promise) => { + expect(fn).toHaveBeenCalledTimes(0); + await ErrAsync('Some error message').isOkAnd(fn); + expect(fn).toHaveBeenCalledTimes(0); + }; + + await _it(fnFactory1()); + await _it(fnFactory2()); + }); + + it('should panic if fn panic', async () => { + await expect(() => OkAsync(2).isOkAnd(panicFn1)).rejects.toThrow(Error('error')); + await expect(() => OkAsync(2).isOkAnd(panicFn2)).rejects.toThrow(Error('error')); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const x: ResultAsync = OkAsync(2); + assert((await x.isOkAnd((value) => Promise.resolve(value > 1))) === true); + + const y: ResultAsync = OkAsync(0); + assert((await y.isOkAnd((value) => Promise.resolve(value > 1))) === false); + + const z: ResultAsync = ErrAsync('Some error message'); + assert((await z.isOkAnd((value) => Promise.resolve(value > 1))) === false); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.isErr.name}\``, () => { + it('should return if itself is `Err`', async () => { + await expect(OkAsync(1).isErr()).resolves.toBe(false); + await expect(ErrAsync('Some error message').isErr()).resolves.toBe(true); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const x: ResultAsync = OkAsync(-3); + assert((await x.isErr()) === false); + + const y: ResultAsync = ErrAsync('Some error message'); + assert((await y.isErr()) === true); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.isErrAnd.name}\``, () => { + enum ErrorKind { + NOT_FOUND, + PERMISSION_DENIED, + } + + const fnFactory1 = () => jest.fn((err: ErrorKind) => err === ErrorKind.NOT_FOUND); + const fnFactory2 = () => jest.fn((err: ErrorKind) => Promise.resolve(err === ErrorKind.NOT_FOUND)); + + it('should return if itself is `Err` and the value inside of it matches a predicate', async () => { + const _it = async (fn: (err: ErrorKind) => boolean | Promise) => { + await expect(ErrAsync(ErrorKind.NOT_FOUND).isErrAnd(fn)).resolves.toBe(true); + await expect(ErrAsync(ErrorKind.PERMISSION_DENIED).isErrAnd(fn)).resolves.toBe(false); + await expect(OkAsync(123).isErrAnd(fn)).resolves.toBe(false); + }; + + await _it(fnFactory1()); + await _it(fnFactory2()); + }); + + it('should call fn only once if itself is `Err`', async () => { + const _it = async (fn: (err: ErrorKind) => boolean | Promise) => { + expect(fn).toHaveBeenCalledTimes(0); + await ErrAsync(ErrorKind.NOT_FOUND).isErrAnd(fn); + expect(fn).toHaveBeenCalledTimes(1); + }; + + await _it(fnFactory1()); + await _it(fnFactory2()); + }); + + it('should not call fn if itself is `Ok`', async () => { + const _it = async (fn: (err: ErrorKind) => boolean | Promise) => { + expect(fn).toHaveBeenCalledTimes(0); + await OkAsync(123).isErrAnd(fn); + expect(fn).toHaveBeenCalledTimes(0); + }; + + await _it(fnFactory1()); + await _it(fnFactory2()); + }); + + it('should panic if fn panic', async () => { + await expect(() => ErrAsync(ErrorKind.NOT_FOUND).isErrAnd(panicFn1)).rejects.toThrow(Error('error')); + await expect(() => ErrAsync(ErrorKind.NOT_FOUND).isErrAnd(panicFn2)).rejects.toThrow(Error('error')); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const x: ResultAsync = ErrAsync(ErrorKind.NOT_FOUND); + assert((await x.isErrAnd((value) => Promise.resolve(value === ErrorKind.NOT_FOUND))) === true); + + const y: ResultAsync = ErrAsync(ErrorKind.PERMISSION_DENIED); + assert((await y.isErrAnd((value) => Promise.resolve(value === ErrorKind.NOT_FOUND))) === false); + + const z: ResultAsync = OkAsync(123); + assert((await z.isErrAnd((value) => Promise.resolve(value === ErrorKind.NOT_FOUND))) === false); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.ok.name}\``, () => { + it('should convert itself to an optional value', async () => { + await expect(OkAsync(1).ok()).resolves.toBe(1); + await expect(ErrAsync('Some error message').ok()).resolves.toBeUndefined(); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const x: ResultAsync = OkAsync(2); + assert((await x.ok()) === 2); + + const y: ResultAsync = ErrAsync('Some error message'); + assert((await y.ok()) === undefined); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.err.name}\``, () => { + it('should convert itself to an optional error', async () => { + await expect(OkAsync(1).err()).resolves.toBeUndefined(); + await expect(ErrAsync('Some error message').err()).resolves.toBe('Some error message'); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const x: ResultAsync = OkAsync(2); + assert((await x.err()) === undefined); + + const y: ResultAsync = ErrAsync('Some error message'); + assert((await y.err()) === 'Some error message'); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.map.name}\``, () => { + const mapFactory1 = () => jest.fn((num: number) => String(num)); + const mapFactory2 = () => jest.fn((num: number) => Promise.resolve(String(num))); + + it('should map itself to another result', async () => { + const _it = async (map: (num: number) => string | Promise) => { + await expect(OkAsync(1).map(map)).resolves.toStrictEqual(Ok('1')); + await expect(ErrAsync('Some error message').map(map)).resolves.toStrictEqual(Err('Some error message')); + }; + + await _it(mapFactory1()); + await _it(mapFactory2()); + }); + + it('should call map fn only once if itself is `Ok`', async () => { + const _it = async (map: (num: number) => string | Promise) => { + expect(map).toHaveBeenCalledTimes(0); + await OkAsync(1).map(map); + expect(map).toHaveBeenCalledTimes(1); + }; + + await _it(mapFactory1()); + await _it(mapFactory2()); + }); + + it('should not call map fn if itself is `Err`', async () => { + const _it = async (map: (num: number) => string | Promise) => { + expect(map).toHaveBeenCalledTimes(0); + await ErrAsync('Some error message').map(map); + expect(map).toHaveBeenCalledTimes(0); + }; + + await _it(mapFactory1()); + await _it(mapFactory2()); + }); + + it('should panic if fn panic', async () => { + await expect(() => OkAsync(1).map(panicFn1)).rejects.toThrow(Error('error')); + await expect(() => OkAsync(1).map(panicFn2)).rejects.toThrow(Error('error')); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const x = OkAsync('foo').map((value) => Promise.resolve(value.length)); + assert((await x.ok()) === 3); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.mapOr.name}\``, () => { + const mapFactory1 = () => jest.fn((num: number) => num * 2); + const mapFactory2 = () => jest.fn((num: number) => Promise.resolve(num * 2)); + + it('should map itself to another result', async () => { + const _it = async (map: (num: number) => number | Promise) => { + await expect(OkAsync(1).mapOr(-1, map)).resolves.toBe(2); + await expect(ErrAsync('Some error message').mapOr(-1, map)).resolves.toBe(-1); + }; + + await _it(mapFactory1()); + await _it(mapFactory2()); + }); + + it('should call map fn only once if itself is `Ok`', async () => { + const _it = async (map: (num: number) => number | Promise) => { + expect(map).toHaveBeenCalledTimes(0); + await OkAsync(1).mapOr(-1, map); + expect(map).toHaveBeenCalledTimes(1); + }; + + await _it(mapFactory1()); + await _it(mapFactory2()); + }); + + it('should not call map fn if itself is `Err`', async () => { + const _it = async (map: (num: number) => number | Promise) => { + expect(map).toHaveBeenCalledTimes(0); + await ErrAsync('Some error message').mapOr(-1, map); + expect(map).toHaveBeenCalledTimes(0); + }; + + await _it(mapFactory1()); + await _it(mapFactory2()); + }); + + it('should panic if fn panic', async () => { + await expect(() => OkAsync(1).mapOr(Err('err'), panicFn1)).rejects.toThrow(Error('error')); + await expect(() => OkAsync(1).mapOr(Err('err'), panicFn2)).rejects.toThrow(Error('error')); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const x: ResultAsync = OkAsync('foo'); + assert((await x.mapOr(42, (value) => value.length)) === 3); + + const y: ResultAsync = ErrAsync('bar'); + assert((await y.mapOr(42, (value) => value.length)) === 42); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.mapOrElse.name}\``, () => { + const mapFactory1 = () => jest.fn(jest.fn((num: number) => String(num))); + const mapFactory2 = () => jest.fn(jest.fn((num: number) => Promise.resolve(String(num)))); + const fallbackFactory1 = () => jest.fn(jest.fn((str: string) => str)); + const fallbackFactory2 = () => jest.fn(jest.fn((str: string) => Promise.resolve(str))); + + it('should map itself to another value', async () => { + const _it = async ( + map: (num: number) => string | Promise, + fallback: (str: string) => string | Promise, + ) => { + await expect(OkAsync(1).mapOrElse(fallback, map)).resolves.toBe('1'); + await expect(ErrAsync('Some error message').mapOrElse(fallback, map)).resolves.toBe('Some error message'); + }; + + await _it(mapFactory1(), fallbackFactory1()); + await _it(mapFactory2(), fallbackFactory2()); + }); + + it('should call map fn only once if itself is `Ok`', async () => { + const _it = async ( + map: (num: number) => string | Promise, + fallback: (str: string) => string | Promise, + ) => { + expect(map).toHaveBeenCalledTimes(0); + expect(fallback).toHaveBeenCalledTimes(0); + await OkAsync(1).mapOrElse(fallback, map); + expect(map).toHaveBeenCalledTimes(1); + expect(fallback).toHaveBeenCalledTimes(0); + }; + + await _it(mapFactory1(), fallbackFactory1()); + await _it(mapFactory2(), fallbackFactory2()); + }); + + it('should call fallback fn only once if itself is `Err`', async () => { + const _it = async ( + map: (num: number) => string | Promise, + fallback: (str: string) => string | Promise, + ) => { + expect(map).toHaveBeenCalledTimes(0); + expect(fallback).toHaveBeenCalledTimes(0); + await ErrAsync('Some error message').mapOrElse(fallback, map); + expect(map).toHaveBeenCalledTimes(0); + expect(fallback).toHaveBeenCalledTimes(1); + }; + + await _it(mapFactory1(), fallbackFactory1()); + await _it(mapFactory2(), fallbackFactory2()); + }); + + it('should panic if fn panic', async () => { + await expect(() => OkAsync(1).mapOrElse(panicFn1, panicFn1)).rejects.toThrow(Error('error')); + await expect(() => ErrAsync('err').mapOrElse(panicFn1, panicFn1)).rejects.toThrow(Error('error')); + await expect(() => OkAsync(1).mapOrElse(panicFn2, panicFn2)).rejects.toThrow(Error('error')); + await expect(() => ErrAsync('err').mapOrElse(panicFn2, panicFn2)).rejects.toThrow(Error('error')); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const k = 21; + + const x: ResultAsync = OkAsync('foo'); + assert( + (await x.mapOrElse( + () => Promise.resolve(k * 2), + (value) => Promise.resolve(value.length), + )) === 3, + ); + + const y: ResultAsync = ErrAsync('bar'); + assert( + (await y.mapOrElse( + () => Promise.resolve(k * 2), + (value) => Promise.resolve(value.length), + )) === 42, + ); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.mapErr.name}\``, () => { + const mapFactory1 = () => jest.fn((num: number) => `error code: ${num}`); + const mapFactory2 = () => jest.fn((num: number) => Promise.resolve(`error code: ${num}`)); + + it('should map itself to another result', async () => { + const _it = async (map: (num: number) => string | Promise) => { + await expect(OkAsync(1).mapErr(map)).resolves.toStrictEqual(Ok(1)); + await expect(ErrAsync(2).mapErr(map)).resolves.toStrictEqual(Err('error code: 2')); + }; + + await _it(mapFactory1()); + await _it(mapFactory2()); + }); + + it('should call map fn only once if itself is `Err`', async () => { + const _it = async (map: (num: number) => string | Promise) => { + expect(map).toHaveBeenCalledTimes(0); + await ErrAsync(2).mapErr(map); + expect(map).toHaveBeenCalledTimes(1); + }; + + await _it(mapFactory1()); + await _it(mapFactory2()); + }); + + it('should not call map fn if itself is `Ok`', async () => { + const _it = async (map: (num: number) => string | Promise) => { + expect(map).toHaveBeenCalledTimes(0); + await OkAsync(1).mapErr(map); + expect(map).toHaveBeenCalledTimes(0); + }; + + await _it(mapFactory1()); + await _it(mapFactory2()); + }); + + it('should panic if fn panic', async () => { + await expect(() => ErrAsync('err').mapErr(panicFn1)).rejects.toThrow(Error('error')); + await expect(() => ErrAsync('err').mapErr(panicFn2)).rejects.toThrow(Error('error')); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const x = ErrAsync(new Error('Some error message')).mapErr((err) => Promise.resolve(err.message)); + assert((await x.err()) === 'Some error message'); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.inspect.name}\``, () => { + it('should not mutate result', async () => { + const fn1 = () => { + // do something + }; + const fn2 = () => { + // do something + return Promise.resolve(); + }; + + const okResult = OkAsync(1); + const errResult = ErrAsync(0); + + const expectedOk = Ok(1); + const expectedErr = Err(0); + + await expect(okResult.inspect(fn1)).resolves.toStrictEqual(expectedOk); + await expect(errResult.inspect(fn1)).resolves.toStrictEqual(expectedErr); + await expect(okResult.inspect(fn2)).resolves.toStrictEqual(expectedOk); + await expect(errResult.inspect(fn2)).resolves.toStrictEqual(expectedErr); + }); + + it('should inspect ok value', async () => { + const fn1 = jest.fn((value: number) => expect(value).toBe(1)); + const fn2 = jest.fn((value: number) => { + expect(value).toBe(1); + return Promise.resolve(); + }); + expect(fn1).toHaveBeenCalledTimes(0); + expect(fn2).toHaveBeenCalledTimes(0); + await OkAsync(1).inspect(fn1); + await OkAsync(1).inspect(fn2); + expect(fn1).toHaveBeenCalledTimes(1); + expect(fn2).toHaveBeenCalledTimes(1); + }); + + it('should not inspect err value', async () => { + const fn = jest.fn(() => { + // do something + }); + expect(fn).toHaveBeenCalledTimes(0); + await ErrAsync(1).inspect(fn); + expect(fn).toHaveBeenCalledTimes(0); + }); + + it('should panic if fn panic', async () => { + await expect(() => OkAsync(1).inspect(panicFn1)).rejects.toThrow(Error('error')); + await expect(() => OkAsync(1).inspect(panicFn2)).rejects.toThrow(Error('error')); + }); + + it('should have correct examples doc', async () => { + async function examples() { + jest.spyOn(console, 'log').mockImplementationOnce(() => {}); + + const num = await OkAsync(4) + .inspect((value) => { + console.log(`original: ${value}`); + }) + .map((value) => value ** 3) + .expect('Some error message'); + assert(num === 64); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.inspectErr.name}\``, () => { + it('should return itself', async () => { + const fn1 = () => { + // do something + }; + const fn2 = () => { + // do something + return Promise.resolve(); + }; + + const okResult = OkAsync(1); + const errResult = ErrAsync(0); + + const expectedOk = Ok(1); + const expectedErr = Err(0); + + await expect(okResult.inspectErr(fn1)).resolves.toStrictEqual(expectedOk); + await expect(errResult.inspectErr(fn1)).resolves.toStrictEqual(expectedErr); + await expect(okResult.inspectErr(fn2)).resolves.toStrictEqual(expectedOk); + await expect(errResult.inspectErr(fn2)).resolves.toStrictEqual(expectedErr); + }); + + it('should not inspect ok value', async () => { + const fn = jest.fn(() => { + // do something + }); + expect(fn).toHaveBeenCalledTimes(0); + await OkAsync(1).inspectErr(fn); + expect(fn).toHaveBeenCalledTimes(0); + }); + + it('should inspect err value', async () => { + const fn1 = jest.fn((value: number) => expect(value).toBe(1)); + const fn2 = jest.fn((value: number) => { + expect(value).toBe(1); + return Promise.resolve(); + }); + expect(fn1).toHaveBeenCalledTimes(0); + expect(fn2).toHaveBeenCalledTimes(0); + await ErrAsync(1).inspectErr(fn1); + await ErrAsync(1).inspectErr(fn2); + expect(fn1).toHaveBeenCalledTimes(1); + expect(fn2).toHaveBeenCalledTimes(1); + }); + + it('should panic if fn panic', async () => { + await expect(() => ErrAsync('err').inspectErr(panicFn1)).rejects.toThrow(Error('error')); + await expect(() => ErrAsync('err').inspectErr(panicFn2)).rejects.toThrow(Error('error')); + }); + + it('should have correct examples doc', async () => { + jest.spyOn(console, 'log').mockImplementationOnce(() => {}); + + async function examples() { + const result = ErrAsync(new SyntaxError('Some error message')).inspectErr((err) => { + console.log(`failed to do something: ${err.message}`); + }); + assert((await result.err()) instanceof SyntaxError); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.expect.name}\``, () => { + it('should unwrap itself to get the contained `Ok` value', async () => { + await expect(OkAsync(1).expect('Operation type should be correct')).resolves.toBe(1); + await expect(() => ErrAsync(2).expect('Operation type should be correct')).rejects.toThrow( + 'Operation type should be correct: 2', + ); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const x: ResultAsync = ErrAsync('emergency failure'); + await x.expect('Failed to operate'); + } + + await expect(examples()).rejects.toThrow('Failed to operate: emergency failure'); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.unwrap.name}\``, () => { + it('should unwrap itself to get the contained `Ok` value', async () => { + await expect(OkAsync(1).unwrap()).resolves.toBe(1); + await expect(() => ErrAsync('Some error message').unwrap()).rejects.toThrow('Some error message'); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const x: ResultAsync = OkAsync(2); + assert((await x.unwrap()) === 2); + + const y: ResultAsync = ErrAsync('emergency failure'); + await y.unwrap(); + } + + await expect(examples()).rejects.toThrow('emergency failure'); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.expectErr.name}\``, () => { + it('should unwrap itself to get the contained `Err` value', async () => { + await expect(ErrAsync('Some error message').expectErr('Testing expectErr')).resolves.toBe('Some error message'); + await expect(() => OkAsync(1).expectErr('Testing expectErr')).rejects.toThrow('Testing expectErr: 1'); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const x: ResultAsync = OkAsync(10); + await x.expectErr('Testing expectErr'); + } + + await expect(examples()).rejects.toThrow('Testing expectErr: 10'); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.unwrapErr.name}\``, () => { + it('should unwrap itself to get the contained `Err` value', async () => { + await expect(ErrAsync('Some error message').unwrapErr()).resolves.toBe('Some error message'); + await expect(() => OkAsync(1).unwrapErr()).rejects.toThrow('1'); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const x: ResultAsync = ErrAsync('emergency failure'); + assert((await x.unwrapErr()) === 'emergency failure'); + + const y: ResultAsync = OkAsync(2); + await y.unwrapErr(); + } + + await expect(examples()).rejects.toThrow('2'); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.unwrapOr.name}\``, () => { + it('should unwrap itself to get the contained `Ok` value or the provided value', async () => { + const ok = OkAsync(100); + const okErr = ErrAsync('Err'); + + await expect(ok.unwrapOr(50)).resolves.toBe(100); + await expect(okErr.unwrapOr(50)).resolves.toBe(50); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const $default = 2; + const x: ResultAsync = OkAsync(9); + assert((await x.unwrapOr($default)) === 9); + + const y: ResultAsync = ErrAsync('error'); + assert((await y.unwrapOr($default)) === $default); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.unwrapOrElse.name}\``, () => { + const fnFactory1 = () => + jest.fn((msg: string) => { + if (msg === 'I got this.') return 50; + throw new Error('BadBad'); + }); + const fnFactory2 = () => + jest.fn((msg: string) => { + if (msg === 'I got this.') return Promise.resolve(50); + throw new Error('BadBad'); + }); + + it('should unwrap itself to get the contained `Ok` value or computes it from a closure', async () => { + const _it = async (op: (msg: string) => number | Promise) => { + await expect(OkAsync(100).unwrapOrElse(op)).resolves.toBe(100); + await expect(ErrAsync('I got this.').unwrapOrElse(op)).resolves.toBe(50); + }; + + await _it(fnFactory1()); + await _it(fnFactory2()); + }); + + it('should call op only once if itself if `Err`', async () => { + const _it = async (op: (msg: string) => number | Promise) => { + expect(op).toHaveBeenCalledTimes(0); + await ErrAsync('I got this.').unwrapOrElse(op); + expect(op).toHaveBeenCalledTimes(1); + }; + + await _it(fnFactory1()); + await _it(fnFactory2()); + }); + + it('should not call op if itself if `Ok`', async () => { + const _it = async (op: (msg: string) => number | Promise) => { + expect(op).toHaveBeenCalledTimes(0); + await OkAsync(100).unwrapOrElse(op); + expect(op).toHaveBeenCalledTimes(0); + }; + + await _it(fnFactory1()); + await _it(fnFactory2()); + }); + + it('should panic if fn panic', async () => { + await expect(() => ErrAsync('err').unwrapOrElse(panicFn1)).rejects.toThrow(Error('error')); + await expect(() => ErrAsync('err').unwrapOrElse(panicFn2)).rejects.toThrow(Error('error')); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const count = (err: string) => Promise.resolve(err.length); + assert((await OkAsync(2).unwrapOrElse(count)) === 2); + assert((await ErrAsync('foo').unwrapOrElse(count)) === 3); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.unwrapUnchecked.name}\``, () => { + it('should unwrap itself to get the contained `Ok` value', async () => { + await expect(OkAsync(100).unwrapUnchecked()).resolves.toBe(100); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const x: ResultAsync = OkAsync(2); + assert((await x.unwrapUnchecked()) === 2); + + const y: ResultAsync = ErrAsync('emergency failure'); + await y.unwrapUnchecked(); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.unwrapErrUnchecked.name}\``, () => { + it('should unwrap itself to get the contained `Err` value', async () => { + await expect(ErrAsync('Err').unwrapErrUnchecked()).resolves.toBe('Err'); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const x: ResultAsync = OkAsync(2); + await x.unwrapErrUnchecked(); + + const y: ResultAsync = ErrAsync('emergency failure'); + assert((await y.unwrapErrUnchecked()) === 'emergency failure'); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +function op1(): ResultAsync { + return OkAsync(666); +} + +function op2(): ResultAsync { + return ErrAsync('sadface'); +} + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.and.name}\``, () => { + it('should return `res`', async () => { + await expect(op1().and(Ok(667))).resolves.toStrictEqual(Ok(667)); + await expect(op1().and(OkAsync(667))).resolves.toStrictEqual(Ok(667)); + await expect(op1().and(Err('bad'))).resolves.toStrictEqual(Err('bad')); + await expect(op1().and(ErrAsync('bad'))).resolves.toStrictEqual(Err('bad')); + }); + + it('should return the `Err` result', async () => { + await expect(op2().and(Ok(667))).resolves.toStrictEqual(Err('sadface')); + await expect(op2().and(OkAsync(667))).resolves.toStrictEqual(Err('sadface')); + await expect(op2().and(Err('bad'))).resolves.toStrictEqual(Err('sadface')); + await expect(op2().and(ErrAsync('bad'))).resolves.toStrictEqual(Err('sadface')); + }); + + it('should have correct examples doc', () => { + async function examples() { + let x: ResultAsync; + let y: ResultAsync; + + x = OkAsync(2); + y = ErrAsync('late error'); + assert(await x.and(y).equal(Err('late error'))); + + x = ErrAsync('early error'); + y = OkAsync('foo'); + assert(await x.and(y).equal(Err('early error'))); + + x = ErrAsync('not a 2'); + y = ErrAsync('late error'); + assert(await x.and(y).equal(Err('not a 2'))); + + x = OkAsync(2); + y = OkAsync('different result type'); + assert(await x.and(y).equal(Ok('different result type'))); + } + + expect(examples).not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.andThen.name}\``, () => { + it('should return `res`', async () => { + await expect(op1().andThen((num) => Ok(num + 1))).resolves.toStrictEqual(Ok(667)); + await expect(op1().andThen((num) => Promise.resolve(Ok(num + 1)))).resolves.toStrictEqual(Ok(667)); + await expect(op1().andThen((num) => OkAsync(num + 1))).resolves.toStrictEqual(Ok(667)); + await expect(op1().andThen(() => Err('bad'))).resolves.toStrictEqual(Err('bad')); + await expect(op1().andThen(() => Promise.resolve(Err('bad')))).resolves.toStrictEqual(Err('bad')); + await expect(op1().andThen(() => ErrAsync('bad'))).resolves.toStrictEqual(Err('bad')); + }); + + it('should return the `Err` result', async () => { + await expect(op2().andThen((num) => Ok(num + 1))).resolves.toStrictEqual(Err('sadface')); + await expect(op2().andThen((num) => Promise.resolve(Ok(num + 1)))).resolves.toStrictEqual(Err('sadface')); + await expect(op2().andThen((num) => OkAsync(num + 1))).resolves.toStrictEqual(Err('sadface')); + await expect(op2().andThen(() => Err('bad'))).resolves.toStrictEqual(Err('sadface')); + await expect(op2().andThen(() => Promise.resolve(Err('bad')))).resolves.toStrictEqual(Err('sadface')); + await expect(op2().andThen(() => ErrAsync('bad'))).resolves.toStrictEqual(Err('sadface')); + }); + + it('should call op fn only once if itself is `Ok`', async () => { + const fn = jest.fn((num: number) => Promise.resolve(Ok(num + 1))); + expect(fn).toHaveBeenCalledTimes(0); + await op1().andThen(fn); + expect(fn).toHaveBeenCalledTimes(1); + }); + + it('should not call op fn if itself is `Err`', async () => { + const fn = jest.fn((num: number) => Promise.resolve(Ok(num + 1))); + expect(fn).toHaveBeenCalledTimes(0); + await op2().andThen(fn); + expect(fn).toHaveBeenCalledTimes(0); + }); + + it('should panic if fn panic', async () => { + await expect(() => OkAsync(1).andThen(panicFn1)).rejects.toThrow(Error('error')); + await expect(() => OkAsync(1).andThen(panicFn2)).rejects.toThrow(Error('error')); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const sq = (num: number): ResultAsync => OkAsync(num * num); + const err = (num: number): ResultAsync => ErrAsync(num); + + const x = OkAsync(2).andThen(sq).andThen(sq); + assert(await x.equal(Ok(16))); + + const y = OkAsync(2).andThen(sq).andThen(err); + assert(await y.equal(Err(4))); + + const z = OkAsync(2).andThen(err).andThen(err); + assert(await z.equal(Err(2))); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.or.name}\``, () => { + it('should return the `Ok` result', async () => { + await expect(op1().or(Ok(667))).resolves.toStrictEqual(Ok(666)); + await expect(op1().or(OkAsync(667))).resolves.toStrictEqual(Ok(666)); + await expect(op1().or(Err('bad'))).resolves.toStrictEqual(Ok(666)); + await expect(op1().or(ErrAsync('bad'))).resolves.toStrictEqual(Ok(666)); + }); + + it('should return `res`', async () => { + await expect(op2().or(Ok(667))).resolves.toStrictEqual(Ok(667)); + await expect(op2().or(OkAsync(667))).resolves.toStrictEqual(Ok(667)); + await expect(op2().or(Err('bad'))).resolves.toStrictEqual(Err('bad')); + await expect(op2().or(ErrAsync('bad'))).resolves.toStrictEqual(Err('bad')); + }); + + it('should have correct examples doc', async () => { + async function examples() { + let x: ResultAsync; + let y: ResultAsync; + + x = OkAsync(2); + y = ErrAsync('late error'); + assert(await x.or(y).equal(Ok(2))); + + x = ErrAsync('early error'); + y = OkAsync(2); + assert(await x.or(y).equal(Ok(2))); + + x = ErrAsync('not a 2'); + y = ErrAsync('late error'); + assert(await x.or(y).equal(Err('late error'))); + + x = OkAsync(2); + y = OkAsync(100); + assert(await x.or(y).equal(Ok(2))); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.orElse.name}\``, () => { + it('should return the `Ok` result', async () => { + await expect(op1().orElse(() => Ok(667))).resolves.toStrictEqual(Ok(666)); + await expect(op1().orElse(() => Promise.resolve(Ok(667)))).resolves.toStrictEqual(Ok(666)); + await expect(op1().orElse(() => OkAsync(667))).resolves.toStrictEqual(Ok(666)); + await expect(op1().orElse((err) => Err(err))).resolves.toStrictEqual(Ok(666)); + await expect(op1().orElse((err) => Promise.resolve(Err(err)))).resolves.toStrictEqual(Ok(666)); + await expect(op1().orElse((err) => ErrAsync(err))).resolves.toStrictEqual(Ok(666)); + }); + + it('should return `res`', async () => { + await expect(op2().orElse(() => Ok(667))).resolves.toStrictEqual(Ok(667)); + await expect(op2().orElse(() => Promise.resolve(Ok(667)))).resolves.toStrictEqual(Ok(667)); + await expect(op2().orElse(() => OkAsync(667))).resolves.toStrictEqual(Ok(667)); + await expect(op2().orElse((err) => Err(err))).resolves.toStrictEqual(Err('sadface')); + await expect(op2().orElse((err) => Promise.resolve(Err(err)))).resolves.toStrictEqual(Err('sadface')); + await expect(op2().orElse((err) => ErrAsync(err))).resolves.toStrictEqual(Err('sadface')); + }); + + it('should call op fn only once if itself is `Err`', async () => { + const fn = jest.fn((err: string) => Promise.resolve(Err(err))); + expect(fn).toHaveBeenCalledTimes(0); + await op2().orElse(fn); + expect(fn).toHaveBeenCalledTimes(1); + }); + + it('should not call op fn if itself is `Ok`', async () => { + const fn = jest.fn((err: string) => Promise.resolve(Err(err))); + expect(fn).toHaveBeenCalledTimes(0); + await op1().orElse(fn); + expect(fn).toHaveBeenCalledTimes(0); + }); + + it('should panic if fn panic', async () => { + await expect(() => ErrAsync('err').orElse(panicFn1)).rejects.toThrow(Error('error')); + await expect(() => ErrAsync('err').orElse(panicFn2)).rejects.toThrow(Error('error')); + }); + + it('should have correct examples doc', async () => { + async function examples() { + const sq = (num: number): ResultAsync => OkAsync(num * num); + const err = (num: number): ResultAsync => ErrAsync(num); + + const x = OkAsync(2).orElse(sq).orElse(sq); + assert(await x.equal(Ok(2))); + + const y = ErrAsync(3).orElse(sq).orElse(err); + assert(await y.equal(Ok(9))); + + const z = ErrAsync(3).orElse(err).orElse(err); + assert(await z.equal(Err(3))); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.transpose.name}\``, () => { + it('should transpose itself to an optional of a `ResultAsync`', async () => { + await expect(OkAsync(1).transpose()).resolves.toStrictEqual(Ok(1)); + await expect(OkAsync(undefined).transpose()).resolves.toBeUndefined(); + await expect(OkAsync(null).transpose()).resolves.toBeUndefined(); + await expect(ErrAsync('Some error message').transpose()).resolves.toStrictEqual(Err('Some error message')); + }); + + it('should have correct examples doc', async () => { + async function examples() { + type SomeErr = unknown; + + let x: ResultAsync; + let y: Result | undefined; + + x = OkAsync(5); + y = Ok(5); + assert((await x.transpose())!.equal(y)); + + x = OkAsync(undefined); + y = undefined; + assert((await x.transpose()) === y); + + x = OkAsync(null); + y = undefined; + assert((await x.transpose()) === y); + } + + await expect(examples()).resolves.not.toThrow(); + }); +}); + +describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.equal.name}\``, () => { + it('should check if itself equals to another result', async () => { + await expect(OkAsync(NaN).equal(Ok(NaN))).resolves.toBe(true); + await expect(OkAsync(NaN).equal(Promise.resolve(Ok(NaN)))).resolves.toBe(true); + await expect(OkAsync(NaN).equal(OkAsync(NaN))).resolves.toBe(true); + + await expect(ErrAsync('err').equal(Err('err'))).resolves.toBe(true); + await expect(ErrAsync('err').equal(Promise.resolve(Err('err')))).resolves.toBe(true); + await expect(ErrAsync('err').equal(ErrAsync('err'))).resolves.toBe(true); + + // simple false + + await expect(OkAsync(1).equal(Ok(2))).resolves.toBe(false); + await expect(OkAsync(1).equal(Promise.resolve(Ok(2)))).resolves.toBe(false); + await expect(OkAsync(1).equal(OkAsync(2))).resolves.toBe(false); + + await expect(OkAsync(1).equal(Ok('hello world'))).resolves.toBe(false); + await expect(OkAsync(1).equal(Promise.resolve(Ok('hello world')))).resolves.toBe(false); + await expect(OkAsync(1).equal(OkAsync('hello world'))).resolves.toBe(false); + + await expect(OkAsync(1).equal(Err('err'))).resolves.toBe(false); + await expect(OkAsync(1).equal(Promise.resolve(Err('err')))).resolves.toBe(false); + await expect(OkAsync(1).equal(ErrAsync('err'))).resolves.toBe(false); + + await expect(ErrAsync('err 1').equal(Err('err 2'))).resolves.toBe(false); + await expect(ErrAsync('err 1').equal(Promise.resolve(Err('err 2')))).resolves.toBe(false); + await expect(ErrAsync('err 1').equal(ErrAsync('err 2'))).resolves.toBe(false); + + await expect(ErrAsync('err 1').equal(Err(-1))).resolves.toBe(false); + await expect(ErrAsync('err 1').equal(Err(-1))).resolves.toBe(false); + await expect(ErrAsync('err 1').equal(ErrAsync(-1))).resolves.toBe(false); + + await expect(ErrAsync('error').equal(Ok(1))).resolves.toBe(false); + await expect(ErrAsync('error').equal(Promise.resolve(Ok(1)))).resolves.toBe(false); + await expect(ErrAsync('error').equal(OkAsync(1))).resolves.toBe(false); + + // nested true + + await expect(OkAsync(Ok(1)).equal(Ok(Ok(1)))).resolves.toBe(true); + await expect(OkAsync(Ok(1)).equal(Ok(OkAsync(1)))).resolves.toBe(true); + await expect(OkAsync(Ok(1)).equal(Promise.resolve(Ok(Ok(1))))).resolves.toBe(true); + await expect(OkAsync(Ok(1)).equal(Promise.resolve(Ok(OkAsync(1))))).resolves.toBe(true); + await expect(OkAsync(Ok(1)).equal(OkAsync(Ok(1)))).resolves.toBe(true); + await expect(OkAsync(Ok(1)).equal(OkAsync(Promise.resolve(Ok(1))))).resolves.toBe(true); + await expect(OkAsync(Ok(1)).equal(OkAsync(OkAsync(1)))).resolves.toBe(true); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(Ok(Ok(1)))).resolves.toBe(true); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(Ok(OkAsync(1)))).resolves.toBe(true); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(Promise.resolve(Ok(Ok(1))))).resolves.toBe(true); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(Promise.resolve(Ok(OkAsync(1))))).resolves.toBe(true); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(OkAsync(Ok(1)))).resolves.toBe(true); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(OkAsync(Promise.resolve(Ok(1))))).resolves.toBe(true); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(OkAsync(OkAsync(1)))).resolves.toBe(true); + await expect(OkAsync(OkAsync(1)).equal(Ok(Ok(1)))).resolves.toBe(true); + await expect(OkAsync(OkAsync(1)).equal(Ok(OkAsync(1)))).resolves.toBe(true); + await expect(OkAsync(OkAsync(1)).equal(Promise.resolve(Ok(Ok(1))))).resolves.toBe(true); + await expect(OkAsync(OkAsync(1)).equal(Promise.resolve(Ok(OkAsync(1))))).resolves.toBe(true); + await expect(OkAsync(OkAsync(1)).equal(OkAsync(Ok(1)))).resolves.toBe(true); + await expect(OkAsync(OkAsync(1)).equal(OkAsync(Promise.resolve(Ok(1))))).resolves.toBe(true); + await expect(OkAsync(OkAsync(1)).equal(OkAsync(OkAsync(1)))).resolves.toBe(true); + + await expect(OkAsync(Err('err')).equal(Ok(Err('err')))).resolves.toBe(true); + await expect(OkAsync(Err('err')).equal(Ok(ErrAsync('err')))).resolves.toBe(true); + await expect(OkAsync(Err('err')).equal(Promise.resolve(Ok(Err('err'))))).resolves.toBe(true); + await expect(OkAsync(Err('err')).equal(Promise.resolve(Ok(ErrAsync('err'))))).resolves.toBe(true); + await expect(OkAsync(Err('err')).equal(OkAsync(Err('err')))).resolves.toBe(true); + await expect(OkAsync(Err('err')).equal(OkAsync(Promise.resolve(Err('err'))))).resolves.toBe(true); + await expect(OkAsync(Err('err')).equal(OkAsync(ErrAsync('err')))).resolves.toBe(true); + await expect(OkAsync(Promise.resolve(Err('err'))).equal(Ok(Err('err')))).resolves.toBe(true); + await expect(OkAsync(Promise.resolve(Err('err'))).equal(Ok(ErrAsync('err')))).resolves.toBe(true); + await expect(OkAsync(Promise.resolve(Err('err'))).equal(Promise.resolve(Ok(Err('err'))))).resolves.toBe(true); + await expect(OkAsync(Promise.resolve(Err('err'))).equal(Promise.resolve(Ok(ErrAsync('err'))))).resolves.toBe( + true, + ); + await expect(OkAsync(Promise.resolve(Err('err'))).equal(OkAsync(Err('err')))).resolves.toBe(true); + await expect(OkAsync(Promise.resolve(Err('err'))).equal(OkAsync(Promise.resolve(Err('err'))))).resolves.toBe( + true, + ); + await expect(OkAsync(Promise.resolve(Err('err'))).equal(OkAsync(ErrAsync('err')))).resolves.toBe(true); + await expect(OkAsync(ErrAsync('err')).equal(Ok(Err('err')))).resolves.toBe(true); + await expect(OkAsync(ErrAsync('err')).equal(Ok(ErrAsync('err')))).resolves.toBe(true); + await expect(OkAsync(ErrAsync('err')).equal(Promise.resolve(Ok(Err('err'))))).resolves.toBe(true); + await expect(OkAsync(ErrAsync('err')).equal(Promise.resolve(Ok(ErrAsync('err'))))).resolves.toBe(true); + await expect(OkAsync(ErrAsync('err')).equal(OkAsync(Err('err')))).resolves.toBe(true); + await expect(OkAsync(ErrAsync('err')).equal(OkAsync(Promise.resolve(Err('err'))))).resolves.toBe(true); + await expect(OkAsync(ErrAsync('err')).equal(OkAsync(ErrAsync('err')))).resolves.toBe(true); + + await expect(ErrAsync(Err('err')).equal(Err(Err('err')))).resolves.toBe(true); + await expect(ErrAsync(Err('err')).equal(Err(ErrAsync('err')))).resolves.toBe(true); + await expect(ErrAsync(Err('err')).equal(Promise.resolve(Err(Err('err'))))).resolves.toBe(true); + await expect(ErrAsync(Err('err')).equal(Promise.resolve(Err(ErrAsync('err'))))).resolves.toBe(true); + await expect(ErrAsync(Err('err')).equal(ErrAsync(Err('err')))).resolves.toBe(true); + await expect(ErrAsync(Err('err')).equal(ErrAsync(Promise.resolve(Err('err'))))).resolves.toBe(true); + await expect(ErrAsync(Err('err')).equal(ErrAsync(ErrAsync('err')))).resolves.toBe(true); + await expect(ErrAsync(Promise.resolve(Err('err'))).equal(Err(Err('err')))).resolves.toBe(true); + await expect(ErrAsync(Promise.resolve(Err('err'))).equal(Err(ErrAsync('err')))).resolves.toBe(true); + await expect(ErrAsync(Promise.resolve(Err('err'))).equal(Promise.resolve(Err(Err('err'))))).resolves.toBe(true); + await expect(ErrAsync(Promise.resolve(Err('err'))).equal(Promise.resolve(Err(ErrAsync('err'))))).resolves.toBe( + true, + ); + await expect(ErrAsync(Promise.resolve(Err('err'))).equal(ErrAsync(Err('err')))).resolves.toBe(true); + await expect(ErrAsync(Promise.resolve(Err('err'))).equal(ErrAsync(Promise.resolve(Err('err'))))).resolves.toBe( + true, + ); + await expect(ErrAsync(Promise.resolve(Err('err'))).equal(ErrAsync(ErrAsync('err')))).resolves.toBe(true); + await expect(ErrAsync(ErrAsync('err')).equal(Err(Err('err')))).resolves.toBe(true); + await expect(ErrAsync(ErrAsync('err')).equal(Err(ErrAsync('err')))).resolves.toBe(true); + await expect(ErrAsync(ErrAsync('err')).equal(Promise.resolve(Err(Err('err'))))).resolves.toBe(true); + await expect(ErrAsync(ErrAsync('err')).equal(Promise.resolve(Err(ErrAsync('err'))))).resolves.toBe(true); + await expect(ErrAsync(ErrAsync('err')).equal(ErrAsync(Err('err')))).resolves.toBe(true); + await expect(ErrAsync(ErrAsync('err')).equal(ErrAsync(Promise.resolve(Err('err'))))).resolves.toBe(true); + await expect(ErrAsync(ErrAsync('err')).equal(ErrAsync(ErrAsync('err')))).resolves.toBe(true); + + await expect(ErrAsync(Ok(1)).equal(Err(Ok(1)))).resolves.toBe(true); + await expect(ErrAsync(Ok(1)).equal(Err(OkAsync(1)))).resolves.toBe(true); + await expect(ErrAsync(Ok(1)).equal(Promise.resolve(Err(Ok(1))))).resolves.toBe(true); + await expect(ErrAsync(Ok(1)).equal(Promise.resolve(Err(OkAsync(1))))).resolves.toBe(true); + await expect(ErrAsync(Ok(1)).equal(ErrAsync(Ok(1)))).resolves.toBe(true); + await expect(ErrAsync(Ok(1)).equal(ErrAsync(Promise.resolve(Ok(1))))).resolves.toBe(true); + await expect(ErrAsync(Ok(1)).equal(ErrAsync(OkAsync(1)))).resolves.toBe(true); + await expect(ErrAsync(Promise.resolve(Ok(1))).equal(Err(Ok(1)))).resolves.toBe(true); + await expect(ErrAsync(Promise.resolve(Ok(1))).equal(Err(OkAsync(1)))).resolves.toBe(true); + await expect(ErrAsync(Promise.resolve(Ok(1))).equal(Promise.resolve(Err(Ok(1))))).resolves.toBe(true); + await expect(ErrAsync(Promise.resolve(Ok(1))).equal(Promise.resolve(Err(OkAsync(1))))).resolves.toBe(true); + await expect(ErrAsync(Promise.resolve(Ok(1))).equal(ErrAsync(Ok(1)))).resolves.toBe(true); + await expect(ErrAsync(Promise.resolve(Ok(1))).equal(ErrAsync(Promise.resolve(Ok(1))))).resolves.toBe(true); + await expect(ErrAsync(Promise.resolve(Ok(1))).equal(ErrAsync(OkAsync(1)))).resolves.toBe(true); + await expect(ErrAsync(OkAsync(1)).equal(Err(Ok(1)))).resolves.toBe(true); + await expect(ErrAsync(OkAsync(1)).equal(Err(OkAsync(1)))).resolves.toBe(true); + await expect(ErrAsync(OkAsync(1)).equal(Promise.resolve(Err(Ok(1))))).resolves.toBe(true); + await expect(ErrAsync(OkAsync(1)).equal(Promise.resolve(Err(OkAsync(1))))).resolves.toBe(true); + await expect(ErrAsync(OkAsync(1)).equal(ErrAsync(Ok(1)))).resolves.toBe(true); + await expect(ErrAsync(OkAsync(1)).equal(ErrAsync(Promise.resolve(Ok(1))))).resolves.toBe(true); + await expect(ErrAsync(OkAsync(1)).equal(ErrAsync(OkAsync(1)))).resolves.toBe(true); + + // nested false + + await expect(OkAsync(Ok(1)).equal(Ok(Ok(2)))).resolves.toBe(false); + await expect(OkAsync(Ok(1)).equal(Ok(OkAsync(2)))).resolves.toBe(false); + await expect(OkAsync(Ok(1)).equal(Promise.resolve(Ok(Ok(2))))).resolves.toBe(false); + await expect(OkAsync(Ok(1)).equal(Promise.resolve(Ok(OkAsync(2))))).resolves.toBe(false); + await expect(OkAsync(Ok(1)).equal(OkAsync(Ok(2)))).resolves.toBe(false); + await expect(OkAsync(Ok(1)).equal(OkAsync(Promise.resolve(Ok(2))))).resolves.toBe(false); + await expect(OkAsync(Ok(1)).equal(OkAsync(OkAsync(2)))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(Ok(Ok(2)))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(Ok(OkAsync(2)))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(Promise.resolve(Ok(Ok(2))))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(Promise.resolve(Ok(OkAsync(2))))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(OkAsync(Ok(2)))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(OkAsync(Promise.resolve(Ok(2))))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(OkAsync(OkAsync(2)))).resolves.toBe(false); + await expect(OkAsync(OkAsync(1)).equal(Ok(Ok(2)))).resolves.toBe(false); + await expect(OkAsync(OkAsync(1)).equal(Ok(OkAsync(2)))).resolves.toBe(false); + await expect(OkAsync(OkAsync(1)).equal(Promise.resolve(Ok(Ok(2))))).resolves.toBe(false); + await expect(OkAsync(OkAsync(1)).equal(Promise.resolve(Ok(OkAsync(2))))).resolves.toBe(false); + await expect(OkAsync(OkAsync(1)).equal(OkAsync(Ok(2)))).resolves.toBe(false); + await expect(OkAsync(OkAsync(1)).equal(OkAsync(Promise.resolve(Ok(2))))).resolves.toBe(false); + await expect(OkAsync(OkAsync(1)).equal(OkAsync(OkAsync(2)))).resolves.toBe(false); + + await expect(OkAsync(Ok(1)).equal(Ok(Err('err')))).resolves.toBe(false); + await expect(OkAsync(Ok(1)).equal(Ok(ErrAsync('err')))).resolves.toBe(false); + await expect(OkAsync(Ok(1)).equal(Promise.resolve(Ok(Err('err'))))).resolves.toBe(false); + await expect(OkAsync(Ok(1)).equal(Promise.resolve(Ok(ErrAsync('err'))))).resolves.toBe(false); + await expect(OkAsync(Ok(1)).equal(OkAsync(Err('err')))).resolves.toBe(false); + await expect(OkAsync(Ok(1)).equal(OkAsync(Promise.resolve(Err('err'))))).resolves.toBe(false); + await expect(OkAsync(Ok(1)).equal(OkAsync(ErrAsync('err')))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(Ok(Err('err')))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(Ok(ErrAsync('err')))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(Promise.resolve(Ok(Err('err'))))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(Promise.resolve(Ok(ErrAsync('err'))))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(OkAsync(Err('err')))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(OkAsync(Promise.resolve(Err('err'))))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok(1))).equal(OkAsync(ErrAsync('err')))).resolves.toBe(false); + await expect(OkAsync(OkAsync(1)).equal(Ok(Err('err')))).resolves.toBe(false); + await expect(OkAsync(OkAsync(1)).equal(Ok(ErrAsync('err')))).resolves.toBe(false); + await expect(OkAsync(OkAsync(1)).equal(Promise.resolve(Ok(Err('err'))))).resolves.toBe(false); + await expect(OkAsync(OkAsync(1)).equal(Promise.resolve(Ok(ErrAsync('err'))))).resolves.toBe(false); + await expect(OkAsync(OkAsync(1)).equal(OkAsync(Err('err')))).resolves.toBe(false); + await expect(OkAsync(OkAsync(1)).equal(OkAsync(Promise.resolve(Err('err'))))).resolves.toBe(false); + await expect(OkAsync(OkAsync(1)).equal(OkAsync(ErrAsync('err')))).resolves.toBe(false); + + await expect(OkAsync(Err('err')).equal(Ok(Ok(1)))).resolves.toBe(false); + await expect(OkAsync(Err('err')).equal(Ok(OkAsync(1)))).resolves.toBe(false); + await expect(OkAsync(Err('err')).equal(Promise.resolve(Ok(Ok(1))))).resolves.toBe(false); + await expect(OkAsync(Err('err')).equal(Promise.resolve(Ok(OkAsync(1))))).resolves.toBe(false); + await expect(OkAsync(Err('err')).equal(OkAsync(Ok(1)))).resolves.toBe(false); + await expect(OkAsync(Err('err')).equal(OkAsync(Promise.resolve(Ok(1))))).resolves.toBe(false); + await expect(OkAsync(Err('err')).equal(OkAsync(OkAsync(1)))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Err('err'))).equal(Ok(Ok(1)))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Err('err'))).equal(Ok(OkAsync(1)))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Err('err'))).equal(Promise.resolve(Ok(Ok(1))))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Err('err'))).equal(Promise.resolve(Ok(OkAsync(1))))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Err('err'))).equal(OkAsync(Ok(1)))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Err('err'))).equal(OkAsync(Promise.resolve(Ok(1))))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Err('err'))).equal(OkAsync(OkAsync(1)))).resolves.toBe(false); + await expect(OkAsync(ErrAsync('err')).equal(Ok(Ok(1)))).resolves.toBe(false); + await expect(OkAsync(ErrAsync('err')).equal(Ok(OkAsync(1)))).resolves.toBe(false); + await expect(OkAsync(ErrAsync('err')).equal(Promise.resolve(Ok(Ok(1))))).resolves.toBe(false); + await expect(OkAsync(ErrAsync('err')).equal(Promise.resolve(Ok(OkAsync(1))))).resolves.toBe(false); + await expect(OkAsync(ErrAsync('err')).equal(OkAsync(Ok(1)))).resolves.toBe(false); + await expect(OkAsync(ErrAsync('err')).equal(OkAsync(Promise.resolve(Ok(1))))).resolves.toBe(false); + await expect(OkAsync(ErrAsync('err')).equal(OkAsync(OkAsync(1)))).resolves.toBe(false); + + await expect(ErrAsync(Err('err')).equal(Err(Ok(1)))).resolves.toBe(false); + await expect(ErrAsync(Err('err')).equal(Err(OkAsync(1)))).resolves.toBe(false); + await expect(ErrAsync(Err('err')).equal(Promise.resolve(Err(Ok(1))))).resolves.toBe(false); + await expect(ErrAsync(Err('err')).equal(Promise.resolve(Err(OkAsync(1))))).resolves.toBe(false); + await expect(ErrAsync(Err('err')).equal(ErrAsync(Ok(1)))).resolves.toBe(false); + await expect(ErrAsync(Err('err')).equal(ErrAsync(Promise.resolve(Ok(1))))).resolves.toBe(false); + await expect(ErrAsync(Err('err')).equal(ErrAsync(OkAsync(1)))).resolves.toBe(false); + await expect(ErrAsync(Promise.resolve(Err('err'))).equal(Err(Ok(1)))).resolves.toBe(false); + await expect(ErrAsync(Promise.resolve(Err('err'))).equal(Err(OkAsync(1)))).resolves.toBe(false); + await expect(ErrAsync(Promise.resolve(Err('err'))).equal(Promise.resolve(Err(Ok(1))))).resolves.toBe(false); + await expect(ErrAsync(Promise.resolve(Err('err'))).equal(Promise.resolve(Err(OkAsync(1))))).resolves.toBe( + false, + ); + await expect(ErrAsync(Promise.resolve(Err('err'))).equal(ErrAsync(Ok(1)))).resolves.toBe(false); + await expect(ErrAsync(Promise.resolve(Err('err'))).equal(ErrAsync(Promise.resolve(Ok(1))))).resolves.toBe( + false, + ); + await expect(ErrAsync(Promise.resolve(Err('err'))).equal(ErrAsync(OkAsync(1)))).resolves.toBe(false); + await expect(ErrAsync(ErrAsync('err')).equal(Err(Ok(1)))).resolves.toBe(false); + await expect(ErrAsync(ErrAsync('err')).equal(Err(OkAsync(1)))).resolves.toBe(false); + await expect(ErrAsync(ErrAsync('err')).equal(Promise.resolve(Err(Ok(1))))).resolves.toBe(false); + await expect(ErrAsync(ErrAsync('err')).equal(Promise.resolve(Err(OkAsync(1))))).resolves.toBe(false); + await expect(ErrAsync(ErrAsync('err')).equal(ErrAsync(Ok(1)))).resolves.toBe(false); + await expect(ErrAsync(ErrAsync('err')).equal(ErrAsync(Promise.resolve(Ok(1))))).resolves.toBe(false); + await expect(ErrAsync(ErrAsync('err')).equal(ErrAsync(OkAsync(1)))).resolves.toBe(false); + + await expect(ErrAsync(Ok(1)).equal(Err(Err('err')))).resolves.toBe(false); + await expect(ErrAsync(Ok(1)).equal(Err(ErrAsync('err')))).resolves.toBe(false); + await expect(ErrAsync(Ok(1)).equal(Promise.resolve(Err(Err('err'))))).resolves.toBe(false); + await expect(ErrAsync(Ok(1)).equal(Promise.resolve(Err(ErrAsync('err'))))).resolves.toBe(false); + await expect(ErrAsync(Ok(1)).equal(ErrAsync(Err('err')))).resolves.toBe(false); + await expect(ErrAsync(Ok(1)).equal(ErrAsync(Promise.resolve(Err('err'))))).resolves.toBe(false); + await expect(ErrAsync(Ok(1)).equal(ErrAsync(ErrAsync('err')))).resolves.toBe(false); + await expect(ErrAsync(Promise.resolve(Ok(1))).equal(Err(Err('err')))).resolves.toBe(false); + await expect(ErrAsync(Promise.resolve(Ok(1))).equal(Err(ErrAsync('err')))).resolves.toBe(false); + await expect(ErrAsync(Promise.resolve(Ok(1))).equal(Promise.resolve(Err(Err('err'))))).resolves.toBe(false); + await expect(ErrAsync(Promise.resolve(Ok(1))).equal(Promise.resolve(Err(ErrAsync('err'))))).resolves.toBe( + false, + ); + await expect(ErrAsync(Promise.resolve(Ok(1))).equal(ErrAsync(Err('err')))).resolves.toBe(false); + await expect(ErrAsync(Promise.resolve(Ok(1))).equal(ErrAsync(Promise.resolve(Err('err'))))).resolves.toBe( + false, + ); + await expect(ErrAsync(Promise.resolve(Ok(1))).equal(ErrAsync(ErrAsync('err')))).resolves.toBe(false); + await expect(ErrAsync(OkAsync(1)).equal(Err(Err('err')))).resolves.toBe(false); + await expect(ErrAsync(OkAsync(1)).equal(Err(ErrAsync('err')))).resolves.toBe(false); + await expect(ErrAsync(OkAsync(1)).equal(Promise.resolve(Err(Err('err'))))).resolves.toBe(false); + await expect(ErrAsync(OkAsync(1)).equal(Promise.resolve(Err(ErrAsync('err'))))).resolves.toBe(false); + await expect(ErrAsync(OkAsync(1)).equal(ErrAsync(Err('err')))).resolves.toBe(false); + await expect(ErrAsync(OkAsync(1)).equal(ErrAsync(Promise.resolve(Err('err'))))).resolves.toBe(false); + await expect(ErrAsync(OkAsync(1)).equal(ErrAsync(ErrAsync('err')))).resolves.toBe(false); + + // object equality + + await expect(OkAsync([1]).equal(Ok([1]))).resolves.toBe(false); + await expect(OkAsync([1]).equal(Promise.resolve(Ok([1])))).resolves.toBe(false); + await expect(OkAsync([1]).equal(OkAsync([1]))).resolves.toBe(false); + + await expect(OkAsync({ foo: 1 }).equal(Ok({ foo: 1 }))).resolves.toBe(false); + await expect(OkAsync({ foo: 1 }).equal(Promise.resolve(Ok({ foo: 1 })))).resolves.toBe(false); + await expect(OkAsync({ foo: 1 }).equal(OkAsync({ foo: 1 }))).resolves.toBe(false); + + await expect(ErrAsync({ message: 'err' }).equal(Err({ message: 'err' }))).resolves.toBe(false); + await expect(ErrAsync({ message: 'err' }).equal(Promise.resolve(Err({ message: 'err' })))).resolves.toBe(false); + await expect(ErrAsync({ message: 'err' }).equal(ErrAsync({ message: 'err' }))).resolves.toBe(false); + + await expect(OkAsync(Ok([1])).equal(Ok(Ok([1])))).resolves.toBe(false); + await expect(OkAsync(Ok([1])).equal(Ok(OkAsync([1])))).resolves.toBe(false); + await expect(OkAsync(Ok([1])).equal(Promise.resolve(Ok(Ok([1]))))).resolves.toBe(false); + await expect(OkAsync(Ok([1])).equal(Promise.resolve(Ok(OkAsync([1]))))).resolves.toBe(false); + await expect(OkAsync(Ok([1])).equal(OkAsync(Ok([1])))).resolves.toBe(false); + await expect(OkAsync(Ok([1])).equal(OkAsync(Promise.resolve(Ok([1]))))).resolves.toBe(false); + await expect(OkAsync(Ok([1])).equal(OkAsync(OkAsync([1])))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok([1]))).equal(Ok(Ok([1])))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok([1]))).equal(Ok(OkAsync([1])))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok([1]))).equal(Promise.resolve(Ok(Ok([1]))))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok([1]))).equal(Promise.resolve(Ok(OkAsync([1]))))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok([1]))).equal(OkAsync(Ok([1])))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok([1]))).equal(OkAsync(Promise.resolve(Ok([1]))))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok([1]))).equal(OkAsync(OkAsync([1])))).resolves.toBe(false); + await expect(OkAsync(OkAsync([1])).equal(Ok(Ok([1])))).resolves.toBe(false); + await expect(OkAsync(OkAsync([1])).equal(Ok(OkAsync([1])))).resolves.toBe(false); + await expect(OkAsync(OkAsync([1])).equal(Promise.resolve(Ok(Ok([1]))))).resolves.toBe(false); + await expect(OkAsync(OkAsync([1])).equal(Promise.resolve(Ok(OkAsync([1]))))).resolves.toBe(false); + await expect(OkAsync(OkAsync([1])).equal(OkAsync(Ok([1])))).resolves.toBe(false); + await expect(OkAsync(OkAsync([1])).equal(OkAsync(Promise.resolve(Ok([1]))))).resolves.toBe(false); + await expect(OkAsync(OkAsync([1])).equal(OkAsync(OkAsync([1])))).resolves.toBe(false); + + await expect(OkAsync(Ok({ foo: 1 })).equal(Ok(Ok({ foo: 1 })))).resolves.toBe(false); + await expect(OkAsync(Ok({ foo: 1 })).equal(Ok(OkAsync({ foo: 1 })))).resolves.toBe(false); + await expect(OkAsync(Ok({ foo: 1 })).equal(Promise.resolve(Ok(Ok({ foo: 1 }))))).resolves.toBe(false); + await expect(OkAsync(Ok({ foo: 1 })).equal(Promise.resolve(Ok(OkAsync({ foo: 1 }))))).resolves.toBe(false); + await expect(OkAsync(Ok({ foo: 1 })).equal(OkAsync(Ok({ foo: 1 })))).resolves.toBe(false); + await expect(OkAsync(Ok({ foo: 1 })).equal(OkAsync(Promise.resolve(Ok({ foo: 1 }))))).resolves.toBe(false); + await expect(OkAsync(Ok({ foo: 1 })).equal(OkAsync(OkAsync({ foo: 1 })))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok({ foo: 1 }))).equal(Ok(Ok({ foo: 1 })))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok({ foo: 1 }))).equal(Ok(OkAsync({ foo: 1 })))).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok({ foo: 1 }))).equal(Promise.resolve(Ok(Ok({ foo: 1 }))))).resolves.toBe( + false, + ); + await expect( + OkAsync(Promise.resolve(Ok({ foo: 1 }))).equal(Promise.resolve(Ok(OkAsync({ foo: 1 })))), + ).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok({ foo: 1 }))).equal(OkAsync(Ok({ foo: 1 })))).resolves.toBe(false); + await expect( + OkAsync(Promise.resolve(Ok({ foo: 1 }))).equal(OkAsync(Promise.resolve(Ok({ foo: 1 })))), + ).resolves.toBe(false); + await expect(OkAsync(Promise.resolve(Ok({ foo: 1 }))).equal(OkAsync(OkAsync({ foo: 1 })))).resolves.toBe(false); + await expect(OkAsync(OkAsync({ foo: 1 })).equal(Ok(Ok({ foo: 1 })))).resolves.toBe(false); + await expect(OkAsync(OkAsync({ foo: 1 })).equal(Ok(OkAsync({ foo: 1 })))).resolves.toBe(false); + await expect(OkAsync(OkAsync({ foo: 1 })).equal(Promise.resolve(Ok(Ok({ foo: 1 }))))).resolves.toBe(false); + await expect(OkAsync(OkAsync({ foo: 1 })).equal(Promise.resolve(Ok(OkAsync({ foo: 1 }))))).resolves.toBe(false); + await expect(OkAsync(OkAsync({ foo: 1 })).equal(OkAsync(Ok({ foo: 1 })))).resolves.toBe(false); + await expect(OkAsync(OkAsync({ foo: 1 })).equal(OkAsync(Promise.resolve(Ok({ foo: 1 }))))).resolves.toBe(false); + await expect(OkAsync(OkAsync({ foo: 1 })).equal(OkAsync(OkAsync({ foo: 1 })))).resolves.toBe(false); + + await expect(ErrAsync(Err({ message: 'err' })).equal(Err(Err({ message: 'err' })))).resolves.toBe(false); + await expect(ErrAsync(Err({ message: 'err' })).equal(Err(ErrAsync({ message: 'err' })))).resolves.toBe(false); + await expect( + ErrAsync(Err({ message: 'err' })).equal(Promise.resolve(Err(Err({ message: 'err' })))), + ).resolves.toBe(false); + await expect( + ErrAsync(Err({ message: 'err' })).equal(Promise.resolve(Err(ErrAsync({ message: 'err' })))), + ).resolves.toBe(false); + await expect(ErrAsync(Err({ message: 'err' })).equal(ErrAsync(Err({ message: 'err' })))).resolves.toBe(false); + await expect( + ErrAsync(Err({ message: 'err' })).equal(ErrAsync(Promise.resolve(Err({ message: 'err' })))), + ).resolves.toBe(false); + await expect(ErrAsync(Err({ message: 'err' })).equal(ErrAsync(ErrAsync({ message: 'err' })))).resolves.toBe( + false, + ); + await expect( + ErrAsync(Promise.resolve(Err({ message: 'err' }))).equal(Err(Err({ message: 'err' }))), + ).resolves.toBe(false); + await expect( + ErrAsync(Promise.resolve(Err({ message: 'err' }))).equal(Err(ErrAsync({ message: 'err' }))), + ).resolves.toBe(false); + await expect( + ErrAsync(Promise.resolve(Err({ message: 'err' }))).equal(Promise.resolve(Err(Err({ message: 'err' })))), + ).resolves.toBe(false); + await expect( + ErrAsync(Promise.resolve(Err({ message: 'err' }))).equal( + Promise.resolve(Err(ErrAsync({ message: 'err' }))), + ), + ).resolves.toBe(false); + await expect( + ErrAsync(Promise.resolve(Err({ message: 'err' }))).equal(ErrAsync(Err({ message: 'err' }))), + ).resolves.toBe(false); + await expect( + ErrAsync(Promise.resolve(Err({ message: 'err' }))).equal( + ErrAsync(Promise.resolve(Err({ message: 'err' }))), + ), + ).resolves.toBe(false); + await expect( + ErrAsync(Promise.resolve(Err({ message: 'err' }))).equal(ErrAsync(ErrAsync({ message: 'err' }))), + ).resolves.toBe(false); + await expect(ErrAsync(ErrAsync({ message: 'err' })).equal(Err(Err({ message: 'err' })))).resolves.toBe(false); + await expect(ErrAsync(ErrAsync({ message: 'err' })).equal(Err(ErrAsync({ message: 'err' })))).resolves.toBe( + false, + ); + await expect( + ErrAsync(ErrAsync({ message: 'err' })).equal(Promise.resolve(Err(Err({ message: 'err' })))), + ).resolves.toBe(false); + await expect( + ErrAsync(ErrAsync({ message: 'err' })).equal(Promise.resolve(Err(ErrAsync({ message: 'err' })))), + ).resolves.toBe(false); + await expect(ErrAsync(ErrAsync({ message: 'err' })).equal(ErrAsync(Err({ message: 'err' })))).resolves.toBe( + false, + ); + await expect( + ErrAsync(ErrAsync({ message: 'err' })).equal(ErrAsync(Promise.resolve(Err({ message: 'err' })))), + ).resolves.toBe(false); + await expect( + ErrAsync(ErrAsync({ message: 'err' })).equal(ErrAsync(ErrAsync({ message: 'err' }))), + ).resolves.toBe(false); + }); +}); diff --git a/src/__tests__/_helpers.ts b/src/__tests__/_helpers.ts index d240f76..b6da604 100644 --- a/src/__tests__/_helpers.ts +++ b/src/__tests__/_helpers.ts @@ -1,26 +1,54 @@ import { expect } from '@jest/globals'; import type { Result } from '../Result'; +import type { ResultAsync } from '../ResultAsync'; import type { RustlikeResult } from '../RustlikeResult'; +import type { RustlikeResultAsync } from '../RustlikeResultAsync'; import type { ResultType } from '../types.internal'; -interface ResultTestDescription { +type ResultInstance = RustlikeResult; + +interface ExpectedResult { type: ResultType; value: unknown; error: unknown; } -export function expectResult(result: Result, description: ResultTestDescription) { - const _result = result as RustlikeResult; - expect(_result['_type']).toBe(description.type); - if (typeof description.value === 'object' && description.value !== null) { - expect(_result['_value']).toStrictEqual(description.value); +export function expectResult(result: Result, expected: ExpectedResult) { + const _result = result as ResultInstance; + + expect(_result['_type']).toBe(expected.type); + + if (typeof expected.value === 'object' && expected.value !== null) { + expect(_result['_value']).toStrictEqual(expected.value); } else { - expect(_result['_value']).toBe(description.value); + expect(_result['_value']).toBe(expected.value); } - if (typeof description.error === 'object' && description.error !== null) { - expect(_result['_error']).toStrictEqual(description.error); + + if (typeof expected.error === 'object' && expected.error !== null) { + expect(_result['_error']).toStrictEqual(expected.error); + } else { + expect(_result['_error']).toBe(expected.error); + } +} + +export async function expectResultAsync(result: ResultAsync, expected: ExpectedResult) { + const instance = result as RustlikeResultAsync; + + const type = ((await instance) as ResultInstance)['_type']; + expect(type).toBe(expected.type); + + const value = ((await instance) as ResultInstance)['_value']; + if (typeof expected.value === 'object' && expected.value !== null) { + expect(value).toStrictEqual(expected.value); + } else { + expect(value).toBe(expected.value); + } + + const error = ((await instance) as ResultInstance)['_error']; + if (typeof expected.error === 'object' && expected.error !== null) { + expect(error).toStrictEqual(expected.error); } else { - expect(_result['_error']).toBe(description.error); + expect(error).toBe(expected.error); } } diff --git a/src/__tests__/factoryAsync.test.ts b/src/__tests__/factoryAsync.test.ts new file mode 100644 index 0000000..1ea3805 --- /dev/null +++ b/src/__tests__/factoryAsync.test.ts @@ -0,0 +1,30 @@ +import { describe, it } from '@jest/globals'; + +import { ErrAsync, OkAsync } from '../factoryAsync'; +import type { ResultAsync } from '../ResultAsync'; + +import { expectResultAsync } from './_helpers'; + +describe(`Test fn \`${OkAsync.name}\``, () => { + it('should create `OkAsync` result', async () => { + const result1 = OkAsync(1); + const result2 = OkAsync(1); + const result3: ResultAsync = OkAsync(2); + + await expectResultAsync(result1, { type: 'ok', value: 1, error: undefined }); + await expectResultAsync(result2, { type: 'ok', value: 1, error: undefined }); + await expectResultAsync(result3, { type: 'ok', value: 2, error: undefined }); + }); +}); + +describe(`Test fn \`${ErrAsync.name}\``, () => { + it('should create `ErrAsync` result', async () => { + const result1 = ErrAsync('Some error message'); + const result2 = ErrAsync('Some error message'); + const result3: ResultAsync = ErrAsync('Some error message'); + + await expectResultAsync(result1, { type: 'err', value: undefined, error: 'Some error message' }); + await expectResultAsync(result2, { type: 'err', value: undefined, error: 'Some error message' }); + await expectResultAsync(result3, { type: 'err', value: undefined, error: 'Some error message' }); + }); +}); diff --git a/src/factoryAsync.ts b/src/factoryAsync.ts new file mode 100644 index 0000000..5a8ab11 --- /dev/null +++ b/src/factoryAsync.ts @@ -0,0 +1,40 @@ +import { Err, Ok } from './factory'; +import type { ResultAsync } from './ResultAsync'; +import { RustlikeResultAsync } from './RustlikeResultAsync'; + +/** + * Creates a `Result` that contains the success value. + * + * Examples: + * ```ts + * const result1 = OkAsync(1); + * const result2 = OkAsync(1); + * const result3: ResultAsync = OkAsync(2); + * ``` + */ +export function OkAsync(value: T | Promise): ResultAsync { + return new RustlikeResultAsync(Promise.resolve(value).then(Ok)); +} + +/** + * Creates a `Result` that contains the error value. + * + * Examples: + * ```ts + * const result = ErrAsync('Some error message'); + * ``` + */ +export function ErrAsync(error: E | Promise): ResultAsync; +/** + * Creates a `ResultAsync` that contains the error value. + * + * Examples: + * ```ts + * const result1 = ErrAsync('Some error message'); + * const result2: ResultAsync = ErrAsync('Some error message'); + * ``` + */ +export function ErrAsync(error: E | Promise): ResultAsync; +export function ErrAsync(error: E | Promise): ResultAsync { + return new RustlikeResultAsync(Promise.resolve(error).then(Err)); +} diff --git a/src/index.ts b/src/index.ts index e719260..4d02702 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,7 @@ export * from './factory'; +export * from './factoryAsync'; export * from './json/simple'; export * from './json/types'; export * from './Result'; +export * from './ResultAsync'; export * from './resultify'; From f6ab1a2480bf89c381d769c2c94b2bff63b28ab7 Mon Sep 17 00:00:00 2001 From: yifanwww Date: Sat, 8 Jun 2024 01:33:55 +0800 Subject: [PATCH 2/4] feat: add Result.async() method to convert sync result to async result --- src/Result.ts | 28 ++++++++++++++++++++++++++++ src/RustlikeResult.ts | 7 +++++++ src/RustlikeResultAsync.ts | 1 + src/__tests__/RustlikeResult.test.ts | 15 ++++++++++++++- src/factory.ts | 1 + 5 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/Result.ts b/src/Result.ts index 980df78..41536ef 100644 --- a/src/Result.ts +++ b/src/Result.ts @@ -1,3 +1,4 @@ +import type { ResultAsync } from './ResultAsync'; import type { Optional } from './types.internal'; /** @@ -53,6 +54,8 @@ export interface Result { isOkAnd(fn: (value: T) => boolean): boolean; /** + * @deprecated Please use `.async().isOkAnd(fn)` instead. + * * Asynchronously returns `true` if the result is `Ok` and the value inside of it matches a predicate. * * Examples: @@ -121,6 +124,8 @@ export interface Result { isErrAnd(fn: (err: E) => boolean): boolean; /** + * @deprecated Please use `.async().isErrAnd(fn)` instead. + * * Asynchronously returns `true` if the result is `Err` and the value inside of it matches a predicate. * * Examples: @@ -205,6 +210,8 @@ export interface Result { map(op: (value: T) => U): Result; /** + * @deprecated Please use `.async().map(op)` instead. + * * Asynchronously maps a `Result` to `Result` by applying a function to a contained `Ok` value, * leaving an `Err` value untouched. * @@ -247,6 +254,8 @@ export interface Result { mapOr(fallback: U, map: (value: T) => U): U; /** + * @deprecated Please use `.async().mapOr(fallback, map)` instead. + * * Asynchronously returns the provided `fallback` (if `Err`), * or applies a function to the contained value (if `Ok`). * @@ -295,6 +304,8 @@ export interface Result { mapOrElse(fallback: (err: E) => U, map: (value: T) => U): U; /** + * @deprecated Please use `.async().mapOrElse(fallback, map)` instead. + * * Asynchronously maps a `Result` to `U` by applying fallback function `fallback` to a contained `Err` value, * or function `map` to a contained `Ok` value. * @@ -338,6 +349,8 @@ export interface Result { mapErr(op: (err: E) => F): Result; /** + * @deprecated Please use `.async().mapErr(op)` instead. + * * Asynchronously maps a `Result` to `Result` by applying a function to a contained `Err` value, * leaving an `Ok` value untouched. * @@ -377,6 +390,8 @@ export interface Result { inspect(fn: (value: T) => void): this; /** + * @deprecated Please use `.async().inspect(fn)` instead. + * * Asynchronously calls the provided closure with a reference to the contained value if `Ok`. * * Examples: @@ -418,6 +433,8 @@ export interface Result { inspectErr(fn: (err: E) => void): this; /** + * @deprecated Please use `.async().inspectErr(fn)` instead. + * * Asynchronously calls the provided closure with a reference to the contained value if `Err`. * * Examples: @@ -565,6 +582,8 @@ export interface Result { unwrapOrElse(op: (err: E) => T): T; /** + * @deprecated Please use `.async().unwrapOrElse(op)` instead. + * * Asynchronously returns the contained `Ok` value or computes it from a closure. * * Examples: @@ -688,6 +707,8 @@ export interface Result { andThen(op: (value: T) => Result): Result; /** + * @deprecated Please use `.async().andThen(op)` instead. + * * Asynchronously calls `op` if itself is `Ok`, otherwise returns the `Err` value of itself. * * This function can be used for control flow based on `Result` values. @@ -777,6 +798,8 @@ export interface Result { orElse(op: (err: E) => Result): Result; /** + * @deprecated Please use `.async().orElse(op)` instead. + * * Asynchronously calls `op` if the result is `Err`, otherwise returns the `Ok` value of self. * * This function can be used for control flow based on `Result` values. @@ -846,4 +869,9 @@ export interface Result { * Returns `true` if `self` equals to `other`. */ equal(other: Result): boolean; + + /** + * Converts this result to an async `Result` so it can work in asynchronous code. + */ + async(): ResultAsync; } diff --git a/src/RustlikeResult.ts b/src/RustlikeResult.ts index 05de4b7..202351c 100644 --- a/src/RustlikeResult.ts +++ b/src/RustlikeResult.ts @@ -1,4 +1,7 @@ import type { Result } from './Result'; +import type { ResultAsync } from './ResultAsync'; +// eslint-disable-next-line import/no-cycle +import { RustlikeResultAsync } from './RustlikeResultAsync'; import type { Optional, ResultType } from './types.internal'; /** @@ -226,4 +229,8 @@ export class RustlikeResult implements Result { ? RustlikeResult._equal(this._value, other.unwrapUnchecked()) : RustlikeResult._equal(this._error, other.unwrapErrUnchecked()); } + + async(): ResultAsync { + return new RustlikeResultAsync(this); + } } diff --git a/src/RustlikeResultAsync.ts b/src/RustlikeResultAsync.ts index 1ce27f1..f37f631 100644 --- a/src/RustlikeResultAsync.ts +++ b/src/RustlikeResultAsync.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line import/no-cycle import { Err, Ok } from './factory'; import type { Result } from './Result'; import type { ResultAsync } from './ResultAsync'; diff --git a/src/__tests__/RustlikeResult.test.ts b/src/__tests__/RustlikeResult.test.ts index f9c4b31..a714913 100644 --- a/src/__tests__/RustlikeResult.test.ts +++ b/src/__tests__/RustlikeResult.test.ts @@ -5,8 +5,9 @@ import { Err, Ok } from '../factory'; import type { Result } from '../Result'; import { resultify } from '../resultify'; import { RustlikeResult } from '../RustlikeResult'; +import { RustlikeResultAsync } from '../RustlikeResultAsync'; -import { expectResult } from './_helpers'; +import { expectResult, expectResultAsync } from './_helpers'; function panicFn1(): never { throw new Error('error'); @@ -1572,3 +1573,15 @@ describe(`Test method \`${RustlikeResult.name}.prototype.${RustlikeResult.protot expect(Err(Err({ message: 'err' })).equal(Err(Err({ message: 'err' })))).toBe(false); }); }); + +describe(`Test method \`${RustlikeResult.name}.prototype.${RustlikeResult.prototype.async.name}\``, () => { + it('should convert a result to an async result', async () => { + const okResult = Ok(1).async(); + expect(okResult).toBeInstanceOf(RustlikeResultAsync); + await expectResultAsync(okResult, { type: 'ok', value: 1, error: undefined }); + + const errResult = Err('Some error message').async(); + expect(errResult).toBeInstanceOf(RustlikeResultAsync); + await expectResultAsync(errResult, { type: 'err', value: undefined, error: 'Some error message' }); + }); +}); diff --git a/src/factory.ts b/src/factory.ts index deafc10..14e6663 100644 --- a/src/factory.ts +++ b/src/factory.ts @@ -1,4 +1,5 @@ import type { Result } from './Result'; +// eslint-disable-next-line import/no-cycle import { RustlikeResult } from './RustlikeResult'; /** From 3cbbabff9eeff2d4f2de78785ea02ae9cb678ea0 Mon Sep 17 00:00:00 2001 From: yifanwww Date: Sat, 8 Jun 2024 02:20:12 +0800 Subject: [PATCH 3/4] feat: add resultifyAsync, resultifySync, resultifyPromise --- src/__tests__/resultify.test.ts | 108 ++++++++++++++++++- src/resultify.ts | 184 +++++++++++++++++++++++++++----- 2 files changed, 259 insertions(+), 33 deletions(-) diff --git a/src/__tests__/resultify.test.ts b/src/__tests__/resultify.test.ts index d5ae22e..3847db7 100644 --- a/src/__tests__/resultify.test.ts +++ b/src/__tests__/resultify.test.ts @@ -1,6 +1,8 @@ import { describe, expect, it } from '@jest/globals'; -import { resultify } from '../resultify'; +import { resultify, resultifyAsync, resultifyPromise, resultifySync } from '../resultify'; +import { RustlikeResult } from '../RustlikeResult'; +import { RustlikeResultAsync } from '../RustlikeResultAsync'; function syncFn(throws: boolean) { if (throws) { @@ -24,6 +26,8 @@ describe(`Test fn \`${resultify.name}\``, () => { const [result1, result2] = await Promise.all([fn(false), fn(true)]); + expect(result1).toBeInstanceOf(RustlikeResult); + expect(result2).toBeInstanceOf(RustlikeResult); expect(result1.isOk()).toBe(true); expect(result1.unwrap()).toBe('hello world'); expect(result2.isErr()).toBe(true); @@ -36,6 +40,8 @@ describe(`Test fn \`${resultify.name}\``, () => { const [result1, result2] = await Promise.all([fn(false), fn(true)]); + expect(result1).toBeInstanceOf(RustlikeResult); + expect(result2).toBeInstanceOf(RustlikeResult); expect(result1.isOk()).toBe(true); expect(result1.unwrap()).toBe('hello world'); expect(result2.isErr()).toBe(true); @@ -48,6 +54,8 @@ describe(`Test fn \`${resultify.name}\``, () => { const [result1, result2] = await Promise.all([fn(false), fn(true)]); + expect(result1).toBeInstanceOf(RustlikeResult); + expect(result2).toBeInstanceOf(RustlikeResult); expect(result1.isOk()).toBe(true); expect(result1.unwrap()).toBe('hello world'); expect(result2.isErr()).toBe(true); @@ -60,6 +68,8 @@ describe(`Test fn \`${resultify.name}\``, () => { const [result1, result2] = await Promise.all([fn(false), fn(true)]); + expect(result1).toBeInstanceOf(RustlikeResult); + expect(result2).toBeInstanceOf(RustlikeResult); expect(result1.isOk()).toBe(true); expect(result1.unwrap()).toBe('hello world'); expect(result2.isErr()).toBe(true); @@ -68,13 +78,77 @@ describe(`Test fn \`${resultify.name}\``, () => { }); }); -describe(`Test fn \`${resultify.sync.name}\``, () => { +describe(`Test fn \`${resultifyAsync.name}\``, () => { + it('should resultify a sync function', async () => { + const fn = resultifyAsync(syncFn); + + const result1 = fn(false); + const result2 = fn(true); + + expect(result1).toBeInstanceOf(RustlikeResultAsync); + expect(result2).toBeInstanceOf(RustlikeResultAsync); + await expect(result1.isOk()).resolves.toBe(true); + await expect(result1.unwrap()).resolves.toBe('hello world'); + await expect(result2.isErr()).resolves.toBe(true); + await expect(result2.unwrapErr()).resolves.toBeInstanceOf(Error); + expect(((await result2.unwrapErr()) as Error).message).toBe('Some error message'); + }); + + it('should resultify an async function', async () => { + const fn = resultifyAsync(asyncFn); + + const result1 = fn(false); + const result2 = fn(true); + + expect(result1).toBeInstanceOf(RustlikeResultAsync); + expect(result2).toBeInstanceOf(RustlikeResultAsync); + await expect(result1.isOk()).resolves.toBe(true); + await expect(result1.unwrap()).resolves.toBe('hello world'); + await expect(result2.isErr()).resolves.toBe(true); + await expect(result2.unwrapErr()).resolves.toBeInstanceOf(Error); + expect(((await result2.unwrapErr()) as Error).message).toBe('Some error message'); + }); + + it('should resultify a sync function with error type specified', async () => { + const fn = resultifyAsync()(syncFn); + + const result1 = fn(false); + const result2 = fn(true); + + expect(result1).toBeInstanceOf(RustlikeResultAsync); + expect(result2).toBeInstanceOf(RustlikeResultAsync); + await expect(result1.isOk()).resolves.toBe(true); + await expect(result1.unwrap()).resolves.toBe('hello world'); + await expect(result2.isErr()).resolves.toBe(true); + await expect(result2.unwrapErr()).resolves.toBeInstanceOf(Error); + expect((await result2.unwrapErr()).message).toBe('Some error message'); + }); + + it('should resultify an async function with error type specified', async () => { + const fn = resultifyAsync()(asyncFn); + + const result1 = fn(false); + const result2 = fn(true); + + expect(result1).toBeInstanceOf(RustlikeResultAsync); + expect(result2).toBeInstanceOf(RustlikeResultAsync); + await expect(result1.isOk()).resolves.toBe(true); + await expect(result1.unwrap()).resolves.toBe('hello world'); + await expect(result2.isErr()).resolves.toBe(true); + await expect(result2.unwrapErr()).resolves.toBeInstanceOf(Error); + expect((await result2.unwrapErr()).message).toBe('Some error message'); + }); +}); + +describe(`Test fn \`${resultifySync.name}\``, () => { it('should resultify a sync function', () => { - const fn = resultify.sync(syncFn); + const fn = resultifySync(syncFn); const result1 = fn(false); const result2 = fn(true); + expect(result1).toBeInstanceOf(RustlikeResult); + expect(result2).toBeInstanceOf(RustlikeResult); expect(result1.isOk()).toBe(true); expect(result1.unwrap()).toBe('hello world'); expect(result2.isErr()).toBe(true); @@ -83,17 +157,23 @@ describe(`Test fn \`${resultify.sync.name}\``, () => { }); it('should resultify a sync function with error type specified', () => { - const fn = resultify.sync()(syncFn); + const fn = resultifySync()(syncFn); const result1 = fn(false); const result2 = fn(true); + expect(result1).toBeInstanceOf(RustlikeResult); + expect(result2).toBeInstanceOf(RustlikeResult); expect(result1.isOk()).toBe(true); expect(result1.unwrap()).toBe('hello world'); expect(result2.isErr()).toBe(true); expect(result2.unwrapErr()).toBeInstanceOf(Error); expect(result2.unwrapErr().message).toBe('Some error message'); }); + + it('should be the same as resultify.sync', () => { + expect(resultifySync).toBe(resultify.sync); + }); }); describe(`Test fn \`${resultify.promise.name}\``, () => { @@ -106,6 +186,8 @@ describe(`Test fn \`${resultify.promise.name}\``, () => { resultify.promise(promise2), ]); + expect(result1).toBeInstanceOf(RustlikeResult); + expect(result2).toBeInstanceOf(RustlikeResult); expect(result1.isOk()).toBe(true); expect(result1.unwrap()).toBe('hello world'); expect(result2.isErr()).toBe(true); @@ -113,3 +195,21 @@ describe(`Test fn \`${resultify.promise.name}\``, () => { expect(result2.unwrapErr().message).toBe('Some error message'); }); }); + +describe(`Test fn \`${resultifyPromise.name}\``, () => { + it('should resultify a promise', async () => { + const promise1 = asyncFn(false); + const promise2 = asyncFn(true); + + const result1 = resultifyPromise(promise1); + const result2 = resultifyPromise(promise2); + + expect(result1).toBeInstanceOf(RustlikeResultAsync); + expect(result2).toBeInstanceOf(RustlikeResultAsync); + await expect(result1.isOk()).resolves.toBe(true); + await expect(result1.unwrap()).resolves.toBe('hello world'); + await expect(result2.isErr()).resolves.toBe(true); + await expect(result2.unwrapErr()).resolves.toBeInstanceOf(Error); + expect((await result2.unwrapErr()).message).toBe('Some error message'); + }); +}); diff --git a/src/resultify.ts b/src/resultify.ts index f89d0be..55184d0 100644 --- a/src/resultify.ts +++ b/src/resultify.ts @@ -1,26 +1,37 @@ import { Err, Ok } from './factory'; import type { Result } from './Result'; +import type { ResultAsync } from './ResultAsync'; +import { RustlikeResultAsync } from './RustlikeResultAsync'; type NoVoid = T extends void ? undefined : T; +async function resultifyPromiseLegacy(promise: Promise): Promise, E>> { + try { + return Ok((await promise) as NoVoid); + } catch (err) { + return Err(err as E); + } +} + /** - * Takes a promise and returns a new promise that contains a result. + * Takes a promise and returns an async `Result`. * * Examples: * ```ts - * const result = await resultify.promise(promise); + * const result = await resultifyPromise(promise); * ``` * * Due to the limit of TypeScript,it's impossible to resultify overloaded functions perfectly that * the returned functions are still overloaded. * This function allows you to resultify the promise that the overloaded functions return. */ -async function resultifyPromise(promise: Promise): Promise, E>> { - try { - return Ok((await promise) as NoVoid); - } catch (err) { - return Err(err as E); - } +export function resultifyPromise(promise: Promise): ResultAsync, E> { + return new RustlikeResultAsync( + promise.then( + (value) => Ok, E>(value as NoVoid), + (err) => Err, E>(err as E), + ), + ); } type CurriedResultifySync = ( @@ -35,7 +46,7 @@ type CurriedResultifySync = ( * function fn(): string { * // throws error if failed * } - * const fn1 = resultify.sync(fn); + * const fn1 = resultifySync(fn); * ``` * * In the context where async functions are not allowed, you can use this function to resultify the sync function. @@ -43,7 +54,9 @@ type CurriedResultifySync = ( * * If you need the error value and want to specify its type, please use another overloaded function. */ -function resultifySync(fn: (...args: Args) => T): (...args: Args) => Result, E>; +export function resultifySync( + fn: (...args: Args) => T, +): (...args: Args) => Result, E>; /** * Takes a function and returns a version that returns results synchronously. * @@ -52,19 +65,19 @@ function resultifySync(fn: (...args: Args) => T): * function fn(): string { * // throws error if failed * } - * const fn1 = resultify.sync(fn); + * const fn1 = resultifySync()(fn); * ``` * * In the context where async functions are not allowed, you can use this function to resultify the sync function. * If you want to resultify an async function, please use `resultify` instead. */ -function resultifySync(): CurriedResultifySync; +export function resultifySync(): CurriedResultifySync; -function resultifySync( +export function resultifySync( fn?: (...args: Args) => T, ): CurriedResultifySync | ((...args: Args) => Result, E>) { function curriedResultify(_fn: (...args: TArgs) => TT) { - return function resultifiedFn(...args: TArgs): Result, E> { + return function resultifiedSyncFn(...args: TArgs): Result, E> { try { return Ok(_fn(...args) as NoVoid); } catch (err) { @@ -80,6 +93,122 @@ type CurriedResultify = ( fn: (...args: Args) => T | Promise, ) => (...args: Args) => Promise>, E>>; +interface ResultifySignature { + /** + * @deprecated Please use `resultifyAsync` instead. + * + * Takes a function and returns a version that returns results asynchronously. + * + * Examples: + * ```ts + * import fs from 'node:fs/promises'; + * + * const copyFile = resultify(fs.copyFile); + * ``` + * + * If you need the error value and want to specify its type, please use another overloaded function. + */ + ( + fn: (...args: Args) => T | Promise, + ): (...args: Args) => Promise>, E>>; + /** + * @deprecated Please use `resultifyAsync` instead. + * + * Takes a function and returns a version that returns results asynchronously. + * This overloaded function allows you to specify the error type. + * + * Examples: + * ```ts + * import fs from 'node:fs/promises'; + * + * const copyFile = resultify()(fs.copyFile); + * ``` + */ + (): CurriedResultify; + + /** + * @deprecated Please use `resultifySync` instead. + * + * Takes a function and returns a version that returns results synchronously. + * + * Examples: + * ```ts + * function fn(): string { + * // throws error if failed + * } + * const fn1 = resultify.sync(fn); + * ``` + * + * In the context where async functions are not allowed, you can use this function to resultify the sync function. + * If you want to resultify an async function, please use `resultify` instead. + * + * If you need the error value and want to specify its type, please use another overloaded function. + */ + sync(fn: (...args: Args) => T): (...args: Args) => Result, E>; + /** + * @deprecated Please use `resultifySync` instead. + * + * Takes a function and returns a version that returns results synchronously. + * + * Examples: + * ```ts + * function fn(): string { + * // throws error if failed + * } + * const fn1 = resultify.sync()(fn); + * ``` + * + * In the context where async functions are not allowed, you can use this function to resultify the sync function. + * If you want to resultify an async function, please use `resultify` instead. + */ + sync(): CurriedResultifySync; + + /** + * @deprecated Please use `resultifyPromise` instead. + * + * Takes a promise and returns a new promise that contains a result. + * + * Examples: + * ```ts + * const result = await resultify.promise(promise); + * ``` + * + * Due to the limit of TypeScript,it's impossible to resultify overloaded functions perfectly that + * the returned functions are still overloaded. + * This function allows you to resultify the promise that the overloaded functions return. + */ + promise(promise: Promise): Promise, E>>; +} + +function resultifyLegacy( + fn: (...args: Args) => T | Promise, +): (...args: Args) => Promise>, E>>; + +function resultifyLegacy(): CurriedResultify; +function resultifyLegacy( + fn?: (...args: Args) => T | Promise, +): CurriedResultify | ((...args: Args) => Promise>, E>>) { + function curriedResultify(_fn: (...args: TArgs) => TT | Promise) { + return async function resultifiedFn(...args: TArgs): Promise>, E>> { + try { + return Ok((await _fn(...args)) as NoVoid>); + } catch (err) { + return Err(err as E); + } + }; + } + + return fn ? curriedResultify(fn) : curriedResultify; +} + +export const resultify = resultifyLegacy as ResultifySignature; +resultify.sync = resultifySync; +resultify.promise = resultifyPromiseLegacy; + +type CurriedResultifyAsync = ( + fn: (...args: Args) => T | Promise, +) => (...args: Args) => ResultAsync>, E>; + /** * Takes a function and returns a version that returns results asynchronously. * @@ -87,14 +216,14 @@ type CurriedResultify = ( * ```ts * import fs from 'node:fs/promises'; * - * const copyFile = resultify(fs.copyFile); + * const copyFile = resultifyAsync(fs.copyFile); * ``` * * If you need the error value and want to specify its type, please use another overloaded function. */ -function resultify( +export function resultifyAsync( fn: (...args: Args) => T | Promise, -): (...args: Args) => Promise>, E>>; +): (...args: Args) => ResultAsync>, E>; /** * Takes a function and returns a version that returns results asynchronously. * This overloaded function allows you to specify the error type. @@ -103,28 +232,25 @@ function resultify( * ```ts * import fs from 'node:fs/promises'; * - * const copyFile = resultify()(fs.copyFile); + * const copyFile = resultifyAsync()(fs.copyFile); * ``` */ -function resultify(): CurriedResultify; +export function resultifyAsync(): CurriedResultifyAsync; -function resultify( +export function resultifyAsync( fn?: (...args: Args) => T | Promise, -): CurriedResultify | ((...args: Args) => Promise>, E>>) { +): CurriedResultifyAsync | ((...args: Args) => ResultAsync>, E>) { function curriedResultify(_fn: (...args: TArgs) => TT | Promise) { - return async function resultifiedFn(...args: TArgs): Promise>, E>> { + return function resultifiedAsyncFn(...args: TArgs): ResultAsync>, E> { try { - return Ok((await _fn(...args)) as NoVoid>); + // `resultifyPromise` handles ok value and async err + return resultifyPromise(Promise.resolve(_fn(...args)) as Promise>); } catch (err) { - return Err(err as E); + // handles sync err + return new RustlikeResultAsync(Err(err as E)); } }; } return fn ? curriedResultify(fn) : curriedResultify; } - -resultify.sync = resultifySync; -resultify.promise = resultifyPromise; - -export { resultify }; From 9308da4a9750357ab3a490627da007f82b26697a Mon Sep 17 00:00:00 2001 From: yifanwww Date: Sat, 8 Jun 2024 15:16:53 +0800 Subject: [PATCH 4/4] docs: update README.md --- README.md | 1054 ++++++++++++++------- src/Result.ts | 55 +- src/ResultAsync.ts | 40 +- src/__tests__/RustlikeResult.test.ts | 47 + src/__tests__/RustlikeResultAsync.test.ts | 38 + src/resultify.ts | 11 +- 6 files changed, 891 insertions(+), 354 deletions(-) diff --git a/README.md b/README.md index 408e2c2..c78d833 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # rustlike-result -Rust-like `Result` for JavaScript. +Rust-like `Result` and `ResultAsync` for JavaScript. -`Result` is a type that represents either success (`Ok`) or `failure` (`Err`). +`Result` is a type that represents either success (`Ok`) or `failure` (`Err`), `ResultAsync` is the asynchronous version of `Result`. ## Table Of Contents @@ -11,48 +11,67 @@ Rust-like `Result` for JavaScript. - [About Rust `Option`](#about-rust-option) - [Methods Documentation](#methods-documentation) - [Rust `Result` Methods](#rust-result-methods) - - [`isOk`](#isok) - - [`isOkAnd`](#isokand) - - [`isOkAndAsync`](#isokandasync) - - [`isErr`](#iserr) - - [`isErrAnd`](#iserrand) - - [`isErrAndAsync`](#iserrandasync) - - [`ok`](#ok) - - [`err`](#err) - - [`map`](#map) - - [`mapAsync`](#mapasync) - - [`mapOr`](#mapor) - - [`mapOrAsync`](#maporasync) - - [`mapOrElse`](#maporelse) - - [`mapOrElseAsync`](#maporelseasync) - - [`mapErr`](#maperr) - - [`mapErrAsync`](#maperrasync) - - [`inspect`](#inspect) - - [`inspectAsync`](#inspectasync) - - [`inspectErr`](#inspecterr) - - [`inspectErrAsync`](#inspecterrasync) - - [`expect`](#expect) - - [`unwrap`](#unwrap) - - [`expectErr`](#expecterr) - - [`unwrapErr`](#unwraperr) - - [`unwrapOr`](#unwrapor) - - [`unwrapOrElse`](#unwraporelse) - - [`unwrapOrElseAsync`](#unwraporelseasync) - - [`unwrapUnchecked`](#unwrapunchecked) - - [`unwrapErrUnchecked`](#unwraperrunchecked) - - [`and`](#and) - - [`andThen`](#andthen) - - [`andThenAsync`](#andthenasync) - - [`or`](#or) - - [`orElse`](#orelse) - - [`orElseAsync`](#orelseasync) - - [`transpose`](#transpose) + - [Synchronous Methods (`Result`)](#synchronous-methods-result) + - [`Result.isOk`](#resultisok) + - [`Result.isOkAnd`](#resultisokand) + - [`Result.isErr`](#resultiserr) + - [`Result.isErrAnd`](#resultiserrand) + - [`Result.ok`](#resultok) + - [`Result.err`](#resulterr) + - [`Result.map`](#resultmap) + - [`Result.mapOr`](#resultmapor) + - [`Result.mapOrElse`](#resultmaporelse) + - [`Result.mapErr`](#resultmaperr) + - [`Result.inspect`](#resultinspect) + - [`Result.inspectErr`](#resultinspecterr) + - [`Result.expect`](#resultexpect) + - [`Result.unwrap`](#resultunwrap) + - [`Result.expectErr`](#resultexpecterr) + - [`Result.unwrapErr`](#resultunwraperr) + - [`Result.unwrapOr`](#resultunwrapor) + - [`Result.unwrapOrElse`](#resultunwraporelse) + - [`Result.unwrapUnchecked`](#resultunwrapunchecked) + - [`Result.unwrapErrUnchecked`](#resultunwraperrunchecked) + - [`Result.and`](#resultand) + - [`Result.andThen`](#resultandthen) + - [`Result.or`](#resultor) + - [`Result.orElse`](#resultorelse) + - [`Result.transpose`](#resulttranspose) + - [Asynchronous Methods (`ResultAsync`)](#asynchronous-methods-resultasync) + - [`ResultAsync.isOk`](#resultasyncresultasyncisok) + - [`ResultAsync.isOkAnd`](#resultasyncisokand) + - [`ResultAsync.isErr`](#resultasynciserr) + - [`ResultAsync.isErrAnd`](#resultasynciserrand) + - [`ResultAsync.ok`](#resultasyncok) + - [`ResultAsync.err`](#resultasyncerr) + - [`ResultAsync.map`](#resultasyncmap) + - [`ResultAsync.mapOr`](#resultasyncmapor) + - [`ResultAsync.mapOrElse`](#resultasyncmaporelse) + - [`ResultAsync.mapErr`](#resultasyncmaperr) + - [`ResultAsync.inspect`](#resultasyncinspect) + - [`ResultAsync.inspectErr`](#resultasyncinspecterr) + - [`ResultAsync.expect`](#resultasyncexpect) + - [`ResultAsync.unwrap`](#resultasyncunwrap) + - [`ResultAsync.expectErr`](#resultasyncexpecterr) + - [`ResultAsync.unwrapErr`](#resultasyncunwraperr) + - [`ResultAsync.unwrapOr`](#resultasyncunwrapor) + - [`ResultAsync.unwrapOrElse`](#resultasyncunwraporelse) + - [`ResultAsync.unwrapUnchecked`](#resultasyncunwrapunchecked) + - [`ResultAsync.unwrapErrUnchecked`](#resultasyncunwraperrunchecked) + - [`ResultAsync.and`](#resultasyncand) + - [`ResultAsync.andThen`](#resultasyncandthen) + - [`ResultAsync.or`](#resultasyncor) + - [`ResultAsync.orElse`](#resultasyncorelse) + - [`ResultAsync.transpose`](#resultasynctranspose) - [Additional Methods](#additional-methods) - - [equal](#equal) + - [`.equal`](#equal) + - [`Result.equal`](#resultequal) + - [`ResultAsync.equal`](#resultasyncequal) + - [`Result.async`](#resultasync) - [Helpers for Resultifying](#helpers-for-resultifying) - - [resultify](#resultify) - - [resultify.sync](#resultifysync) - - [resultify.promise](#resultifypromise) + - [resultifyAsync](#resultifyasync) + - [resultifySync](#resultifysync) + - [resultifyPromise](#resultifypromise) - [JSON Serialization \& Deserialization](#json-serialization--deserialization) - [Built-in Simple Implementation](#built-in-simple-implementation) - [Community (De)Serialization Solutions](#community-deserialization-solutions) @@ -105,35 +124,35 @@ This package doesn't implement Rust-like `Option`. Handling `undefined`/`null` i ## Methods Documentation ### Rust `Result` Methods -The Rust-like `Result` implements the following methods: - -| Rust-like `Result` method | Rust `Result` method | -| :------------------------------- | :--------------------- | -| isOk | [is_ok] | -| isOkAnd / isOkAndAsync | [is_ok_and] | -| isErr | [is_err] | -| isErrAnd / isErrAndAsync | [is_err_and] | -| ok | [ok] | -| err | [err] | -| map / mapAsync | [map] | -| mapOr / mapOrAsync | [map_or] | -| mapOrElse / mapOrElseAsync | [map_or_else] | -| mapErr / mapErrAsync | [map_err] | -| inspect / inspectAsync | [inspect] | -| inspectErr / inspectErrAsync | [inspect_err] | -| expect | [expect] | -| unwrap | [unwrap] | -| expectErr | [expect_err] | -| unwrapErr | [unwrap_err] | -| unwrapOr | [unwrap_or] | -| unwrapOrElse / unwrapOrElseAsync | [unwrap_or_else] | -| unwrapUnchecked | [unwrap_unchecked] | -| unwrapErrUnchecked | [unwrap_err_unchecked] | -| and | [and] | -| andThen / andThenAsync | [and_then] | -| or | [or] | -| orElse / orElseAsync | [or_else] | -| transpose | [transpose] | +The Rust-like `Result` and `ResultAsync` implements the following methods: + +| Rust-like `Result` / `ResultAsync` method | Rust `Result` method | +| :---------------------------------------- | :--------------------- | +| isOk | [is_ok] | +| isOkAnd | [is_ok_and] | +| isErr | [is_err] | +| isErrAnd | [is_err_and] | +| ok | [ok] | +| err | [err] | +| map | [map] | +| mapOr | [map_or] | +| mapOrElse | [map_or_else] | +| mapErr | [map_err] | +| inspect | [inspect] | +| inspectErr | [inspect_err] | +| expect | [expect] | +| unwrap | [unwrap] | +| expectErr | [expect_err] | +| unwrapErr | [unwrap_err] | +| unwrapOr | [unwrap_or] | +| unwrapOrElse | [unwrap_or_else] | +| unwrapUnchecked | [unwrap_unchecked] | +| unwrapErrUnchecked | [unwrap_err_unchecked] | +| and | [and] | +| andThen | [and_then] | +| or | [or] | +| orElse | [or_else] | +| transpose | [transpose] | Unlike Rust, JavaScript doesn't have the 'Ownership' feature, so some API like `as_ref` are not necessary. These implementations are not implemented: @@ -187,7 +206,8 @@ hash [or_else]: https://doc.rust-lang.org/std/result/enum.Result.html#method.or_else [transpose]: https://doc.rust-lang.org/std/result/enum.Result.html#method.transpose -#### `isOk` +#### Synchronous Methods (`Result`) +##### `Result.isOk` Returns `true` if the result is `Ok`. @@ -203,7 +223,7 @@ const y: Result = Err('Some error message'); assert(y.isOk() === false); ``` -#### `isOkAnd` +##### `Result.isOkAnd` Returns `true` if the result is `Ok` and the value inside of it matches a predicate. @@ -222,26 +242,7 @@ const z: Result = Err('Some error message'); assert(z.isOkAnd((value) => value > 1) === false); ``` -#### `isOkAndAsync` - -Asynchronously returns `true` if the result is `Ok` and the value inside of it matches a predicate. - -Examples: - -```ts -import { Err, Ok, type Result } from 'rustlike-result'; - -const x: Result = Ok(2); -assert((await x.isOkAndAsync((value) => Promise.resolve(value > 1))) === true); - -const y: Result = Ok(0); -assert((await y.isOkAndAsync((value) => Promise.resolve(value > 1))) === false); - -const z: Result = Err('Some error message'); -assert((await z.isOkAndAsync((value) => Promise.resolve(value > 1))) === false); -``` - -#### `isErr` +##### `Result.isErr` Returns `true` if the result is `Err`. @@ -257,7 +258,7 @@ const y: Result = Err('Some error message'); assert(y.isErr() === true); ``` -#### `isErrAnd` +##### `Result.isErrAnd` Returns `true` if the result is `Err` and the value inside of it matches a predicate. @@ -267,8 +268,8 @@ Examples: import { Err, Ok, type Result } from 'rustlike-result'; enum ErrorKind { -NOT_FOUND, -PERMISSION_DENIED, + NOT_FOUND, + PERMISSION_DENIED, } const x: Result = Err(ErrorKind.NOT_FOUND); @@ -281,31 +282,7 @@ const z: Result = Ok(123); assert(z.isErrAnd((value) => value === ErrorKind.NOT_FOUND) === false); ``` -#### `isErrAndAsync` - -Asynchronously returns `true` if the result is `Err` and the value inside of it matches a predicate. - -Examples: - -```ts -import { Err, Ok, type Result } from 'rustlike-result'; - -enum ErrorKind { -NOT_FOUND, -PERMISSION_DENIED, -} - -const x: Result = Err(ErrorKind.NOT_FOUND); -assert((await x.isErrAndAsync((value) => Promise.resolve(value === ErrorKind.NOT_FOUND))) === true); - -const y: Result = Err(ErrorKind.PERMISSION_DENIED); -assert((await y.isErrAndAsync((value) => Promise.resolve(value === ErrorKind.NOT_FOUND))) === false); - -const z: Result = Ok(123); -assert((await z.isErrAndAsync((value) => Promise.resolve(value === ErrorKind.NOT_FOUND))) === false); -``` - -#### `ok` +##### `Result.ok` Converts from `Result` to `Optional` and discarding the error, if any. @@ -321,7 +298,7 @@ const y: Result = Err('Some error message'); assert(y.ok() === undefined); ``` -#### `err` +##### `Result.err` Converts from `Result` to `Optional` and discarding the success value, if any. @@ -337,7 +314,7 @@ const y: Result = Err('Some error message'); assert(y.err() === 'Some error message'); ``` -#### `map` +##### `Result.map` Maps a `Result` to `Result` by applying a function to a contained `Ok` value, leaving an `Err` value untouched. @@ -352,22 +329,7 @@ const x: Result = Ok('foo'); assert(x.map((value) => value.length).ok() === 3); ``` -#### `mapAsync` - -Asynchronously maps a `Result` to `Result` by applying a function to a contained `Ok` value, leaving an `Err` value untouched. - -This function can be used to compose the results of two functions. - -Examples: - -```ts -import { Ok } from 'rustlike-result'; - -const x = await Ok('foo').mapAsync((value) => Promise.resolve(value.length)); -assert(x.ok() === 3); -``` - -#### `mapOr` +##### `Result.mapOr` Returns the provided `fallback` (if `Err`), or applies a function to the contained value (if `Ok`). @@ -385,25 +347,7 @@ const y: Result = Err('bar'); assert(y.mapOr(42, (value) => value.length) === 42); ``` -#### `mapOrAsync` - -Asynchronously returns the provided `fallback` (if `Err`), or applies a function to the contained value (if `Ok`). - -Arguments passed to `mapOr` are eagerly evaluated; if you are passing the result of a function call, it is recommended to use `mapOrElse`, which is lazily evaluated. - -Examples: - -```ts -import { Err, Ok, type Result } from 'rustlike-result'; - -const x: Result = Ok('foo'); -assert((await x.mapOrAsync(42, (value) => value.length)) === 3); - -const y: Result = Err('bar'); -assert((await y.mapOrAsync(42, (value) => value.length)) === 42); -``` - -#### `mapOrElse` +##### `Result.mapOrElse` Maps a `Result` to `U` by applying fallback function `fallback` to a contained `Err` value, or function `map` to a contained `Ok` value. @@ -423,27 +367,7 @@ const y: Result = Err('bar'); assert(y.mapOrElse((err) => k * 2, (value) => value.length) === 42); ``` -#### `mapOrElseAsync` - -Asynchronously maps a `Result` to `U` by applying fallback function `fallback` to a contained `Err` value, or function `map` to a contained `Ok` value. - -This function can be used to unpack a successful result while handling an error. - -Examples: - -```ts -import { Err, Ok, type Result } from 'rustlike-result'; - -const k = 21; - -const x: Result = Ok('foo'); -assert((await x.mapOrElseAsync(() => Promise.resolve(k * 2), (value) => Promise.resolve(value.length))) === 3); - -const y: Result = Err('bar'); -assert((await y.mapOrElseAsync(() => Promise.resolve(k * 2), (value) => Promise.resolve(value.length))) === 42); -``` - -#### `mapErr` +##### `Result.mapErr` Maps a `Result` to `Result` by applying a function to a contained `Err` value, leaving an `Ok` value untouched. @@ -458,22 +382,7 @@ const x: Result = Err(new Error('Some error message')); assert(x.mapErr((err) => err.message).err() === 'Some error message'); ``` -#### `mapErrAsync` - -Asynchronously maps a `Result` to `Result` by applying a function to a contained `Err` value, leaving an `Ok` value untouched. - -This function can be used to pass through a successful result while handling an error. - -Examples: - -```ts -import { Err } from 'rustlike-result'; - -const x = await Err(new Error('Some error message')).mapErrAsync((err) => Promise.resolve(err.message)); -assert(x.err() === 'Some error message'); -``` - -#### `inspect` +##### `Result.inspect` Calls the provided closure with a reference to the contained value if `Ok`. @@ -490,27 +399,7 @@ const num = resultify assert(num === 64); ``` -#### `inspectAsync` - -Asynchronously calls the provided closure with a reference to the contained value if `Ok`. - -Examples: - -```ts -import { resultify } from 'rustlike-result'; - -const num = await resultify - .sync()(JSON.parse)('4') - .inspectAsync((value: number) => { - console.log(`original: ${value}`); - return Promise.resolve(); - }) - .then((result) => result.map((value) => value ** 3)) - .then((result) => result.expect('failed to parse number')); -assert(num === 64); -``` - -#### `inspectErr` +##### `Result.inspectErr` Calls the provided closure with a reference to the contained value if `Err`. @@ -525,25 +414,7 @@ const num = resultify assert(num.err() instanceof SyntaxError); ``` -#### `inspectErrAsync` - -Asynchronously calls the provided closure with a reference to the contained value if `Err`. - -Examples: - -```ts -import { resultify } from 'rustlike-result'; - -const num = await resultify - .sync()(JSON.parse)('asdf') - .inspectErrAsync((err) => { - console.log(`failed to parse json string: ${err.message}`); - return Promise.resolve(); - }); -assert(num.err() instanceof SyntaxError); -``` - -#### `expect` +##### `Result.expect` Returns the contained `Ok` value. @@ -560,7 +431,7 @@ const x: Result = Err('emergency failure'); x.expect('Failed to operate'); // throws Error('Failed to operate: emergency failure') ``` -#### `unwrap` +##### `Result.unwrap` Returns the contained `Ok` value. @@ -580,7 +451,7 @@ const y: Result = Err('emergency failure'); y.unwrap(); // throws Error('emergency failure') ``` -#### `expectErr` +##### `Result.expectErr` Returns the contained `Err` value. @@ -595,7 +466,7 @@ const x: Result = Ok(10); x.expectErr('Testing expectErr'); // throws Error('Testing expectErr: 10') ``` -#### `unwrapErr` +##### `Result.unwrapErr` Returns the contained `Err` value. @@ -613,7 +484,7 @@ const y: Result = Ok(2); y.unwrapErr(); // throws Error(2) ``` -#### `unwrapOr` +##### `Result.unwrapOr` Returns the contained `Ok` value or a provided default. @@ -632,7 +503,7 @@ const y: Result = Err('error'); assert(y.unwrapOr($default) === $default); ``` -#### `unwrapOrElse` +##### `Result.unwrapOrElse` Returns the contained `Ok` value or computes it from a closure. @@ -646,21 +517,7 @@ assert(Ok(2).unwrapOrElse(count) === 2); assert(Err('foo').unwrapOrElse(count) === 3); ``` -#### `unwrapOrElseAsync` - -Asynchronously returns the contained `Ok` value or computes it from a closure. - -Examples: - -```ts -import { Err, Ok } from 'rustlike-result'; - -const count = (err: string) => Promise.resolve(err.length); -assert((await Ok(2).unwrapOrElseAsync(count)) === 2); -assert((await Err('foo').unwrapOrElseAsync(count)) === 3); -``` - -#### `unwrapUnchecked` +##### `Result.unwrapUnchecked` Returns the contained `Ok` value, without checking that the value is not an `Err`. @@ -679,7 +536,7 @@ const y: Result = Err('emergency failure'); y.unwrapUnchecked(); ``` -#### `unwrapErrUnchecked` +##### `Result.unwrapErrUnchecked` Returns the contained `Err` value, without checking that the value is not an `Ok`. @@ -698,7 +555,7 @@ const y: Result = Err('emergency failure'); assert(y.unwrapErrUnchecked() === 'emergency failure'); ``` -#### `and` +##### `Result.and` Returns `res` if itself is `Ok`, otherwise returns the `Err` value of itself. @@ -729,11 +586,11 @@ y = Ok('different result type'); assert(x.and(y).equal(Ok('different result type'))); ``` -#### `andThen` +##### `Result.andThen` Calls `op` if itself is `Ok`, otherwise returns the `Err` value of itself. -This function can be used for control flow based on Result values. +This function can be used for control flow based on `Result` values. Examples: @@ -753,35 +610,7 @@ assert( ); ``` -#### `andThenAsync` - -Asynchronously calls `op` if itself is `Ok`, otherwise returns the `Err` value of itself. - -This function can be used for control flow based on Result values. - -Examples: - -```ts -import { Err, Ok } from 'rustlike-result'; - -const parseJSON = (json: string) => - Promise.resolve( - resultify - .sync()(JSON.parse)(json) - .mapErr((err) => err.message), - ); - -const x = await Ok('2').andThenAsync(parseJSON); -assert(x.equal(Ok(2))); - -const y = await Ok('asdf').andThenAsync(parseJSON); -assert(y.equal(Err('Unexpected token \'a\', "asdf" is not valid JSON'))); - -const z = await Err('not a valid json string').andThenAsync(parseJSON); -assert(z.equal(Err('not a valid json string'))); -``` - -#### `or` +##### `Result.or` Returns `res` if itself is `Err`, otherwise returns the `Ok` value of itself. @@ -809,14 +638,14 @@ assert(x.or(y).equal(Err('late error'))); x = Ok(2); y = Ok(100); -assert(x.and(y).equal(Ok('different result type'))); +assert(x.or(y).equal(Ok(2))); ``` -#### `orElse` +##### `Result.orElse` Calls `op` if the result is `Err`, otherwise returns the `Ok` value of self. -This function can be used for control flow based on result values. +This function can be used for control flow based on `Result` values. Examples: @@ -832,37 +661,7 @@ assert(Err(3).orElse(sq).orElse(err).equal(Ok(9))); assert(Err(3).orElse(err).orElse(err).equal(Err(3))); ``` -#### `orElseAsync` - -Asynchronously calls `op` if the result is `Err`, otherwise returns the `Ok` value of self. - -This function can be used for control flow based on result values. - -Examples: - -```ts -import { Err, Ok, type Result } from 'rustlike-result'; - -const sq = (num: number): Promise> => Promise.resolve(Ok(num * num)); -const err = (num: number): Promise> => Promise.resolve(Err(num)); - -const x = await Ok(2) - .orElseAsync(sq) - .then((result) => result.orElseAsync(sq)); -assert(x.equal(Ok(2))); - -const y = await Err(3) - .orElseAsync(sq) - .then((result) => result.orElseAsync(err)); -assert(y.equal(Ok(9))); - -const z = await Err(3) - .orElseAsync(err) - .then((result) => result.orElseAsync(err)); -assert(z.equal(Err(3))); -``` - -#### `transpose` +##### `Result.transpose` Transposes a `Result` of an optional value into an optional of a `Result`. @@ -891,46 +690,613 @@ y = undefined; assert(x.transpose() === y); ``` -### Additional Methods -#### equal +#### Asynchronous Methods (`ResultAsync`) +##### `ResultAsync.isOk` + +Asynchornously returns `true` if the result is `Ok`. + +Examples: + +```ts +import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + +const x: ResultAsync = OkAsync(2); +assert((await x.isOk()) === true); + +const y: ResultAsync = ErrAsync('Some error message'); +assert((await y.isOk()) === false); +``` + +##### `ResultAsync.isOkAnd` + +Asynchronously returns `true` if the result is `Ok` and the value inside of it matches a predicate. + +Examples: + +```ts +import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + +const x: ResultAsync = OkAsync(2); +assert((await x.isOkAnd((value) => Promise.resolve(value > 1))) === true); + +const y: ResultAsync = OkAsync(0); +assert((await y.isOkAnd((value) => Promise.resolve(value > 1))) === false); + +const z: ResultAsync = ErrAsync('Some error message'); +assert((await z.isOkAnd((value) => Promise.resolve(value > 1))) === false); +``` + +##### `ResultAsync.isErr` + +Asynchornously returns `true` if the result is `Err`. + +Examples: + +```ts +import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + +const x: ResultAsync = OkAsync(-3); +assert((await x.isErr()) === false); + +const y: ResultAsync = ErrAsync('Some error message'); +assert((await y.isErr()) === true); +``` + +##### `ResultAsync.isErrAnd` + +Asynchronously returns `true` if the result is `Err` and the value inside of it matches a predicate. + +Examples: + +```ts +import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + +enum ErrorKind { + NOT_FOUND, + PERMISSION_DENIED, +} + +const x: ResultAsync = ErrAsync(ErrorKind.NOT_FOUND); +assert((await x.isErrAnd((value) => Promise.resolve(value === ErrorKind.NOT_FOUND))) === true); + +const y: ResultAsync = ErrAsync(ErrorKind.PERMISSION_DENIED); +assert((await y.isErrAnd((value) => Promise.resolve(value === ErrorKind.NOT_FOUND))) === false); + +const z: ResultAsync = OkAsync(123); +assert((await z.isErrAnd((value) => Promise.resolve(value === ErrorKind.NOT_FOUND))) === false); +``` + +##### `ResultAsync.ok` + +Asynchornously converts from `ResultAsync` to `Optional` and discarding the error, if any. + +Examples: + +```ts +import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + +const x: ResultAsync = OkAsync(2); +assert((await x.ok()) === 2); + +const y: ResultAsync = ErrAsync('Some error message'); +assert((await y.ok()) === undefined); +``` + +##### `ResultAsync.err` + +Asynchornously converts from `ResultAsync` to `Optional` and discarding the success value, if any. + +Examples: + +```ts +import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + +const x: ResultAsync = OkAsync(2); +assert((await x.err()) === undefined); + +const y: ResultAsync = ErrAsync('Some error message'); +assert((await y.err()) === 'Some error message'); +``` + +##### `ResultAsync.map` + +Asynchronously maps a `ResultAsync` to `ResultAsync` by applying a function to a contained `Ok` value, leaving an `Err` value untouched. + +This function can be used to compose the results of two functions. + +Examples: + +```ts +import { OkAsync } from 'rustlike-result'; + +const x = OkAsync('foo').map((value) => Promise.resolve(value.length)); +assert((await x.ok()) === 3); +``` + +##### `ResultAsync.mapOr` + +Asynchronously returns the provided `fallback` (if `Err`), or applies a function to the contained value (if `Ok`). + +Arguments passed to `mapOr` are eagerly evaluated; if you are passing the result of a function call, it is recommended to use `mapOrElse`, which is lazily evaluated. + +Examples: + +```ts +import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + +const x: ResultAsync = OkAsync('foo'); +assert((await x.mapOr(42, (value) => value.length)) === 3); + +const y: ResultAsync = ErrAsync('bar'); +assert((await y.mapOr(42, (value) => value.length)) === 42); +``` + +##### `ResultAsync.mapOrElse` + +Asynchronously maps a `ResultAsync` to `U` by applying fallback function `fallback` to a contained `Err` value, or function `map` to a contained `Ok` value. -You can not just use `===` or `==` to compare `Result`, so `Result` itself provides an method call `equal` for that. +This function can be used to unpack a successful result while handling an error. + +Examples: + +```ts +import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + +const k = 21; + +const x: ResultAsync = OkAsync('foo'); +assert((await x.mapOrElse(() => Promise.resolve(k * 2), (value) => Promise.resolve(value.length))) === 3); + +const y: ResultAsync = ErrAsync('bar'); +assert((await y.mapOrElse(() => Promise.resolve(k * 2), (value) => Promise.resolve(value.length))) === 42); +``` + +##### `ResultAsync.mapErr` + +Asynchronously maps a `ResultAsync` to `ResultAsync` by applying a function to a contained `Err` value, leaving an `Ok` value untouched. + +This function can be used to pass through a successful result while handling an error. + +Examples: + +```ts +import { ErrAsync } from 'rustlike-result'; + +const x = ErrAsync(new Error('Some error message')).mapErr((err) => Promise.resolve(err.message)); +assert((await x.err()) === 'Some error message'); +``` + +##### `ResultAsync.inspect` + +Asynchronously calls the provided closure with a reference to the contained value if `Ok`. + +Examples: + +```ts +const num = await OkAsync(4) + .inspect((value) => { + console.log(`original: ${value}`); + }) + .map((value) => value ** 3) + .expect('Some error message'); +assert(num === 64); +``` + +##### `ResultAsync.inspectErr` + +Asynchronously calls the provided closure with a reference to the contained value if `Err`. + +Examples: + +```ts +const result = ErrAsync(new SyntaxError('Some error message')).inspectErr((err) => { + console.log(`failed to do something: ${err.message}`); +}); +assert((await result.err()) instanceof SyntaxError); +``` + +##### `ResultAsync.expect` + +Asynchronously returns the contained `Ok` value. + +Because this function may throw an error, its use is generally discouraged. Instead, prefer to call `unwrapOr`, `unwrapOrElse`. + +Throws an Error if itself is `Err`, with an error message including the passed message, and the content of the `Err`. + +Examples: + +```ts +import { ErrAsync, type ResultAsync } from 'rustlike-result'; + +const x: ResultAsync = ErrAsync('emergency failure'); +await x.expect('Failed to operate'); // throws Error('Failed to operate: emergency failure') +``` + +##### `ResultAsync.unwrap` + +Asynchronously returns the contained `Ok` value. + +Because this function may throw an error, its use is generally discouraged. Instead, prefer to call `unwrapOr`, `unwrapOrElse`. + +Throws an Error if itself is `Err`, with an error message provided by the `Err`'s value. + +Examples: + +```ts +import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + +const x: ResultAsync = OkAsync(2); +assert((await x.unwrap()) === 2); + +const y: ResultAsync = ErrAsync('emergency failure'); +await y.unwrap(); // throws Error('emergency failure') +``` + +##### `ResultAsync.expectErr` + +Asynchronously returns the contained `Err` value. + +Throws an Error if itself is `Err`, with an error message provided by the `Ok`'s value. + +Examples: + +```ts +import { OkAsync, type ResultAsync } from 'rustlike-result'; + +const x: ResultAsync = OkAsync(10); +await x.expectErr('Testing expectErr'); // throws Error('Testing expectErr: 10') +``` + +##### `ResultAsync.unwrapErr` + +Asynchronously returns the contained `Err` value. + +Throws an Error if itself is `Ok`, with an error message provided by the `Ok`'s value. + +Examples: + +```ts +import { OkAsync, type ResultAsync } from 'rustlike-result'; + +const x: ResultAsync = Err('emergency failure'); +assert((await x.unwrapErr()) === 'emergency failure'); + +const y: ResultAsync = OkAsync(2); +await y.unwrapErr(); // throws Error(2) +``` + +##### `ResultAsync.unwrapOr` + +Asynchronously returns the contained `Ok` value or a provided default. + +Arguments passed to `unwrapOr` are eagerly evaluated; if you are passing the result of a function call, it is recommended to use `unwrapOrElse`, which is lazily evaluated. + +Examples: + +```ts +import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + +const $default = 2; +const x: ResultAsync = OkAsync(9); +assert((await x.unwrapOr($default)) === 9); + +const y: ResultAsync = ErrAsync('error'); +assert((await y.unwrapOr($default)) === $default); +``` + +##### `ResultAsync.unwrapOrElse` + +Asynchronously returns the contained `Ok` value or computes it from a closure. + +Examples: + +```ts +import { ErrAsync, OkAsync } from 'rustlike-result'; + +const count = (err: string) => Promise.resolve(err.length); +assert((await OkAsync(2).unwrapOrElse(count)) === 2); +assert((await ErrAsync('foo').unwrapOrElse(count)) === 3); +``` + +##### `ResultAsync.unwrapUnchecked` + +Asynchronously returns the contained `Ok` value, without checking that the value is not an `Err`. + +**SAFETY**: Calling this method on an `Err` is undefined behavior. +The safety contract must be upheld by the caller. + +Examples: + +```ts +import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + +const x: ResultAsync = OkAsync(2); +assert((await x.unwrapUnchecked()) === 2); -```js -expect(Ok(1).equal(Ok(1))).toBe(true); -expect(Ok(1).equal(Ok(2))).toBe(false); -expect(Ok(1).equal(Ok(2))).toBe(false); -expect(Ok('hello').equal(Ok('hello'))).toBe(true); -expect(Ok('hello').equal(Ok('hello world'))).toBe(false); -expect(Ok(1).equal(Ok('hello world'))).toBe(false); +const y: ResultAsync = ErrAsync('emergency failure'); +await y.unwrapUnchecked(); +``` + +##### `ResultAsync.unwrapErrUnchecked` + +Asynchronously returns the contained `Err` value, without checking that the value is not an `Ok`. + +**SAFETY**: Calling this method on an `Ok` is undefined behavior. +The safety contract must be upheld by the caller. + +Examples: + +```ts +import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; + +const x: ResultAsync = OkAsync(2); +await x.unwrapErrUnchecked(); + +const y: ResultAsync = ErrAsync('emergency failure'); +assert((await y.unwrapErrUnchecked()) === 'emergency failure'); +``` + +##### `ResultAsync.and` + +Asynchronously returns `res` if itself is `Ok`, otherwise returns the `Err` value of itself. + +Arguments passed to `and` are eagerly evaluated; if you are passing the result of a function call, it is recommended to use `andThen`, which is lazily evaluated. + +Examples: + +```ts +import { Err, ErrAsync, Ok, OkAsync, type ResultAsync } from 'rustlike-result'; + +let x: ResultAsync; +let y: ResultAsync; + +x = OkAsync(2); +y = ErrAsync('late error'); +assert(await x.and(y).equal(Err('late error'))); + +x = ErrAsync('early error'); +y = OkAsync('foo'); +assert(await x.and(y).equal(Err('early error'))); + +x = ErrAsync('not a 2'); +y = ErrAsync('late error'); +assert(await x.and(y).equal(Err('not a 2'))); + +x = OkAsync(2); +y = OkAsync('different result type'); +assert(await x.and(y).equal(Ok('different result type'))); +``` + +##### `ResultAsync.andThen` + +Asynchronously calls `op` if itself is `Ok`, otherwise returns the `Err` value of itself. -expect(Ok({ foo: 1 }).equal(Ok({ foo: 1 }))).toBe(false); -expect(Ok([1]).equal(Ok([1]))).toBe(false); +This function can be used for control flow based on `ResultAsync` values. + +Examples: + +```ts +import { Err, ErrAsync, Ok, OkAsync, type ResultAsync } from 'rustlike-result'; + +const sq = (num: number): ResultAsync => OkAsync(num * num); +const err = (num: number): ResultAsync => ErrAsync(num); + +const x = OkAsync(2).andThen(sq).andThen(sq); +assert(await x.equal(Ok(16))); + +const y = OkAsync(2).andThen(sq).andThen(err); +assert(await y.equal(Err(4))); + +const z = OkAsync(2).andThen(err).andThen(err); +assert(await z.equal(Err(2))); +``` + +##### `ResultAsync.or` + +Returns `res` if itself is `Err`, otherwise returns the `Ok` value of itself. + +Arguments passed to `or` are eagerly evaluated; if you are passing the result of a function call, it is recommended to use `orElse`, which is lazily evaluated. + +Examples: + +```ts +import { Err, ErrAsync, Ok, OkAsync, type ResultAsync } from 'rustlike-result'; + +let x: ResultAsync; +let y: ResultAsync; + +x = OkAsync(2); +y = ErrAsync('late error'); +assert(await x.or(y).equal(Ok(2))); + +x = ErrAsync('early error'); +y = OkAsync(2); +assert(await x.or(y).equal(Ok(2))); + +x = ErrAsync('not a 2'); +y = ErrAsync('late error'); +assert(await x.or(y).equal(Err('late error'))); + +x = OkAsync(2); +y = OkAsync(100); +assert(await x.or(y).equal(Ok(2))); ``` -There is no built-in deep-equal support in this package for array, object and some built-in classes like `Date`. If you do want to deeply compare those complex structures, you will have to write your own helper functions. +##### `ResultAsync.orElse` + +Asynchronously calls `op` if the result is `Err`, otherwise returns the `Ok` value of self. + +This function can be used for control flow based on `ResultAsync` values. + +Examples: + +```ts +import { Err, ErrAsync, Ok, OkAsync, type ResultAsync } from 'rustlike-result'; + +const sq = (num: number): ResultAsync => OkAsync(num * num); +const err = (num: number): ResultAsync => ErrAsync(num); + +const x = OkAsync(2).orElse(sq).orElse(sq); +assert(await x.equal(Ok(2))); + +const y = ErrAsync(3).orElse(sq).orElse(err); +assert(await y.equal(Ok(9))); + +const z = ErrAsync(3).orElse(err).orElse(err); +assert(await z.equal(Err(3))); +``` + +##### `ResultAsync.transpose` + +Asynchronously transposes a `ResultAsync` of an optional value into an optional of a `ResultAsync`. + +`OkAsync(undefined | null)` will be mapped to `Promise`. `OkAsync(_)` and `ErrAsync(_)` will be mapped to `OkAsync(_)` and `ErrAsync(_)`. + +Examples: + +```ts +import { OkAsync, type ResultAsync } from 'rustlike-result'; + +type SomeErr = unknown; + +let x: ResultAsync; +let y: ResultAsync | undefined; + +x = OkAsync(5); +y = OkAsync(5); +assert(await x.transpose()!.equal(y)); + +x = OkAsync(undefined); +y = undefined; +assert((await x.transpose()) === y); + +x = OkAsync(null); +y = undefined; +assert((await x.transpose()) === y); +``` + +### Additional Methods +#### `.equal` + +You can not just use `===` or `==` to compare `Result` or `ResultAsync`, so `Result` and `ResultAsync` themselves provide an method call `equal` for that. + +There is no built-in deep-equal support in this package for array, object, built-in classes like `Date`, custom classes. If you do want to deeply compare those complex structures, you will have to write your own helper functions. There is a [proposal] (stage 2) that introduces `Record` and `Tuple` which are compared by content rather than identity. In the future, we can use `Record` and `Tuple` in `Result` so that we don't need to implement custom equality comparison function. [proposal]: https://github.com/tc39/proposal-record-tuple +##### `Result.equal` + +Returns `true` if `self` equals to `other`. + +Examples: + +```ts +import { Err, Ok } from 'rustlike-result'; + +assert(Ok(1).equal(Ok(1))); +assert(Ok(NaN).equal(Ok(NaN))); +assert(Err('err').equal(Err('err'))); + +assert(Ok(1).equal(Ok(2)) === false); +assert(Err('err 1').equal(Err(-1)) === false); + +assert(Ok(Ok(1)).equal(Ok(Ok(1)))); +assert(Ok(Err('err')).equal(Ok(Err('err')))); +assert(Err(Err('err')).equal(Err(Err('err')))); +assert(Err(Ok(1)).equal(Err(Ok(1)))); + +assert(Ok(Ok(1)).equal(Ok(Ok(2))) === false); +assert(Ok(Ok(1)).equal(Ok(Err('err'))) === false); +assert(Err(Ok(1)).equal(Err(Err('err'))) === false); + +assert(Ok([1]).equal(Ok([1])) === false); +assert(Ok({ foo: 1 }).equal(Ok({ foo: 1 })) === false); +assert(Ok(Ok([1])).equal(Ok(Ok([1]))) === false); +assert(Ok(Ok({ foo: 1 })).equal(Ok(Ok({ foo: 1 }))) === false); +``` + +##### `ResultAsync.equal` + +Asynchronously returns `true` if `self` equals to `other`. + +Examples: + +```ts +import { Err, ErrAsync, Ok, OkAsync, type ResultAsync } from 'rustlike-result'; + +assert(await OkAsync(1).equal(Ok(1))); +assert(await OkAsync(1).equal(Promise.resolve(Ok(1)))); +assert(await OkAsync(1).equal(OkAsync(1))); + +assert((await OkAsync(1).equal(Ok(2))) === false); +assert((await OkAsync(1).equal(Promise.resolve(Ok(2)))) === false); +assert((await OkAsync(1).equal(OkAsync(2))) === false); + +assert(await OkAsync(Ok(1)).equal(Ok(Ok(1)))); +assert(await OkAsync(Ok(1)).equal(Ok(OkAsync(1)))); +assert(await OkAsync(Ok(1)).equal(Promise.resolve(Ok(Ok(1))))); +assert(await OkAsync(Ok(1)).equal(OkAsync(Promise.resolve(Ok(1))))); +assert(await OkAsync(Ok(1)).equal(OkAsync(OkAsync(1)))); +assert(await OkAsync(Promise.resolve(Ok(1))).equal(Promise.resolve(Ok(OkAsync(1))))); +assert(await OkAsync(OkAsync(1)).equal(OkAsync(Ok(1)))); + +assert((await OkAsync([1]).equal(Ok([1]))) === false); +assert((await OkAsync({ foo: 1 }).equal(Promise.resolve(Ok({ foo: 1 })))) === false); +assert((await ErrAsync({ message: 'err' }).equal(ErrAsync({ message: 'err' }))) === false); + +assert((await OkAsync(Ok([1])).equal(Ok(Ok([1])))) === false); +assert((await OkAsync(Ok([1])).equal(OkAsync(OkAsync([1])))) === false); +assert((await OkAsync(Promise.resolve(Ok([1]))).equal(OkAsync(Ok([1])))) === false); +assert((await OkAsync(Promise.resolve(Ok({ foo: 1 }))).equal(Ok(OkAsync({ foo: 1 })))) === false); +assert((await OkAsync(OkAsync({ foo: 1 })).equal(OkAsync(OkAsync({ foo: 1 })))) === false); +``` + +#### `Result.async` + +Converts this result to an async `Result` so it can work in asynchronous code. + +Examples: + +```ts +import { Ok } from 'rustlike-result'; + +function fn(value: number): Promise { + // do something asynchronously + return Promise.resolve(value ** 2); +} + +const num = await Ok(3) + .async() + .map(fn) + .inspectErr((err) => { + console.log(err); + }) + .ok(); +assert(num === 9); +``` + ## Helpers for Resultifying -### resultify +### resultifyAsync Takes a function and returns a version that returns results asynchronously. ```ts import fs from 'node:fs/promises'; +import { resultifyAsync } from 'rustlike-result'; -const copyFile1 = resultify(fs.copyFile); -const copyFile2 = resultify()(fs.copyFile); +const copyFile1 = resultifyAsync(fs.copyFile); +const copyFile2 = resultifyAsync()(fs.copyFile); ``` -### resultify.sync +### resultifySync Takes a function and returns a version that returns results synchronously. ```ts +import { resultifySync } from 'rustlike-result'; + /** * @throws {Error} Some error messages */ @@ -938,18 +1304,20 @@ function fn(): string { // do something } -const fn1 = resultify.sync(fn); -const fn1 = resultify.sync()(fn); +const fn1 = resultifySync(fn); +const fn1 = resultifySync()(fn); ``` In the context where async functions are not allowed, you can use this function to resultify the sync function. -### resultify.promise +### resultifyPromise Takes a promise and returns a new promise that contains a result. ```ts -const result = await resultify.promise(promise); +import { resultifyPromise } from 'rustlike-result'; + +const result = await resultifyPromise(promise); ``` Due to the limit of TypeScript,it's impossible to resultify overloaded functions perfectly that the returned functions are still overloaded. diff --git a/src/Result.ts b/src/Result.ts index 41536ef..3894fee 100644 --- a/src/Result.ts +++ b/src/Result.ts @@ -105,8 +105,8 @@ export interface Result { * import { Err, Ok, type Result } from 'rustlike-result'; * * enum ErrorKind { - * NOT_FOUND, - * PERMISSION_DENIED, + * NOT_FOUND, + * PERMISSION_DENIED, * } * * const x: Result = Err(ErrorKind.NOT_FOUND); @@ -134,8 +134,8 @@ export interface Result { * import { Err, Ok, type Result } from 'rustlike-result'; * * enum ErrorKind { - * NOT_FOUND, - * PERMISSION_DENIED, + * NOT_FOUND, + * PERMISSION_DENIED, * } * * const x: Result = Err(ErrorKind.NOT_FOUND); @@ -867,11 +867,58 @@ export interface Result { /** * Returns `true` if `self` equals to `other`. + * + * Examples: + * + * ``` + * import { Err, Ok } from 'rustlike-result'; + * + * assert(Ok(1).equal(Ok(1))); + * assert(Ok(NaN).equal(Ok(NaN))); + * assert(Err('err').equal(Err('err'))); + * + * assert(Ok(1).equal(Ok(2)) === false); + * assert(Err('err 1').equal(Err(-1)) === false); + * + * assert(Ok(Ok(1)).equal(Ok(Ok(1)))); + * assert(Ok(Err('err')).equal(Ok(Err('err')))); + * assert(Err(Err('err')).equal(Err(Err('err')))); + * assert(Err(Ok(1)).equal(Err(Ok(1)))); + * + * assert(Ok(Ok(1)).equal(Ok(Ok(2))) === false); + * assert(Ok(Ok(1)).equal(Ok(Err('err'))) === false); + * assert(Err(Ok(1)).equal(Err(Err('err'))) === false); + * + * assert(Ok([1]).equal(Ok([1])) === false); + * assert(Ok({ foo: 1 }).equal(Ok({ foo: 1 })) === false); + * assert(Ok(Ok([1])).equal(Ok(Ok([1]))) === false); + * assert(Ok(Ok({ foo: 1 })).equal(Ok(Ok({ foo: 1 }))) === false); + * ``` */ equal(other: Result): boolean; /** * Converts this result to an async `Result` so it can work in asynchronous code. + * + * Examples: + * + * ``` + * import { Ok } from 'rustlike-result'; + * + * function fn(value: number): Promise { + * // do something asynchronously + * return Promise.resolve(value ** 2); + * } + * + * const num = await Ok(3) + * .async() + * .map(fn) + * .inspectErr((err) => { + * console.log(err); + * }) + * .ok(); + * assert(num === 9); + * ``` */ async(): ResultAsync; } diff --git a/src/ResultAsync.ts b/src/ResultAsync.ts index 683f1ed..8c9c9b1 100644 --- a/src/ResultAsync.ts +++ b/src/ResultAsync.ts @@ -81,8 +81,8 @@ export interface ResultAsync extends PromiseLike> { * import { ErrAsync, OkAsync, type ResultAsync } from 'rustlike-result'; * * enum ErrorKind { - * NOT_FOUND, - * PERMISSION_DENIED, + * NOT_FOUND, + * PERMISSION_DENIED, * } * * const x: ResultAsync = ErrAsync(ErrorKind.NOT_FOUND); @@ -229,8 +229,6 @@ export interface ResultAsync extends PromiseLike> { * Examples: * * ``` - * import { resultify } from 'rustlike-result'; - * * const num = await OkAsync(4) * .inspect((value) => { * console.log(`original: ${value}`); @@ -250,8 +248,6 @@ export interface ResultAsync extends PromiseLike> { * Examples: * * ``` - * import { resultify } from 'rustlike-result'; - * * const result = ErrAsync(new SyntaxError('Some error message')).inspectErr((err) => { * console.log(`failed to do something: ${err.message}`); * }); @@ -591,6 +587,38 @@ export interface ResultAsync extends PromiseLike> { /** * Asynchronously returns `true` if `self` equals to `other`. + * + * Examples: + * + * ``` + * import { Err, ErrAsync, Ok, OkAsync, type ResultAsync } from 'rustlike-result'; + * + * assert(await OkAsync(1).equal(Ok(1))); + * assert(await OkAsync(1).equal(Promise.resolve(Ok(1)))); + * assert(await OkAsync(1).equal(OkAsync(1))); + * + * assert((await OkAsync(1).equal(Ok(2))) === false); + * assert((await OkAsync(1).equal(Promise.resolve(Ok(2)))) === false); + * assert((await OkAsync(1).equal(OkAsync(2))) === false); + * + * assert(await OkAsync(Ok(1)).equal(Ok(Ok(1)))); + * assert(await OkAsync(Ok(1)).equal(Ok(OkAsync(1)))); + * assert(await OkAsync(Ok(1)).equal(Promise.resolve(Ok(Ok(1))))); + * assert(await OkAsync(Ok(1)).equal(OkAsync(Promise.resolve(Ok(1))))); + * assert(await OkAsync(Ok(1)).equal(OkAsync(OkAsync(1)))); + * assert(await OkAsync(Promise.resolve(Ok(1))).equal(Promise.resolve(Ok(OkAsync(1))))); + * assert(await OkAsync(OkAsync(1)).equal(OkAsync(Ok(1)))); + * + * assert((await OkAsync([1]).equal(Ok([1]))) === false); + * assert((await OkAsync({ foo: 1 }).equal(Promise.resolve(Ok({ foo: 1 })))) === false); + * assert((await ErrAsync({ message: 'err' }).equal(ErrAsync({ message: 'err' }))) === false); + * + * assert((await OkAsync(Ok([1])).equal(Ok(Ok([1])))) === false); + * assert((await OkAsync(Ok([1])).equal(OkAsync(OkAsync([1])))) === false); + * assert((await OkAsync(Promise.resolve(Ok([1]))).equal(OkAsync(Ok([1])))) === false); + * assert((await OkAsync(Promise.resolve(Ok({ foo: 1 }))).equal(Ok(OkAsync({ foo: 1 })))) === false); + * assert((await OkAsync(OkAsync({ foo: 1 })).equal(OkAsync(OkAsync({ foo: 1 })))) === false); + * ``` */ equal( other: Result | Promise> | ResultAsync, diff --git a/src/__tests__/RustlikeResult.test.ts b/src/__tests__/RustlikeResult.test.ts index a714913..7ee1539 100644 --- a/src/__tests__/RustlikeResult.test.ts +++ b/src/__tests__/RustlikeResult.test.ts @@ -1572,6 +1572,33 @@ describe(`Test method \`${RustlikeResult.name}.prototype.${RustlikeResult.protot expect(Ok(Ok({ foo: 1 })).equal(Ok(Ok({ foo: 1 })))).toBe(false); expect(Err(Err({ message: 'err' })).equal(Err(Err({ message: 'err' })))).toBe(false); }); + + it('should have correct examples doc', () => { + function examples() { + assert(Ok(1).equal(Ok(1))); + assert(Ok(NaN).equal(Ok(NaN))); + assert(Err('err').equal(Err('err'))); + + assert(Ok(1).equal(Ok(2)) === false); + assert(Err('err 1').equal(Err(-1)) === false); + + assert(Ok(Ok(1)).equal(Ok(Ok(1)))); + assert(Ok(Err('err')).equal(Ok(Err('err')))); + assert(Err(Err('err')).equal(Err(Err('err')))); + assert(Err(Ok(1)).equal(Err(Ok(1)))); + + assert(Ok(Ok(1)).equal(Ok(Ok(2))) === false); + assert(Ok(Ok(1)).equal(Ok(Err('err'))) === false); + assert(Err(Ok(1)).equal(Err(Err('err'))) === false); + + assert(Ok([1]).equal(Ok([1])) === false); + assert(Ok({ foo: 1 }).equal(Ok({ foo: 1 })) === false); + assert(Ok(Ok([1])).equal(Ok(Ok([1]))) === false); + assert(Ok(Ok({ foo: 1 })).equal(Ok(Ok({ foo: 1 }))) === false); + } + + expect(examples).not.toThrow(); + }); }); describe(`Test method \`${RustlikeResult.name}.prototype.${RustlikeResult.prototype.async.name}\``, () => { @@ -1584,4 +1611,24 @@ describe(`Test method \`${RustlikeResult.name}.prototype.${RustlikeResult.protot expect(errResult).toBeInstanceOf(RustlikeResultAsync); await expectResultAsync(errResult, { type: 'err', value: undefined, error: 'Some error message' }); }); + + it('should have correct examples doc', async () => { + async function examples() { + function fn(value: number): Promise { + // do something asynchronously + return Promise.resolve(value ** 2); + } + + const num = await Ok(3) + .async() + .map(fn) + .inspectErr((err) => { + console.log(err); + }) + .ok(); + assert(num === 9); + } + + await expect(examples()).resolves.not.toThrow(); + }); }); diff --git a/src/__tests__/RustlikeResultAsync.test.ts b/src/__tests__/RustlikeResultAsync.test.ts index 0693e01..2e578fb 100644 --- a/src/__tests__/RustlikeResultAsync.test.ts +++ b/src/__tests__/RustlikeResultAsync.test.ts @@ -1011,6 +1011,12 @@ describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAs describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAsync.prototype.equal.name}\``, () => { it('should check if itself equals to another result', async () => { + // simple true + + await expect(OkAsync(1).equal(Ok(1))).resolves.toBe(true); + await expect(OkAsync(1).equal(Promise.resolve(Ok(1)))).resolves.toBe(true); + await expect(OkAsync(1).equal(OkAsync(1))).resolves.toBe(true); + await expect(OkAsync(NaN).equal(Ok(NaN))).resolves.toBe(true); await expect(OkAsync(NaN).equal(Promise.resolve(Ok(NaN)))).resolves.toBe(true); await expect(OkAsync(NaN).equal(OkAsync(NaN))).resolves.toBe(true); @@ -1387,4 +1393,36 @@ describe(`Test method \`${RustlikeResultAsync.name}.prototype.${RustlikeResultAs ErrAsync(ErrAsync({ message: 'err' })).equal(ErrAsync(ErrAsync({ message: 'err' }))), ).resolves.toBe(false); }); + + it('should have correct examples doc', async () => { + async function examples() { + assert(await OkAsync(1).equal(Ok(1))); + assert(await OkAsync(1).equal(Promise.resolve(Ok(1)))); + assert(await OkAsync(1).equal(OkAsync(1))); + + assert((await OkAsync(1).equal(Ok(2))) === false); + assert((await OkAsync(1).equal(Promise.resolve(Ok(2)))) === false); + assert((await OkAsync(1).equal(OkAsync(2))) === false); + + assert(await OkAsync(Ok(1)).equal(Ok(Ok(1)))); + assert(await OkAsync(Ok(1)).equal(Ok(OkAsync(1)))); + assert(await OkAsync(Ok(1)).equal(Promise.resolve(Ok(Ok(1))))); + assert(await OkAsync(Ok(1)).equal(OkAsync(Promise.resolve(Ok(1))))); + assert(await OkAsync(Ok(1)).equal(OkAsync(OkAsync(1)))); + assert(await OkAsync(Promise.resolve(Ok(1))).equal(Promise.resolve(Ok(OkAsync(1))))); + assert(await OkAsync(OkAsync(1)).equal(OkAsync(Ok(1)))); + + assert((await OkAsync([1]).equal(Ok([1]))) === false); + assert((await OkAsync({ foo: 1 }).equal(Promise.resolve(Ok({ foo: 1 })))) === false); + assert((await ErrAsync({ message: 'err' }).equal(ErrAsync({ message: 'err' }))) === false); + + assert((await OkAsync(Ok([1])).equal(Ok(Ok([1])))) === false); + assert((await OkAsync(Ok([1])).equal(OkAsync(OkAsync([1])))) === false); + assert((await OkAsync(Promise.resolve(Ok([1]))).equal(OkAsync(Ok([1])))) === false); + assert((await OkAsync(Promise.resolve(Ok({ foo: 1 }))).equal(Ok(OkAsync({ foo: 1 })))) === false); + assert((await OkAsync(OkAsync({ foo: 1 })).equal(OkAsync(OkAsync({ foo: 1 })))) === false); + } + + await expect(examples()).resolves.not.toThrow(); + }); }); diff --git a/src/resultify.ts b/src/resultify.ts index 55184d0..713abb6 100644 --- a/src/resultify.ts +++ b/src/resultify.ts @@ -18,6 +18,8 @@ async function resultifyPromiseLegacy(promise: Promise): Promise = ( * * Examples: * ```ts + * import { resultifySync } from 'rustlike-result'; + * * function fn(): string { * // throws error if failed * } @@ -59,9 +63,12 @@ export function resultifySync( ): (...args: Args) => Result, E>; /** * Takes a function and returns a version that returns results synchronously. + * This overloaded function allows you to easily specify the error type. * * Examples: * ```ts + * import { resultifySync } from 'rustlike-result'; + * * function fn(): string { * // throws error if failed * } @@ -215,6 +222,7 @@ type CurriedResultifyAsync = ( * Examples: * ```ts * import fs from 'node:fs/promises'; + * import { resultifyAsync } from 'rustlike-result'; * * const copyFile = resultifyAsync(fs.copyFile); * ``` @@ -226,11 +234,12 @@ export function resultifyAsync( ): (...args: Args) => ResultAsync>, E>; /** * Takes a function and returns a version that returns results asynchronously. - * This overloaded function allows you to specify the error type. + * This overloaded function allows you to easily specify the error type. * * Examples: * ```ts * import fs from 'node:fs/promises'; + * import { resultifyAsync } from 'rustlike-result'; * * const copyFile = resultifyAsync()(fs.copyFile); * ```