Skip to content

Commit

Permalink
chore(fix): allow Result tuple to be [undefined, undefined] (#174)
Browse files Browse the repository at this point in the history
  • Loading branch information
aleclarson authored Aug 16, 2024
1 parent c772099 commit ada3225
Show file tree
Hide file tree
Showing 5 changed files with 26 additions and 23 deletions.
17 changes: 10 additions & 7 deletions docs/typed/isResult.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ 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
Expand All @@ -27,6 +23,9 @@ _.isResult([undefined, true, undefined]) // => false
_.isResult([]) // => false
_.isResult({}) // => false
_.isResult(null) // => false

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

Also see the related [isResultOk](/typed/isResultOk) and [isResultErr](/typed/isResultErr) functions.
Expand All @@ -38,11 +37,9 @@ Also see the related [isResultOk](/typed/isResultOk) and [isResultErr](/typed/is
“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.
- The second element is always the **result value**, unless an error occurred.
- 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

Expand All @@ -56,6 +53,8 @@ The names "Ok" and "Err" are inspired by Rust's `std::result` module.
To check for an `Ok` result, do this:

```ts
declare const value: unknown

if (isResult(value) && value[0] == null) {
value // <-- now an Ok<unknown> type
value[1] // <-- This is the resulting value!
Expand All @@ -65,8 +64,12 @@ if (isResult(value) && value[0] == null) {
To check for an `Err` result, do this:

```ts
declare const value: unknown

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

You can also use the `isResultOk` and `isResultErr` functions to check for `Ok` and `Err` results respectively.
13 changes: 6 additions & 7 deletions src/typed/isResult.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isArray, type Result } from 'radashi'
import { isArray, isError, type Result } from 'radashi'

/**
* Returns true if the value is a `Result` tuple.
Expand All @@ -9,10 +9,6 @@ import { isArray, type Result } 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
Expand All @@ -21,12 +17,15 @@ import { isArray, type Result } from 'radashi'
* isResult([]) => false
* isResult({}) => false
* isResult(null) => false
*
* // Result tuples cannot have both a value and an error.
* isResult([new Error(), true]) => false
* ```
*/
export function isResult(value: unknown): value is Result<unknown, unknown> {
export function isResult(value: unknown): value is Result<unknown> {
return (
isArray(value) &&
value.length === 2 &&
(value[0] === undefined) !== (value[1] === undefined)
(isError(value[0]) ? value[1] : value[0]) === undefined
)
}
2 changes: 1 addition & 1 deletion src/typed/isResultErr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { type Err, isResult } from 'radashi'
* isResultErr([undefined, "hello"]) // false
* ```
*/
export function isResultErr<TError = Error>(
export function isResultErr<TError extends Error = Error>(
value: unknown,
): value is Err<TError> {
return isResult(value) && value[0] !== undefined
Expand Down
14 changes: 7 additions & 7 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export type Ok<TResult> = [err: undefined, result: TResult]
* // ^? [TypeError | MyCoolCustomError, undefined]
* ```
*/
export type Err<TError = Error> = [err: NonNullable<TError>, result: undefined]
export type Err<TError extends Error = Error> = [err: TError, result: undefined]

/**
* A result tuple.
Expand All @@ -139,9 +139,9 @@ export type Err<TError = Error> = [err: NonNullable<TError>, result: undefined]
* // ^? Ok<string> | Err<TypeError>
* ```
*/
export type Result<TResult, TError = Error> =
export type Result<TResult, TError extends Error = Error> =
| Ok<TResult>
| Err<NonNullable<TError>>
| Err<TError>

/**
* A promise that resolves to a result tuple.
Expand All @@ -155,12 +155,12 @@ export type Result<TResult, TError = Error> =
* // ^? Promise<Ok<string> | Err<TypeError>>
* ```
*/
export type ResultPromise<TResult, TError = Error> = Promise<
[NonNullable<TError>] extends [never]
export type ResultPromise<TResult, TError extends Error = Error> = Promise<
[TError] extends [never]
? Ok<TResult>
: [TResult] extends [never]
? Err<NonNullable<TError>>
: Result<TResult, NonNullable<TError>>
? Err<TError>
: Result<TResult, TError>
>

/**
Expand Down
3 changes: 2 additions & 1 deletion tests/typed/isResult.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ describe('isResult', () => {
test('should return true for valid Result tuples', () => {
expect(_.isResult([undefined, 42])).toBe(true)
expect(_.isResult([new Error(), undefined])).toBe(true)
expect(_.isResult([new TypeError(), undefined])).toBe(true)
expect(_.isResult([undefined, 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)
Expand Down

0 comments on commit ada3225

Please sign in to comment.