From e98e61d9bf693eb2f8f8ba17a7e3556228c7d8d5 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 1 Apr 2025 12:54:07 +0200 Subject: [PATCH 1/4] add `nonEmptyRecord` refiner --- .../browserify-plugin/snap.manifest.json | 2 +- .../packages/browserify/snap.manifest.json | 2 +- packages/snaps-sdk/src/index.ts | 1 + packages/snaps-sdk/src/structs.test.ts | 20 ++++++++++++++++++ packages/snaps-sdk/src/structs.ts | 21 +++++++++++++++++++ 5 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 packages/snaps-sdk/src/structs.test.ts create mode 100644 packages/snaps-sdk/src/structs.ts diff --git a/packages/examples/packages/browserify-plugin/snap.manifest.json b/packages/examples/packages/browserify-plugin/snap.manifest.json index f1e34c3f64..9bf785c005 100644 --- a/packages/examples/packages/browserify-plugin/snap.manifest.json +++ b/packages/examples/packages/browserify-plugin/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "2WLcpc3RXg2Up0zeSMxWS/2rsVI1bC1iHCHAhzc3DOA=", + "shasum": "03Y0FecvVHqC2DG+GpXwwNPtWFl2b5tA3PLjMK7d3hw=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/browserify/snap.manifest.json b/packages/examples/packages/browserify/snap.manifest.json index 14eb4eb126..3bf523cf77 100644 --- a/packages/examples/packages/browserify/snap.manifest.json +++ b/packages/examples/packages/browserify/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "LQYiFCEu2fQXCuzfYHVpFfqffCbzzUjaqH7kPwgBfNc=", + "shasum": "I/C34T7c3uxkDeFC91tMUg3raVe104ocTkr5F4ciGyQ=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/snaps-sdk/src/index.ts b/packages/snaps-sdk/src/index.ts index c90eb4c6fa..78d360f0f2 100644 --- a/packages/snaps-sdk/src/index.ts +++ b/packages/snaps-sdk/src/index.ts @@ -26,4 +26,5 @@ export * from './errors'; export * from './error-wrappers'; export * from './images'; export * from './types'; +export * from './structs'; export * from './ui'; diff --git a/packages/snaps-sdk/src/structs.test.ts b/packages/snaps-sdk/src/structs.test.ts new file mode 100644 index 0000000000..9ce93e0a0e --- /dev/null +++ b/packages/snaps-sdk/src/structs.test.ts @@ -0,0 +1,20 @@ +import { any, is, string } from '@metamask/superstruct'; + +import { nonEmptyRecord } from './structs'; + +describe('nonEmptyRecord', () => { + it.each([[1, 2, 3], { foo: 'bar' }])('validates "%p"', (value) => { + const struct = nonEmptyRecord(string(), any()); + + expect(is(value, struct)).toBe(true); + }); + + it.each(['foo', 42, null, undefined, [], {}])( + 'does not validate "%p"', + (value) => { + const struct = nonEmptyRecord(string(), any()); + + expect(is(value, struct)).toBe(false); + }, + ); +}); diff --git a/packages/snaps-sdk/src/structs.ts b/packages/snaps-sdk/src/structs.ts new file mode 100644 index 0000000000..2e8bac45e5 --- /dev/null +++ b/packages/snaps-sdk/src/structs.ts @@ -0,0 +1,21 @@ +import type { Struct } from '@metamask/superstruct'; +import { record, refine } from '@metamask/superstruct'; + +/** + * Refine a struct to be a non-empty record. + * + * @param Key - The struct for the record key. + * @param Value - The struct for the record value. + * @returns The refined struct. + */ +export function nonEmptyRecord( + Key: Struct, + Value: Struct, +) { + return refine(record(Key, Value), 'Non-empty record', (value) => { + return ( + (Array.isArray(value) && value.length > 0) || + Object.keys(value).length > 0 + ); + }); +} From 2c9017db082775728a811f987426d65ee3ce9e93 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 1 Apr 2025 13:09:25 +0200 Subject: [PATCH 2/4] move to `/internals` and disallow arrays --- packages/snaps-sdk/src/index.ts | 2 +- .../snaps-sdk/src/internals/structs.test.ts | 33 ++++++++++++++++++- packages/snaps-sdk/src/internals/structs.ts | 18 ++++++++++ packages/snaps-sdk/src/structs.test.ts | 20 ----------- packages/snaps-sdk/src/structs.ts | 21 ------------ 5 files changed, 51 insertions(+), 43 deletions(-) delete mode 100644 packages/snaps-sdk/src/structs.test.ts delete mode 100644 packages/snaps-sdk/src/structs.ts diff --git a/packages/snaps-sdk/src/index.ts b/packages/snaps-sdk/src/index.ts index 78d360f0f2..761189b66c 100644 --- a/packages/snaps-sdk/src/index.ts +++ b/packages/snaps-sdk/src/index.ts @@ -11,6 +11,7 @@ export { enumValue, typedUnion, selectiveUnion, + nonEmptyRecord, } from './internals'; // Re-exported from `@metamask/utils` for convenience. @@ -26,5 +27,4 @@ export * from './errors'; export * from './error-wrappers'; export * from './images'; export * from './types'; -export * from './structs'; export * from './ui'; diff --git a/packages/snaps-sdk/src/internals/structs.test.ts b/packages/snaps-sdk/src/internals/structs.test.ts index 64e310cf6c..e5942b7744 100644 --- a/packages/snaps-sdk/src/internals/structs.test.ts +++ b/packages/snaps-sdk/src/internals/structs.test.ts @@ -5,9 +5,16 @@ import { object, string, validate, + any, } from '@metamask/superstruct'; -import { enumValue, literal, typedUnion, union } from './structs'; +import { + enumValue, + literal, + typedUnion, + union, + nonEmptyRecord, +} from './structs'; import type { BoxElement } from '../jsx'; import { Footer, Icon, Text, Button, Box } from '../jsx'; import { @@ -145,3 +152,27 @@ describe('typedUnion', () => { ]); }); }); + +describe('nonEmptyRecord', () => { + it.each([ + { foo: 'bar' }, + { + a: { + b: 'c', + }, + }, + ])('validates "%p"', (value) => { + const struct = nonEmptyRecord(string(), any()); + + expect(is(value, struct)).toBe(true); + }); + + it.each(['foo', 42, null, undefined, [], {}, [1, 2, 3]])( + 'does not validate "%p"', + (value) => { + const struct = nonEmptyRecord(string(), any()); + + expect(is(value, struct)).toBe(false); + }, + ); +}); diff --git a/packages/snaps-sdk/src/internals/structs.ts b/packages/snaps-sdk/src/internals/structs.ts index f635a8422f..7721d08fa2 100644 --- a/packages/snaps-sdk/src/internals/structs.ts +++ b/packages/snaps-sdk/src/internals/structs.ts @@ -2,6 +2,8 @@ import type { AnyStruct, Infer, InferStructTuple } from '@metamask/superstruct'; import { Struct, define, + record, + refine, literal as superstructLiteral, union as superstructUnion, } from '@metamask/superstruct'; @@ -217,3 +219,19 @@ export function selectiveUnion AnyStruct>( }, }); } + +/** + * Refine a struct to be a non-empty record. + * + * @param Key - The struct for the record key. + * @param Value - The struct for the record value. + * @returns The refined struct. + */ +export function nonEmptyRecord( + Key: Struct, + Value: Struct, +) { + return refine(record(Key, Value), 'Non-empty record', (value) => { + return !Array.isArray(value) && Object.keys(value).length > 0; + }); +} diff --git a/packages/snaps-sdk/src/structs.test.ts b/packages/snaps-sdk/src/structs.test.ts deleted file mode 100644 index 9ce93e0a0e..0000000000 --- a/packages/snaps-sdk/src/structs.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { any, is, string } from '@metamask/superstruct'; - -import { nonEmptyRecord } from './structs'; - -describe('nonEmptyRecord', () => { - it.each([[1, 2, 3], { foo: 'bar' }])('validates "%p"', (value) => { - const struct = nonEmptyRecord(string(), any()); - - expect(is(value, struct)).toBe(true); - }); - - it.each(['foo', 42, null, undefined, [], {}])( - 'does not validate "%p"', - (value) => { - const struct = nonEmptyRecord(string(), any()); - - expect(is(value, struct)).toBe(false); - }, - ); -}); diff --git a/packages/snaps-sdk/src/structs.ts b/packages/snaps-sdk/src/structs.ts deleted file mode 100644 index 2e8bac45e5..0000000000 --- a/packages/snaps-sdk/src/structs.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { Struct } from '@metamask/superstruct'; -import { record, refine } from '@metamask/superstruct'; - -/** - * Refine a struct to be a non-empty record. - * - * @param Key - The struct for the record key. - * @param Value - The struct for the record value. - * @returns The refined struct. - */ -export function nonEmptyRecord( - Key: Struct, - Value: Struct, -) { - return refine(record(Key, Value), 'Non-empty record', (value) => { - return ( - (Array.isArray(value) && value.length > 0) || - Object.keys(value).length > 0 - ); - }); -} From 8e1dd1db67e71c302dd1ad510cf0f9701b630e67 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 1 Apr 2025 13:11:59 +0200 Subject: [PATCH 3/4] update manifests --- packages/examples/packages/browserify-plugin/snap.manifest.json | 2 +- packages/examples/packages/browserify/snap.manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/examples/packages/browserify-plugin/snap.manifest.json b/packages/examples/packages/browserify-plugin/snap.manifest.json index 9bf785c005..5163760f24 100644 --- a/packages/examples/packages/browserify-plugin/snap.manifest.json +++ b/packages/examples/packages/browserify-plugin/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "03Y0FecvVHqC2DG+GpXwwNPtWFl2b5tA3PLjMK7d3hw=", + "shasum": "2XIZ9NDtR8XhO4hsAxS89GtjVkpwj/iTuHUv2P0TKYk=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/browserify/snap.manifest.json b/packages/examples/packages/browserify/snap.manifest.json index 3bf523cf77..9460a92c3e 100644 --- a/packages/examples/packages/browserify/snap.manifest.json +++ b/packages/examples/packages/browserify/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "I/C34T7c3uxkDeFC91tMUg3raVe104ocTkr5F4ciGyQ=", + "shasum": "4U6ZsKhhX3XQp20W7rLNWpaExEasUgcSU/+Q/UQQNns=", "location": { "npm": { "filePath": "dist/bundle.js", From f56848d70b0d7450850c22e18b622ac17d2f8d8e Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 1 Apr 2025 13:16:52 +0200 Subject: [PATCH 4/4] add a note that it disallows arrays --- packages/snaps-sdk/src/internals/structs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/snaps-sdk/src/internals/structs.ts b/packages/snaps-sdk/src/internals/structs.ts index 7721d08fa2..288e4cb6c4 100644 --- a/packages/snaps-sdk/src/internals/structs.ts +++ b/packages/snaps-sdk/src/internals/structs.ts @@ -221,7 +221,7 @@ export function selectiveUnion AnyStruct>( } /** - * Refine a struct to be a non-empty record. + * Refine a struct to be a non-empty record and disallows usage of arrays. * * @param Key - The struct for the record key. * @param Value - The struct for the record value.