Skip to content

Commit

Permalink
asNonNullish like assertNonNullish but returns the value (#675)
Browse files Browse the repository at this point in the history
# Motivation

We sometimes have a value of type `T | undefined` but we know it is of
type `T` so we use `foo as T` before using it.
It would be safer to use `assertNonNullish` first but this can be
tedious because it can't be used inline.

For example, something as simple as:
```
const getToken = (universe) => {
  return universe.canisterId == OWN_CANISTER_ID_TEXT ? ICPToken : universe.summary?.token as Token;
};
```
would become
```
const getToken = (universe) => {
  if (universe.canisterId == OWN_CANISTER_ID_TEXT) {
    return ICPToken;
  } else {
    assertNonNullish(universe.summary);
    return universe.summary.token;
  }
};
```
It would be nicer if we could use `assertNonNullish` inline like this:
```
const getToken = (universe) => {
  return universe.canisterId == OWN_CANISTER_ID_TEXT ? ICPToken : assertNonNullish(universe.summary).token;
};
```

I've tried to make this work but it doesn't seem possible to both assert
the type of the argument and also return the argument. See
https://stackoverflow.com/questions/78770654/typescript-assertion-function-that-returns-asserted-value

So I'm adding an extra function `asNonNullish` which can be used as in
the second use case.

# Changes

1. Added `asNonNullish` which asserts that its argument is non-nullish
and returns it.
2. Simply the syntax of `assertNonNullish` by using `function` instead
of `=>`.

# Tests

1. Added a missing unit test for `assertNonNullish` which tests (at
compile time) that the type of the value becomes non-nullish. This test
unfortunately doesn't pass if `assertNonNullish` is replaced with
`asNonNullish`.
3. Added the same unit tests for `asNonNullish` that already existed for
`assertNonNullish`.
4. Added one more unit test for `asNonNullish` that tests that the value
is returned. This test would not pass for `assertNonNullish`.

# Todos

- [x] Add entry to changelog (if necessary).

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
dskloetd and github-actions[bot] authored Jul 22, 2024
1 parent b9f9a59 commit 4261747
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Add optional `includeEmptyNeurons` parameter to `listNeurons`.
- Extend `eip1559TransactionPrice` for Erc20.
- Add "Protocol Canister Management" and "Service Nervous System Management" topics support.
- Add `asNonNullish` function, like `assertNonNullish` but returns the value.

## Fix

Expand Down
11 changes: 10 additions & 1 deletion packages/utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ npm i @dfinity/agent @dfinity/candid @dfinity/principal
- [createAgent](#gear-createagent)
- [createServices](#gear-createservices)
- [assertNonNullish](#gear-assertnonnullish)
- [asNonNullish](#gear-asnonnullish)
- [assertPercentageNumber](#gear-assertpercentagenumber)
- [uint8ArrayToBigInt](#gear-uint8arraytobigint)
- [bigIntToUint8Array](#gear-biginttouint8array)
Expand Down Expand Up @@ -150,13 +151,21 @@ Parameters:

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/utils/src/utils/asserts.utils.ts#L7)

#### :gear: asNonNullish

| Function | Type |
| -------------- | ---------------------------------------------------------------- |
| `asNonNullish` | `<T>(value: T, message?: string or undefined) => NonNullable<T>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/utils/src/utils/asserts.utils.ts#L16)

#### :gear: assertPercentageNumber

| Function | Type |
| ------------------------ | ------------------------------ |
| `assertPercentageNumber` | `(percentage: number) => void` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/utils/src/utils/asserts.utils.ts#L15)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/utils/src/utils/asserts.utils.ts#L21)

#### :gear: uint8ArrayToBigInt

Expand Down
44 changes: 44 additions & 0 deletions packages/utils/src/utils/asserts.utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
asNonNullish,
assertNonNullish,
assertPercentageNumber,
InvalidPercentageError,
Expand Down Expand Up @@ -34,6 +35,49 @@ describe("asserts-utils", () => {
const call = () => assertNonNullish({});
expect(call).not.toThrow();
});

it("should make value of non-nullable type", () => {
const getStringOrNull = (): string | null => "test";
const value: string | null = getStringOrNull();
assertNonNullish(value);
const nonNullValue: string = value;
});
});

describe("asNonNullish", () => {
it("should throw an exception if undefined", () => {
const call = () => asNonNullish(undefined);

expect(call).toThrow();
});

it("should throw an exception if null", () => {
const call = () => asNonNullish(null);

expect(call).toThrow();
});

it("should throw an exception with particular message", () => {
const call = () => asNonNullish(undefined, "Test error");

expect(call).toThrow(new NullishError("Test error"));
});

it("should not throw an exception if valid primitive type", () => {
const call = () => asNonNullish(1);
expect(call).not.toThrow();
});

it("should not throw an exception if valid object", () => {
const call = () => asNonNullish({});
expect(call).not.toThrow();
});

it("should return the value if valid", () => {
const value: string | undefined = "test";
const result: string = asNonNullish(value);
expect(result).toBe(value);
});
});

describe("assertPercentageNumber", () => {
Expand Down
6 changes: 6 additions & 0 deletions packages/utils/src/utils/asserts.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export const assertNonNullish: <T>(
throw new NullishError(message);
}
};

export const asNonNullish = <T>(value: T, message?: string): NonNullable<T> => {
assertNonNullish(value, message);
return value;
};

export const assertPercentageNumber = (percentage: number) => {
if (percentage < 0 || percentage > 100) {
throw new InvalidPercentageError(
Expand Down

0 comments on commit 4261747

Please sign in to comment.