Skip to content

Commit 70a83bd

Browse files
GuillaumeRxMrtenz
andauthored
chore: Remove pattern in favour of definePattern (#3142)
`@metamask/utils` introduced a new `definePattern` superstruct helper to have better error messages for `pattern`. This updates the repo to use it. Fixes: #3066 --------- Co-authored-by: Maarten Zuidhoorn <[email protected]>
1 parent f5e359f commit 70a83bd

File tree

5 files changed

+86
-18
lines changed

5 files changed

+86
-18
lines changed

packages/snaps-controllers/src/snaps/SnapController.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6597,7 +6597,7 @@ describe('SnapController', () => {
65976597
[snapId]: {},
65986598
}),
65996599
).rejects.toThrow(
6600-
`Invalid snap ID: Expected the value to satisfy a union of \`intersection | string\`, but received: "foo".`,
6600+
`Invalid snap ID: Invalid or no prefix found. Expected Snap ID to start with one of: "npm:", "local:", but received: "foo".`,
66016601
);
66026602

66036603
controller.destroy();

packages/snaps-utils/coverage.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"branches": 99.74,
3-
"functions": 98.92,
3+
"functions": 98.93,
44
"lines": 99.61,
5-
"statements": 96.91
5+
"statements": 96.94
66
}

packages/snaps-utils/src/snaps.test.ts

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
verifyRequestedSnapPermissions,
1515
stripSnapPrefix,
1616
isSnapId,
17+
SnapIdPrefixStruct,
1718
} from './snaps';
1819
import { MOCK_SNAP_ID } from './test-utils';
1920
import { uri, WALLET_SNAP_PERMISSION_KEY } from './types';
@@ -52,14 +53,14 @@ describe('assertIsValidSnapId', () => {
5253
// TODO: Either fix this lint violation or explain why it's necessary to
5354
// ignore.
5455
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-base-to-string
55-
`Invalid snap ID: Expected the value to satisfy a union of \`intersection | string\`, but received: ${value}.`,
56+
`Invalid snap ID: Expected a string, but received: ${value}.`,
5657
);
5758
},
5859
);
5960

6061
it('throws for invalid snap id', () => {
6162
expect(() => assertIsValidSnapId('foo:bar')).toThrow(
62-
`Invalid snap ID: Expected the value to satisfy a union of \`intersection | string\`, but received: "foo:bar".`,
63+
`Invalid snap ID: Invalid or no prefix found. Expected Snap ID to start with one of: "npm:", "local:", but received: "foo:bar".`,
6364
);
6465
});
6566

@@ -75,22 +76,27 @@ describe('assertIsValidSnapId', () => {
7576
).not.toThrow();
7677
});
7778

79+
it('disallows whitespace at the beginning', () => {
80+
expect(() => assertIsValidSnapId(' local:http://localhost:8000')).toThrow(
81+
'Invalid snap ID: Invalid or no prefix found. Expected Snap ID to start with one of: "npm:", "local:", but received: " local:http://localhost:8000".',
82+
);
83+
});
84+
7885
it.each([
79-
' local:http://localhost:8000',
8086
'local:http://localhost:8000 ',
8187
'local:http://localhost:8000\n',
8288
'local:http://localhost:8000\r',
8389
])('disallows whitespace #%#', (value) => {
8490
expect(() => assertIsValidSnapId(value)).toThrow(
85-
/Invalid snap ID: Expected the value to satisfy a union of `intersection \| string`, but received: .+\./u,
91+
/Invalid snap ID: Expected a value of type `Base Snap Id`, but received: .+\./u,
8692
);
8793
});
8894

8995
it.each(['local:😎', 'local:␡'])(
9096
'disallows non-ASCII symbols #%#',
9197
(value) => {
9298
expect(() => assertIsValidSnapId(value)).toThrow(
93-
`Invalid snap ID: Expected the value to satisfy a union of \`intersection | string\`, but received: "${value}".`,
99+
`Invalid snap ID: Expected a value of type \`Base Snap Id\`, but received: \`"${value}"\`.`,
94100
);
95101
},
96102
);
@@ -238,6 +244,36 @@ describe('HttpSnapIdStruct', () => {
238244
});
239245
});
240246

247+
describe('SnapIdPrefixStruct', () => {
248+
it.each(['local:', 'npm:', 'local:foobar', 'npm:foobar'])(
249+
'validates "%s" as proper Snap ID prefix',
250+
(value) => {
251+
expect(is(value, SnapIdPrefixStruct)).toBe(true);
252+
},
253+
);
254+
255+
it.each([
256+
0,
257+
1,
258+
false,
259+
true,
260+
{},
261+
[],
262+
uri,
263+
URL,
264+
new URL('http://github.com'),
265+
'',
266+
'local',
267+
'npm',
268+
'foo:npm',
269+
'foo:local',
270+
'localfoobar',
271+
'npmfoobar',
272+
])('invalidates an improper Snap ID prefix', (value) => {
273+
expect(is(value, SnapIdPrefixStruct)).toBe(false);
274+
});
275+
});
276+
241277
describe('isSnapPermitted', () => {
242278
it("will check an origin's permissions object to see if it has permission to interact with a specific snap", () => {
243279
const validPermissions: SubjectPermissions<PermissionConstraint> = {

packages/snaps-utils/src/snaps.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,24 @@ import type {
44
PermissionConstraint,
55
} from '@metamask/permission-controller';
66
import type { BlockReason } from '@metamask/snaps-registry';
7-
import type { SnapId, Snap as TruncatedSnap } from '@metamask/snaps-sdk';
7+
import {
8+
selectiveUnion,
9+
type SnapId,
10+
type Snap as TruncatedSnap,
11+
} from '@metamask/snaps-sdk';
812
import type { Struct } from '@metamask/superstruct';
913
import {
1014
is,
1115
empty,
1216
enums,
1317
intersection,
1418
literal,
15-
pattern,
1619
refine,
1720
string,
18-
union,
1921
validate,
2022
} from '@metamask/superstruct';
2123
import type { Json } from '@metamask/utils';
22-
import { assert, isObject, assertStruct } from '@metamask/utils';
24+
import { assert, isObject, assertStruct, definePattern } from '@metamask/utils';
2325
import { base64 } from '@scure/base';
2426
import stableStringify from 'fast-json-stable-stringify';
2527
import validateNPMPackage from 'validate-npm-package-name';
@@ -228,7 +230,10 @@ export async function validateSnapShasum(
228230
export const LOCALHOST_HOSTNAMES = ['localhost', '127.0.0.1', '[::1]'] as const;
229231

230232
// Require snap ids to only consist of printable ASCII characters
231-
export const BaseSnapIdStruct = pattern(string(), /^[\x21-\x7E]*$/u);
233+
export const BaseSnapIdStruct = definePattern(
234+
'Base Snap Id',
235+
/^[\x21-\x7E]*$/u,
236+
);
232237

233238
const LocalSnapIdSubUrlStruct = uri({
234239
protocol: enums(['http:', 'https:']),
@@ -284,7 +289,35 @@ export const HttpSnapIdStruct = intersection([
284289
}),
285290
]) as unknown as Struct<string, null>;
286291

287-
export const SnapIdStruct = union([NpmSnapIdStruct, LocalSnapIdStruct]);
292+
export const SnapIdPrefixStruct = refine(
293+
string(),
294+
'Snap ID prefix',
295+
(value) => {
296+
if (
297+
Object.values(SnapIdPrefixes).some((prefix) => value.startsWith(prefix))
298+
) {
299+
return true;
300+
}
301+
302+
const allowedPrefixes = Object.values(SnapIdPrefixes)
303+
.map((prefix) => `"${prefix}"`)
304+
.join(', ');
305+
306+
return `Invalid or no prefix found. Expected Snap ID to start with one of: ${allowedPrefixes}, but received: "${value}"`;
307+
},
308+
);
309+
310+
export const SnapIdStruct = selectiveUnion((value) => {
311+
if (typeof value === 'string' && value.startsWith(SnapIdPrefixes.npm)) {
312+
return NpmSnapIdStruct;
313+
}
314+
315+
if (typeof value === 'string' && value.startsWith(SnapIdPrefixes.local)) {
316+
return LocalSnapIdStruct;
317+
}
318+
319+
return SnapIdPrefixStruct;
320+
});
288321

289322
/**
290323
* Extracts the snap prefix from a snap ID.

packages/snaps-utils/src/types.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
instance,
33
is,
44
optional,
5-
pattern,
65
refine,
76
size,
87
string,
@@ -12,7 +11,7 @@ import {
1211
} from '@metamask/superstruct';
1312
import type { Infer, Struct } from '@metamask/superstruct';
1413
import type { Json } from '@metamask/utils';
15-
import { VersionStruct } from '@metamask/utils';
14+
import { definePattern, VersionStruct } from '@metamask/utils';
1615

1716
import type { SnapCaveatType } from './caveats';
1817
import type { SnapFunctionExports, SnapRpcHookArgs } from './handlers';
@@ -26,8 +25,8 @@ export enum NpmSnapFileNames {
2625
}
2726

2827
export const NameStruct = size(
29-
pattern(
30-
string(),
28+
definePattern(
29+
'Snap Name',
3130
/^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/u,
3231
),
3332
1,

0 commit comments

Comments
 (0)