0.4.3 • Published 28 days ago

rustlike-result v0.4.3

Weekly downloads
-
License
MIT
Repository
github
Last release
28 days ago

rustlike-result

Rust-like Result for JavaScript.

Result is a type that represents either success (Ok) or failure (Err).

Table Of Contents

Installation

> npm install rustlike-result
> yarn add rustlike-result
> pnpm install rustlike-result

Usage

This package implement a Rust-like Result, nearly all methods are similar to the Result.

const ok = Ok(1);
const err = Err('Some error message');
import fs, { Dirent, Stats } from 'node:fs/promises';

const result1: Result<Stats, Error> = await fs
    .stat(path)
    .then((value) => Ok(value))
    .catch((err) => Err(err));

const result2: Result<Dirent[], Error> = await fs
    .readdir(path, { withFileTypes: true })
    .then((value) => Ok(value))
    .catch((err) => Err(err));

About Rust Option

This package doesn't implement Rust-like Option. Handling undefined/null is not as hard as it was a few years ago, because right now we already have proposal-optional-chaining and proposal-nullish-coalescing to help handle it.

Methods Documentation

Rust Result Methods

The Rust-like Result implements the following methods:

Rust-like Result methodRust Result method
isOkis_ok
isOkAnd / isOkAndAsyncis_ok_and
isErris_err
isErrAnd / isErrAndAsyncis_err_and
okok
errerr
map / mapAsyncmap
mapOr / mapOrAsyncmap_or
mapOrElse / mapOrElseAsyncmap_or_else
mapErr / mapErrAsyncmap_err
inspect / inspectAsyncinspect
inspectErr / inspectErrAsyncinspect_err
expectexpect
unwrapunwrap
expectErrexpect_err
unwrapErrunwrap_err
unwrapOrunwrap_or
unwrapOrElse / unwrapOrElseAsyncunwrap_or_else
unwrapUncheckedunwrap_unchecked
unwrapErrUncheckedunwrap_err_unchecked
andand
andThen / andThenAsyncand_then
oror
orElse / orElseAsyncor_else
transposetranspose

Unlike Rust, JavaScript doesn't have the 'Ownership' feature, so some API like as_ref are not necessary. These implementations are not implemented:

<!-- implementations -->
as_ref
as_mut
inspect (unstable)
inspect_err (unstable)
as_deref
as_deref_mut
iter
iter_mut
unwrap_or_default
into_ok (unstable)
into_err (unstable)
copied
cloned
flatten (unstable)

<!-- some of trait implementations -->
clone
clone_from
fmt
hash

isOk

Returns true if the result is Ok.

Examples:

import { Err, Ok, type Result } from 'rustlike-result';

const x: Result<number, string> = Ok(2);
assert(x.isOk() === true);

const y: Result<number, string> = Err('Some error message');
assert(y.isOk() === false);

isOkAnd

Returns true if the result is Ok and the value inside of it matches a predicate.

Examples:

import { Err, Ok, type Result } from 'rustlike-result';

const x: Result<number, string> = Ok(2);
assert(x.isOkAnd((value) => value > 1) === true);

const y: Result<number, string> = Ok(0);
assert(y.isOkAnd((value) => value > 1) === false);

const z: Result<number, string> = 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:

import { Err, Ok, type Result } from 'rustlike-result';

const x: Result<number, string> = Ok(2);
assert((await x.isOkAndAsync((value) => Promise.resolve(value > 1))) === true);

const y: Result<number, string> = Ok(0);
assert((await y.isOkAndAsync((value) => Promise.resolve(value > 1))) === false);

const z: Result<number, string> = Err('Some error message');
assert((await z.isOkAndAsync((value) => Promise.resolve(value > 1))) === false);

isErr

Returns true if the result is Err.

Examples:

import { Err, Ok, type Result } from 'rustlike-result';

const x: Result<number, string> = Ok(-3);
assert(x.isErr() === false);

const y: Result<number, string> = Err('Some error message');
assert(y.isErr() === true);

isErrAnd

Returns true if the result is Err and the value inside of it matches a predicate.

Examples:

import { Err, Ok, type Result } from 'rustlike-result';

enum ErrorKind {
NOT_FOUND,
PERMISSION_DENIED,
}

const x: Result<number, ErrorKind> = Err(ErrorKind.NOT_FOUND);
assert(x.isErrAnd((value) => value === ErrorKind.NOT_FOUND) === true);

const y: Result<number, ErrorKind> = Err(ErrorKind.PERMISSION_DENIED);
assert(y.isErrAnd((value) => value === ErrorKind.NOT_FOUND) === false);

const z: Result<number, ErrorKind> = 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:

import { Err, Ok, type Result } from 'rustlike-result';

enum ErrorKind {
NOT_FOUND,
PERMISSION_DENIED,
}

const x: Result<number, ErrorKind> = Err(ErrorKind.NOT_FOUND);
assert((await x.isErrAndAsync((value) => Promise.resolve(value === ErrorKind.NOT_FOUND))) === true);

const y: Result<number, ErrorKind> = Err(ErrorKind.PERMISSION_DENIED);
assert((await y.isErrAndAsync((value) => Promise.resolve(value === ErrorKind.NOT_FOUND))) === false);

const z: Result<number, ErrorKind> = Ok(123);
assert((await z.isErrAndAsync((value) => Promise.resolve(value === ErrorKind.NOT_FOUND))) === false);

ok

Converts from Result<T, E> to Optional<T> and discarding the error, if any.

Examples:

import { Err, Ok, type Result } from 'rustlike-result';

const x: Result<number, string> = Ok(2);
assert(x.ok() === 2);

const y: Result<number, string> = Err('Some error message');
assert(y.ok() === undefined);

err

Converts from Result<T, E> to Optional<E> and discarding the success value, if any.

Examples:

import { Err, Ok, type Result } from 'rustlike-result';

const x: Result<number, string> = Ok(2);
assert(x.err() === undefined);

const y: Result<number, string> = Err('Some error message');
assert(y.err() === 'Some error message');

map

Maps a Result<T, E> to Result<U, E> 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 { Ok, type Result } from 'rustlike-result';

const x: Result<string, string> = Ok('foo');
assert(x.map((value) => value.length).ok() === 3);

mapAsync

Asynchronously maps a Result<T, E> to Result<U, E> 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 { Ok } from 'rustlike-result';

const x = await Ok<string, string>('foo').mapAsync((value) => Promise.resolve(value.length));
assert(x.ok() === 3);

mapOr

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 { Err, Ok, type Result } from 'rustlike-result';

const x: Result<string, string> = Ok('foo');
assert(x.mapOr(42, (value) => value.length) === 3);

const y: Result<string, string> = 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:

import { Err, Ok, type Result } from 'rustlike-result';

const x: Result<string, string> = Ok('foo');
assert((await x.mapOrAsync(42, (value) => value.length)) === 3);

const y: Result<string, string> = Err('bar');
assert((await y.mapOrAsync(42, (value) => value.length)) === 42);

mapOrElse

Maps a Result<T, E> 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 { Err, Ok, type Result } from 'rustlike-result';

const k = 21;

const x: Result<string, string> = Ok('foo');
assert(x.mapOrElse((err) => k * 2, (value) => value.length) === 3);

const y: Result<string, string> = Err('bar');
assert(y.mapOrElse((err) => k * 2, (value) => value.length) === 42);

mapOrElseAsync

Asynchronously maps a Result<T, E> 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 { Err, Ok, type Result } from 'rustlike-result';

const k = 21;

const x: Result<string, string> = Ok('foo');
assert((await x.mapOrElseAsync(() => Promise.resolve(k * 2), (value) => Promise.resolve(value.length))) === 3);

const y: Result<string, string> = Err('bar');
assert((await y.mapOrElseAsync(() => Promise.resolve(k * 2), (value) => Promise.resolve(value.length))) === 42);

mapErr

Maps a Result<T, E> to Result<T, F> 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 { Err, type Result } from 'rustlike-result';

const x: Result<number, Error> = Err(new Error('Some error message'));
assert(x.mapErr((err) => err.message).err() === 'Some error message');

mapErrAsync

Asynchronously maps a Result<T, E> to Result<T, F> 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 { 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

Calls the provided closure with a reference to the contained value if Ok.

Examples:

import { resultify } from 'rustlike-result';

const num = resultify
    .sync<SyntaxError>()(JSON.parse)('4')
    .inspect((value: number) => console.log(`original: ${value}`))
    .map((value) => value ** 3)
    .expect('failed to parse number');
assert(num === 64);

inspectAsync

Asynchronously calls the provided closure with a reference to the contained value if Ok.

Examples:

import { resultify } from 'rustlike-result';

const num = await resultify
    .sync<SyntaxError>()(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

Calls the provided closure with a reference to the contained value if Err.

Examples:

import { resultify } from 'rustlike-result';

const num = resultify
    .sync<SyntaxError>()(JSON.parse)('asdf')
    .inspectErr((err) => console.log(`failed to parse json string: ${err.message}`));
assert(num.err() instanceof SyntaxError);

inspectErrAsync

Asynchronously calls the provided closure with a reference to the contained value if Err.

Examples:

import { resultify } from 'rustlike-result';

const num = await resultify
    .sync<SyntaxError>()(JSON.parse)('asdf')
    .inspectErrAsync((err) => {
        console.log(`failed to parse json string: ${err.message}`);
        return Promise.resolve();
    });
assert(num.err() instanceof SyntaxError);

expect

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 { Err, type Result } from 'rustlike-result';

const x: Result<number, string> = Err('emergency failure');
x.expect('Failed to operate'); // throws Error('Failed to operate: emergency failure')

unwrap

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 { Err, Ok, type Result } from 'rustlike-result';

const x: Result<number, string> = Ok(2);
assert(x.unwrap() === 2);

const y: Result<number, string> = Err('emergency failure');
y.unwrap(); // throws Error('emergency failure')

expectErr

Returns the contained Err value.

Throws an Error if itself is Err, with an error message provided by the Ok's value.

Examples:

import { Ok, type Result } from 'rustlike-result';

const x: Result<number, string> = Ok(10);
x.expectErr('Testing expectErr'); // throws Error('Testing expectErr: 10')

unwrapErr

Returns the contained Err value.

Throws an Error if itself is Ok, with an error message provided by the Ok's value.

Examples:

import { Ok, type Result } from 'rustlike-result';

const x: Result<number, string> = Err('emergency failure');
assert(x.unwrapErr() === 'emergency failure');

const y: Result<number, string> = Ok(2);
y.unwrapErr(); // throws Error(2)

unwrapOr

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 { Err, Ok, type Result } from 'rustlike-result';

const $default = 2;
const x: Result<number, string> = Ok(9);
assert(x.unwrapOr($default) === 9);

const y: Result<number, string> = Err('error');
assert(y.unwrapOr($default) === $default);

unwrapOrElse

Returns the contained Ok value or computes it from a closure.

Examples:

import { Err, Ok } from 'rustlike-result';

const count = (err: string) => err.length;
assert(Ok<number, string>(2).unwrapOrElse(count) === 2);
assert(Err<number, string>('foo').unwrapOrElse(count) === 3);

unwrapOrElseAsync

Asynchronously returns the contained Ok value or computes it from a closure.

Examples:

import { Err, Ok } from 'rustlike-result';

const count = (err: string) => Promise.resolve(err.length);
assert((await Ok<number, string>(2).unwrapOrElseAsync(count)) === 2);
assert((await Err<number, string>('foo').unwrapOrElseAsync(count)) === 3);

unwrapUnchecked

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 { Err, Ok, type Result } from 'rustlike-result';

const x: Result<number, string> = Ok(2);
assert(x.unwrapUnchecked() === 2);

const y: Result<number, string> = Err('emergency failure');
y.unwrapUnchecked();

unwrapErrUnchecked

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 { Err, Ok, type Result } from 'rustlike-result';

const x: Result<number, string> = Ok(2);
x.unwrapErrUnchecked();

const y: Result<number, string> = Err('emergency failure');
assert(y.unwrapErrUnchecked() === 'emergency failure');

and

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, Ok, type Result } from 'rustlike-result';

let x: Result<number, string>;
let y: Result<string, string>;

x = Ok(2);
y = Err('late error');
assert(x.and(y).equal(Err('late error')));

x = Err('early error');
y = Ok('foo');
assert(x.and(y).equal(Err('early error')));

x = Err('not a 2');
y = Err('late error');
assert(x.and(y).equal(Err('not a 2')));

x = Ok(2);
y = Ok('different result type');
assert(x.and(y).equal(Ok('different result type')));

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.

Examples:

import { Err, Ok } from 'rustlike-result';

const parseJSON = (json: string) =>
    resultify
    .sync<SyntaxError>()(JSON.parse)(json)
        .mapErr((err) => err.message);

assert(Ok<string, string>('2').andThen(parseJSON).equal(Ok(2)));
assert(
    Ok<string, string>('asdf')
        .andThen(parseJSON)
        .equal(Err('Unexpected token \'a\', "asdf" is not valid JSON')),
);

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:

import { Err, Ok } from 'rustlike-result';

const parseJSON = (json: string) =>
    Promise.resolve(
        resultify
            .sync<SyntaxError>()(JSON.parse)(json)
            .mapErr((err) => err.message),
    );

const x = await Ok<string, string>('2').andThenAsync(parseJSON);
assert(x.equal(Ok(2)));

const y = await Ok<string, string>('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

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, Ok, type Result } from 'rustlike-result';

let x: Result<number, string>;
let y: Result<number, string>;

x = Ok(2);
y = Err('late error');
assert(x.or(y).equal(Ok(2)));

x = Err('early error');
y = Ok(2);
assert(x.or(y).equal(Ok(2)));

x = Err('not a 2');
y = Err('late error');
assert(x.or(y).equal(Err('late error')));

x = Ok(2);
y = Ok(100);
assert(x.and(y).equal(Ok('different result type')));

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.

Examples:

import { Err, Ok, type Result } from 'rustlike-result';

const sq = (num: number): Result<number, number> => Ok(num * num);
const err = (num: number): Result<number, number> => Err(num);

assert(Ok(2).orElse(sq).orElse(sq).equal(Ok(2)));
assert(Ok(2).orElse(err).orElse(sq).equal(Ok(2)));
assert(Err<number, number>(3).orElse(sq).orElse(err).equal(Ok(9)));
assert(Err<number, number>(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:

import { Err, Ok, type Result } from 'rustlike-result';

const sq = (num: number): Promise<Result<number, number>> => Promise.resolve(Ok(num * num));
const err = (num: number): Promise<Result<number, number>> => 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<number, number>(3)
    .orElseAsync(sq)
    .then((result) => result.orElseAsync(err));
assert(y.equal(Ok(9)));

const z = await Err<number, number>(3)
    .orElseAsync(err)
    .then((result) => result.orElseAsync(err));
assert(z.equal(Err(3)));

transpose

Transposes a Result of an optional value into an optional of a Result.

Ok(undefined | null) will be mapped to undefined. Ok(_) and Err(_) will be mapped to Ok(_) and Err(_).

Examples:

import { Err, Ok, type Result } from 'rustlike-result';

type SomeErr = unknown;

let x: Result<number | undefined | null, SomeErr>;
let y: Result<number, SomeErr> | undefined;

x = Ok(5);
y = Ok(5);
assert(x.transpose()!.equal(y));

x = Ok(undefined);
y = undefined;
assert(x.transpose() === y);

x = Ok(null);
y = undefined;
assert(x.transpose() === y);

Additional Methods

equal

You can not just use === or == to compare Result, so Result itself provides an method call equal for that.

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);

expect(Ok({ foo: 1 }).equal(Ok({ foo: 1 }))).toBe(false);
expect(Ok([1]).equal(Ok([1]))).toBe(false);

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.

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.

Helpers for Resultifying

resultify

Takes a function and returns a version that returns results asynchronously.

import fs from 'node:fs/promises';

const copyFile1 = resultify(fs.copyFile);
const copyFile2 = resultify<Error>()(fs.copyFile);

resultify.sync

Takes a function and returns a version that returns results synchronously.

/**
 * @throws {Error} Some error messages
 */
function fn(): string {
    // do something
}

const fn1 = resultify.sync(fn);
const fn1 = resultify.sync<Error>()(fn);

In the context where async functions are not allowed, you can use this function to resultify the sync function.

resultify.promise

Takes a promise and returns a new promise that contains a result.

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.

JSON Serialization & Deserialization

You can always write your (de)serialization implementation for your use cases. But before you write it, you can check following helper functions to see if they can help you.

Built-in Simple Implementation

This package provides a simple implementation for JSON (de)serialization.

// serialization
ResultJSON.serialize(Ok(1)) // { type: 'ok', value: 1 }
ResultJSON.serialize(Err('Some error message')) // { type: 'err', value: 'Some error message' }
ResultJSON.serialize(Ok(Ok(2))) // { type: 'ok', value: { type: 'ok', value: 2 } }

// deserialization
ResultJSON.deserialize({ type: 'ok', value: 1 }) // Ok(1)
ResultJSON.deserialize({ type: 'err', value: 'Some error message' }) // Err('Some error message')
ResultJSON.deserialize({ type: 'ok', value: { type: 'ok', value: 2 } }) // Ok({ type: 'ok', value: 2 }) *the nested `Result` won't be deserialized*

This simple implementation only covers a few use cases. It may not be suitable if:

  • the Result has a nested Result
  • the Result is in a complex structure
  • the Result contains a complex object, such as a class instance, requiring custom (de)serialization

Community (De)Serialization Solutions

There're some great JSON (de)serialization libraries for complex objects. This package also provides some helper functions to help you use some of them.

serializr

Please install serializr first, then you can use two helper functions resultPropSchema and createResultModelSchema as shown in the following example:

import { createResultModelSchema, resultPropSchema } from 'rustlike-result/serializr';

class User {
    username: string;
    password: string;
}

const userSchema = createModelSchema(User, {
    username: primitive(),
    password: primitive(),
})

// example 1

class Job {
    result: Result<User[], string>;
}

const schema = createModelSchema(Job, {
    result: resultPropSchema({ ok: list(object(userSchema)) }),
});

const job: Job;
serialize(schema, job)
// {
//   result: {
//     type: 'ok',
//     value: [{ username: '<name>', password: '<password>' }, { ... }, ...],
//   },
// }

// example 2

const schema = createResultModelSchema({ ok: list(object(userSchema)) });

const result: Result<User[], string>;
serialize(schema, result)
// {
//   type: 'ok',
//   value: [{ username: '<name>', password: '<password>' }, { ... }, ...],
// }

class-transformer

TODO.

JSON Representation Format

The format of the JSON object follows the adjacently tagged enum representation in Rust library Serde. The reason it doesn't follow the externally tagged enum representation (the default in Serde) is that, the externally tagged representation of Ok(undefined) and Err(undefined) are both {}, therefore we can't tell whether {} should be deserialized to Ok(undefined) or Err(undefined).

Write Your Own Implementation of Result?

Although you do have the ability to do so, it's not recommended that you write your own implementation.

The default implementation that this package provides should meet your requirements in most cases. If if leaks some abilities please feel free to file an issue.

License

The rustlike-result project is available as open source under the terms of the MIT license.

0.4.3

28 days ago

0.4.2

2 months ago

0.4.1

2 months ago

0.4.0

2 months ago

0.3.2

4 months ago

0.3.0

8 months ago

0.3.1

8 months ago

0.3.0-alpha

8 months ago

0.2.0

8 months ago

0.1.0

8 months ago