Skip to content

Commit

Permalink
feat: add isResult, isResultOk, and isResultErr functions (#172)
Browse files Browse the repository at this point in the history
  • Loading branch information
aleclarson authored Aug 15, 2024
1 parent 8e7dfc3 commit 08d4329
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 0 deletions.
72 changes: 72 additions & 0 deletions docs/typed/isResult.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
title: isResult
description: Check if a value is a Result tuple
---

### Usage

Check if a value is a `Result` tuple.

**Don't know what that is?** Read the [Result](#result) section further down.

```ts
import * as _ from 'radashi'

_.isResult([undefined, 42]) // => true
_.isResult([new Error(), undefined]) // => true

// Result tuples cannot have both a value and an error, or neither.
_.isResult([undefined, undefined]) // => false
_.isResult([new Error(), true]) // => false

// Tuple must be of length 2.
_.isResult([new Error()]) // => false
_.isResult([undefined, true, undefined]) // => false

// Non-tuple values are false.
_.isResult([]) // => false
_.isResult({}) // => false
_.isResult(null) // => false
```

Also see the related [isResultOk](/typed/isResultOk) and [isResultErr](/typed/isResultErr) functions.

## Types In-Depth

### Result

“Results” are tuples of 2 elements (an **error** and a **result value**).

- The first element is always the **error**, or `undefined` if the operation was successful.
- The second element is always the **result value**, or `undefined` if the operation failed.
- These tuples are represented by the `Result<TResult, TError>` type.
- A default error type of `Error` is used when no error type is explicitly defined (e.g. `Result<string>`).
- The default error type is _not_ `unknown` because we assume you're following best practices and so you avoid throwing anything but `Error` objects.
- You're free to define the error type to be anything (like `Result<string, Error | number>`), not just `Error` types.

### Ok and Err

There are 2 types of result: `Ok<TResult>` and `Err<TError>`.

- The `Ok` type represents a successful operation. It's a tuple of `[undefined, TResult]`.
- The `Err` type represents a failed operation. It's a tuple of `[TError, undefined]`.

The names "Ok" and "Err" are inspired by Rust's `std::result` module.

To check for an `Ok` result, do this:

```ts
if (isResult(value) && value[0] == null) {
value // <-- now an Ok<unknown> type
value[1] // <-- This is the resulting value!
}
```

To check for an `Err` result, do this:

```ts
if (isResult(value) && value[0] != null) {
value // <-- now an Err<Error> type
value[0] // <-- This is the error!
}
```
19 changes: 19 additions & 0 deletions docs/typed/isResultErr.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: isResultErr
description: Returns true for failed Result tuple
---

### Usage

Check if a value is both a `Result` tuple and an `Err` result.

```ts
declare const value: unknown

if (isResultErr(value)) {
value // <-- now an Err<Error> type
value[0] // <-- This is the error!
}
```

Also see the related [isResult](/typed/isResult) and [isResultOk](/typed/isResultOk) functions.
19 changes: 19 additions & 0 deletions docs/typed/isResultOk.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: isResultOk
description: Returns true for successful Result tuple
---

### Usage

Check if a value is both a `Result` tuple and an `Ok` result.

```ts
declare const value: unknown

if (isResultOk(value)) {
value // <-- now an Ok<unknown> type
value[1] // <-- This is the resulting value!
}
```

Also see the related [isResult](/typed/isResult) and [isResultErr](/typed/isResultErr) functions.
3 changes: 3 additions & 0 deletions src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ export * from './typed/isPlainObject.ts'
export * from './typed/isPrimitive.ts'
export * from './typed/isPromise.ts'
export * from './typed/isRegExp.ts'
export * from './typed/isResult.ts'
export * from './typed/isResultErr.ts'
export * from './typed/isResultOk.ts'
export * from './typed/isSet.ts'
export * from './typed/isString.ts'
export * from './typed/isSymbol.ts'
Expand Down
32 changes: 32 additions & 0 deletions src/typed/isResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { isArray, type Result } from 'radashi'

/**
* Returns true if the value is a `Result` tuple.
*
* @see https://radashi-org.github.io/reference/typed/isResult
* @example
* ```ts
* isResult([undefined, 42]) => true
* isResult([new Error(), undefined]) => true
*
* // Result tuples cannot have both a value and an error, or neither.
* isResult([undefined, undefined]) => false
* isResult([new Error(), true]) => false
*
* // Tuple must be of length 2.
* isResult([new Error()]) => false
* isResult([undefined, true, undefined]) => false
*
* // Non-tuple values are false.
* isResult([]) => false
* isResult({}) => false
* isResult(null) => false
* ```
*/
export function isResult(value: unknown): value is Result<unknown, unknown> {
return (
isArray(value) &&
value.length === 2 &&
(value[0] === undefined) !== (value[1] === undefined)
)
}
17 changes: 17 additions & 0 deletions src/typed/isResultErr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { type Err, isResult } from 'radashi'

/**
* Returns true if the value is an `Err` result.
*
* @see https://radashi-org.github.io/reference/typed/isResultErr
* @example
* ```ts
* isResultErr([new Error(), undefined]) // true
* isResultErr([undefined, "hello"]) // false
* ```
*/
export function isResultErr<TError = Error>(
value: unknown,
): value is Err<TError> {
return isResult(value) && value[0] !== undefined
}
17 changes: 17 additions & 0 deletions src/typed/isResultOk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { isResult, type Ok } from 'radashi'

/**
* Returns true if the value is an `Ok` result.
*
* @see https://radashi-org.github.io/reference/typed/isResultOk
* @example
* ```ts
* isResultOk([undefined, "hello"]) // true
* isResultOk([new Error(), undefined]) // false
* ```
*/
export function isResultOk<TValue = unknown>(
value: unknown,
): value is Ok<TValue> {
return isResult(value) && value[0] === undefined
}
21 changes: 21 additions & 0 deletions tests/typed/isResult.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as _ from 'radashi'

describe('isResult', () => {
test('should return true for valid Result tuples', () => {
expect(_.isResult([undefined, 42])).toBe(true)
expect(_.isResult([new Error(), undefined])).toBe(true)
})

test('should return false for invalid Result tuples', () => {
expect(_.isResult([undefined, undefined])).toBe(false)
expect(_.isResult([new Error(), true])).toBe(false)
expect(_.isResult([new Error()])).toBe(false)
expect(_.isResult([undefined, true, undefined])).toBe(false)
})

test('should return false for non-tuple values', () => {
expect(_.isResult([])).toBe(false)
expect(_.isResult({})).toBe(false)
expect(_.isResult(null)).toBe(false)
})
})
15 changes: 15 additions & 0 deletions tests/typed/isResultErr.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as _ from 'radashi'

describe('isResultErr', () => {
test('valid Err results', () => {
expect(_.isResultErr([new Error(), undefined])).toBe(true)
})
test('invalid Err results', () => {
expect(_.isResultErr([undefined, 42])).toBe(false)
})
test('other values', () => {
expect(_.isResultErr([])).toBe(false)
expect(_.isResultErr({})).toBe(false)
expect(_.isResultErr(null)).toBe(false)
})
})
15 changes: 15 additions & 0 deletions tests/typed/isResultOk.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as _ from 'radashi'

describe('isResultOk', () => {
test('valid Ok results', () => {
expect(_.isResultOk([undefined, 42])).toBe(true)
})
test('invalid Ok results', () => {
expect(_.isResultOk([new Error(), undefined])).toBe(false)
})
test('other values', () => {
expect(_.isResultOk([])).toBe(false)
expect(_.isResultOk({})).toBe(false)
expect(_.isResultOk(null)).toBe(false)
})
})

0 comments on commit 08d4329

Please sign in to comment.