Skip to content

Commit

Permalink
feat: fromNullishNullable (#809)
Browse files Browse the repository at this point in the history
# Motivation

In Juno, I often encounter a pattern where an optional Candid variant of
an optional Candid variant needs to be transformed into a nullish value.
To simplify this, I introduced a utility
([PR](junobuild/juno#1068)) to shorten the
pattern since each `fromNullable` of an `undefined` value requires the
chaining operation `?? []`.

I took a quick look at the NNS dapp and OISY, and it seems to follow a
similar pattern. Therefore, I considered adding the utility to
`@dfinity/utils`.

# Notes

Not against better naming?

# Changes

- Implement `fromNullishNullable`
- Extract reused test data and add two blocks `describe` (in addition to
the test for the new function)

---------

Signed-off-by: David Dal Busco <[email protected]>
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
peterpeterparker and github-actions[bot] authored Jan 20, 2025
1 parent a2f74ea commit 90d7241
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 29 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- Expose types `IcrcApproveError` and `IcrcTransferFromError` in `@dfinity/ledger-icrc`.
- Expose few additional did types in `@dfinity/ledger-icp`.
- Add utility `fromNullishNullable` extracts the value from a nullish Candid-style variant representation.

## Fix

Expand Down
15 changes: 15 additions & 0 deletions packages/utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ npm i @dfinity/agent @dfinity/candid @dfinity/principal
- [toNullable](#gear-tonullable)
- [fromNullable](#gear-fromnullable)
- [fromDefinedNullable](#gear-fromdefinednullable)
- [fromNullishNullable](#gear-fromnullishnullable)
- [jsonReplacer](#gear-jsonreplacer)
- [jsonReviver](#gear-jsonreviver)
- [principalToSubAccount](#gear-principaltosubaccount)
Expand Down Expand Up @@ -391,6 +392,20 @@ Parameters:

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

#### :gear: fromNullishNullable

Extracts the value from a nullish Candid-style variant representation.

| Function | Type |
| --------------------- | ------------------------------------------------------ |
| `fromNullishNullable` | `<T>(value: [] or [T] or undefined) => T or undefined` |

Parameters:

- `value`: - A Candid-style variant or `undefined`.

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

#### :gear: jsonReplacer

A parser that interprets revived BigInt, Principal, and Uint8Array when constructing JavaScript values or objects.
Expand Down
85 changes: 56 additions & 29 deletions packages/utils/src/utils/did.utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,79 @@
import { NullishError } from "./asserts.utils";
import { fromDefinedNullable, fromNullable, toNullable } from "./did.utils";
import {
fromDefinedNullable,
fromNullable,
fromNullishNullable,
toNullable,
} from "./did.utils";

describe("did-utils", () => {
it("should convert from empty array to undefined", () => {
expect(fromNullable([])).toBeUndefined();
});
const test = { test: "1" };

it("should convert from array to object", () => {
const test = { test: "1" };
expect(fromNullable([{ test: "1" }])).toEqual(test);
});
describe("fromNullable", () => {
it("should convert from empty array to undefined", () => {
expect(fromNullable([])).toBeUndefined();
});

it("should convert from undefined to empty array", () => {
expect(toNullable(undefined)).toEqual([]);
it("should convert from array to object", () => {
expect(fromNullable([{ test: "1" }])).toEqual(test);
});
});

it("should convert from null to empty array", () => {
expect(toNullable(null)).toEqual([]);
});
describe("toNullable", () => {
it("should convert from undefined to empty array", () => {
expect(toNullable(undefined)).toEqual([]);
});

it("should convert object to array", () => {
const test = { test: "1" };
expect(toNullable(test)).toEqual([test]);
});
it("should convert from null to empty array", () => {
expect(toNullable(null)).toEqual([]);
});

it("should convert boolean to array", () => {
const test = false;
expect(toNullable(test)).toEqual([test]);
});
it("should convert object to array", () => {
const test = { test: "1" };
expect(toNullable(test)).toEqual([test]);
});

it("should convert null to empty array", () => {
const test = null;
expect(toNullable(test)).toEqual([]);
});
it("should convert boolean to array", () => {
const test = false;
expect(toNullable(test)).toEqual([test]);
});

it("should convert 0 to array", () => {
const test = 0;
expect(toNullable(test)).toEqual([test]);
it("should convert null to empty array", () => {
const test = null;
expect(toNullable(test)).toEqual([]);
});

it("should convert 0 to array", () => {
const test = 0;
expect(toNullable(test)).toEqual([test]);
});
});

describe("fromDefinedNullable", () => {
it("should convert from array to object", () => {
const test = { test: "1" };
expect(fromDefinedNullable([{ test: "1" }])).toEqual(test);
});

it("should throw on undefined", () => {
expect(() => fromDefinedNullable([])).toThrow(NullishError);
});
});

describe("fromNullishNullable", () => {
it("should return undefined for an empty array", () => {
expect(fromNullishNullable([])).toBeUndefined();
});

it("should return undefined for undefined input", () => {
expect(fromNullishNullable(undefined)).toBeUndefined();
});

it("should return the value for a non-empty array", () => {
expect(fromNullishNullable([{ test: "1" }])).toEqual(test);
});

it("should return undefined when the array contains undefined", () => {
expect(fromNullishNullable([undefined])).toBeUndefined();
});
});
});
11 changes: 11 additions & 0 deletions packages/utils/src/utils/did.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,14 @@ export const fromDefinedNullable = <T>(value: [] | [T]): T => {

return result;
};

/**
* Extracts the value from a nullish Candid-style variant representation.
*
* @template T The type of the value.
* @param {([] | [T]) | undefined} value - A Candid-style variant or `undefined`.
* @returns {T | undefined} The extracted value, or `undefined` if the input is nullish or the array is empty.
*/
export const fromNullishNullable = <T>(
value: ([] | [T]) | undefined,
): T | undefined => fromNullable(value ?? []);

0 comments on commit 90d7241

Please sign in to comment.