Skip to content

Commit

Permalink
feat: Add type-guard isResult. (#297)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hannes Leutloff authored Mar 29, 2021
1 parent 4aacd9a commit 2d1e15a
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 0 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,20 @@ const token = validateToken('a token').unwrapOrElse(
);
```

### Recognizing a `Result`

If you need to assert the type of a `Result`, you can use the `isResult` type-guard:

```typescript
import { isResult } from 'defekt';

const someValue: any = someFunction();

if (isResult(someValue)) {
// In this scope someValue is of type Result<any, any>.
}
```

## Running quality assurance

To run quality assurance for this module use [roboter](https://www.npmjs.com/package/roboter):
Expand Down
2 changes: 2 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { defekt } from './defekt';
import { ErrorConstructor } from './ErrorConstructor';
import { isCustomError } from './isCustomError';
import { isError } from './isError';
import { isResult } from './isResult';
import { error, Result, value } from './Result';

export {
Expand All @@ -12,6 +13,7 @@ export {
ErrorConstructor,
isCustomError,
isError,
isResult,
Result,
value
};
16 changes: 16 additions & 0 deletions lib/isResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Result } from './Result';

const isResult = function (value: any): value is Result<any, any> {
return typeof value === 'object' && value !== null &&
Object.keys(value).length === 6 &&
('error' in value || 'value' in value) &&
'hasError' in value && typeof value.hasError === 'function' &&
'hasValue' in value && typeof value.hasValue === 'function' &&
'unwrapOrThrow' in value && typeof value.unwrapOrThrow === 'function' &&
'unwrapOrElse' in value && typeof value.unwrapOrElse === 'function' &&
'unwrapOrDefault' in value && typeof value.unwrapOrDefault === 'function';
};

export {
isResult
};
104 changes: 104 additions & 0 deletions test/unit/isResultTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { assert } from 'assertthat';
import { isResult } from '../../lib/isResult';
import { error, value } from '../../lib/Result';

suite('isResult', (): void => {
test('returns true for a ResultValue.', async (): Promise<void> => {
const resultObject = value('foo');

assert.that(isResult(resultObject)).is.true();
});

test('returns true for a ResultError.', async (): Promise<void> => {
const resultObject = error(new Error('foo'));

assert.that(isResult(resultObject)).is.true();
});

test('returns false for an empty object.', async (): Promise<void> => {
const resultObject = {};

assert.that(isResult(resultObject)).is.false();
});

test('returns false for a number.', async (): Promise<void> => {
const resultObject = 5;

assert.that(isResult(resultObject)).is.false();
});

test('returns false for a string.', async (): Promise<void> => {
const resultObject = 'foo';

assert.that(isResult(resultObject)).is.false();
});

test('returns false for a boolean.', async (): Promise<void> => {
const resultObject = false;

assert.that(isResult(resultObject)).is.false();
});

test('returns false for a symbol.', async (): Promise<void> => {
const resultObject = Symbol('foo');

assert.that(isResult(resultObject)).is.false();
});

test('returns false for an array.', async (): Promise<void> => {
const resultObject = [ 2, 4 ];

assert.that(isResult(resultObject)).is.false();
});

test('returns false for a function.', async (): Promise<void> => {
// eslint-disable-next-line unicorn/consistent-function-scoping
const resultObject = (): number => 5;

assert.that(isResult(resultObject)).is.false();
});

test('returns false for null.', async (): Promise<void> => {
const resultObject = null;

assert.that(isResult(resultObject)).is.false();
});

test('returns false for an object that has a value property but none of the functions.', async (): Promise<void> => {
const resultObject = { value: 'foo' };

assert.that(isResult(resultObject)).is.false();
});

test('returns false for an object that has an error property but none of the functions.', async (): Promise<void> => {
const resultObject = { error: new Error('foo') };

assert.that(isResult(resultObject)).is.false();
});

for (const functionName of [ 'hasError', 'hasValue', 'unwrapOrThrow', 'unwrapOrElse', 'unwrapOrDefault' ]) {
test(`returns false for an object that is missing the function ${functionName}.`, async (): Promise<void> => {
const resultObject = value('foo');

Reflect.deleteProperty(resultObject, functionName);

assert.that(isResult(resultObject)).is.false();
});

test(`returns false for an objects whose ${functionName} property is not a function.`, async (): Promise<void> => {
const resultObject = value('foo');

(resultObject as any)[functionName] = 5;

assert.that(isResult(resultObject)).is.false();
});
}

test('returns false for a result with additional properties.', async (): Promise<void> => {
const resultObject = value('foo');

(resultObject as any).foo = 'bar';

assert.that(isResult(resultObject)).is.false();
});
});

0 comments on commit 2d1e15a

Please sign in to comment.