Skip to content

Commit e90f300

Browse files
authored
chore: Port the ISO 8601 time structs to snaps-utils (#3287)
This PR moves 2 refiners related to ISO 8601 time strings to `snaps-utils`, this allows the refiner to be used in other places (required for #3282).
1 parent 9044621 commit e90f300

File tree

8 files changed

+114
-35
lines changed

8 files changed

+114
-35
lines changed

packages/snaps-rpc-methods/jest.config.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ module.exports = deepmerge(baseConfig, {
1010
],
1111
coverageThreshold: {
1212
global: {
13-
branches: 94.98,
14-
functions: 98.64,
15-
lines: 98.76,
16-
statements: 98.45,
13+
branches: 94.93,
14+
functions: 98.63,
15+
lines: 98.75,
16+
statements: 98.44,
1717
},
1818
},
1919
});

packages/snaps-rpc-methods/src/permitted/scheduleBackgroundEvent.ts

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,12 @@ import {
88
type ScheduleBackgroundEventResult,
99
} from '@metamask/snaps-sdk';
1010
import type { CronjobRpcRequest, InferMatching } from '@metamask/snaps-utils';
11-
import { CronjobRpcRequestStruct } from '@metamask/snaps-utils';
1211
import {
13-
StructError,
14-
create,
15-
object,
16-
refine,
17-
string,
18-
} from '@metamask/superstruct';
12+
CronjobRpcRequestStruct,
13+
ISO8601DateStruct,
14+
ISO8601DurationStruct,
15+
} from '@metamask/snaps-utils';
16+
import { StructError, create, object } from '@metamask/superstruct';
1917
import {
2018
assert,
2119
hasProperty,
@@ -56,31 +54,13 @@ export const scheduleBackgroundEventHandler: PermittedHandlerExport<
5654
hookNames,
5755
};
5856

59-
const offsetRegex = /Z|([+-]\d{2}:?\d{2})$/u;
60-
6157
const ScheduleBackgroundEventParametersWithDateStruct = object({
62-
date: refine(string(), 'date', (val) => {
63-
const date = DateTime.fromISO(val);
64-
if (date.isValid) {
65-
// Luxon doesn't have a reliable way to check if timezone info was not provided
66-
if (!offsetRegex.test(val)) {
67-
return 'ISO 8601 date must have timezone information';
68-
}
69-
return true;
70-
}
71-
return 'Not a valid ISO 8601 date';
72-
}),
58+
date: ISO8601DateStruct,
7359
request: CronjobRpcRequestStruct,
7460
});
7561

7662
const ScheduleBackgroundEventParametersWithDurationStruct = object({
77-
duration: refine(string(), 'duration', (val) => {
78-
const duration = Duration.fromISO(val);
79-
if (!duration.isValid) {
80-
return 'Not a valid ISO 8601 duration';
81-
}
82-
return true;
83-
}),
63+
duration: ISO8601DurationStruct,
8464
request: CronjobRpcRequestStruct,
8565
});
8666

packages/snaps-utils/coverage.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"branches": 99.74,
3-
"functions": 98.93,
4-
"lines": 99.61,
5-
"statements": 96.95
2+
"branches": 99.75,
3+
"functions": 98.94,
4+
"lines": 99.62,
5+
"statements": 96.99
66
}

packages/snaps-utils/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"fast-deep-equal": "^3.1.3",
9696
"fast-json-stable-stringify": "^2.1.0",
9797
"fast-xml-parser": "^4.4.1",
98+
"luxon": "^3.5.0",
9899
"marked": "^12.0.1",
99100
"rfdc": "^1.3.0",
100101
"semver": "^7.5.4",
@@ -111,6 +112,7 @@
111112
"@swc/jest": "^0.2.26",
112113
"@ts-bridge/cli": "^0.6.1",
113114
"@types/jest": "^27.5.1",
115+
"@types/luxon": "^3",
114116
"@types/mocha": "^10.0.1",
115117
"@types/node": "18.14.2",
116118
"@types/semver": "^7.5.0",

packages/snaps-utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export * from './platform-version';
2626
export * from './snaps';
2727
export * from './strings';
2828
export * from './structs';
29+
export * from './time';
2930
export * from './types';
3031
export * from './ui';
3132
export * from './url';
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { create, is } from '@metamask/superstruct';
2+
import { DateTime } from 'luxon';
3+
4+
import { ISO8601DateStruct, ISO8601DurationStruct } from './time';
5+
6+
describe('ISO8601DateStruct', () => {
7+
it('should return true for a valid ISO 8601 date', () => {
8+
const value = DateTime.now().toISO();
9+
expect(is(value, ISO8601DateStruct)).toBe(true);
10+
});
11+
12+
it('should return false for an invalid ISO 8601 date', () => {
13+
const value = 'Mon Mar 31 2025';
14+
expect(is(value, ISO8601DateStruct)).toBe(false);
15+
});
16+
17+
it('should return false for an ISO 8601 date without timezone information', () => {
18+
const value = '2025-03-31T12:00:00';
19+
expect(is(value, ISO8601DateStruct)).toBe(false);
20+
});
21+
22+
it('should return an error message for invalid ISO 8601 date', () => {
23+
const value = 'Mon Mar 31 2025';
24+
expect(() => create(value, ISO8601DateStruct)).toThrow(
25+
'Not a valid ISO 8601 date',
26+
);
27+
});
28+
29+
it('should return an error message for ISO 8601 date without timezone information', () => {
30+
const value = '2025-03-31T12:00:00';
31+
expect(() => create(value, ISO8601DateStruct)).toThrow(
32+
'ISO 8601 date must have timezone information',
33+
);
34+
});
35+
});
36+
37+
describe('ISO8601DurationStruct', () => {
38+
it('should return true for a valid ISO 8601 duration', () => {
39+
const value = 'P3Y6M4DT12H30M5S';
40+
expect(is(value, ISO8601DurationStruct)).toBe(true);
41+
});
42+
43+
it('should return false for an invalid ISO 8601 duration', () => {
44+
const value = 'Millisecond';
45+
expect(is(value, ISO8601DurationStruct)).toBe(false);
46+
});
47+
48+
it('should return an error message for invalid ISO 8601 duration', () => {
49+
const value = '1Millisecond';
50+
expect(() => create(value, ISO8601DurationStruct)).toThrow(
51+
'Not a valid ISO 8601 duration',
52+
);
53+
});
54+
});

packages/snaps-utils/src/time.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { refine, string } from '@metamask/superstruct';
2+
import { DateTime, Duration } from 'luxon';
3+
4+
/**
5+
* Refines a string as an ISO 8601 duration.
6+
*/
7+
export const ISO8601DurationStruct = refine(
8+
string(),
9+
'ISO 8601 duration',
10+
(value) => {
11+
const parsedDuration = Duration.fromISO(value);
12+
if (!parsedDuration.isValid) {
13+
return 'Not a valid ISO 8601 duration';
14+
}
15+
return true;
16+
},
17+
);
18+
19+
/**
20+
* Regex to match the offset part of an ISO 8601 date.
21+
*/
22+
const offsetRegex = /Z|([+-]\d{2}:?\d{2})$/u;
23+
24+
/**
25+
* Refines a string as an ISO 8601 date.
26+
*/
27+
export const ISO8601DateStruct = refine(string(), 'ISO 8601 date', (value) => {
28+
const parsedDate = DateTime.fromISO(value);
29+
30+
if (!parsedDate.isValid) {
31+
return 'Not a valid ISO 8601 date';
32+
}
33+
34+
if (!offsetRegex.test(value)) {
35+
// Luxon doesn't have a reliable way to check if timezone info was not provided
36+
return 'ISO 8601 date must have timezone information';
37+
}
38+
39+
return true;
40+
});

yarn.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6049,6 +6049,7 @@ __metadata:
60496049
"@swc/jest": "npm:^0.2.26"
60506050
"@ts-bridge/cli": "npm:^0.6.1"
60516051
"@types/jest": "npm:^27.5.1"
6052+
"@types/luxon": "npm:^3"
60526053
"@types/mocha": "npm:^10.0.1"
60536054
"@types/node": "npm:18.14.2"
60546055
"@types/semver": "npm:^7.5.0"
@@ -6075,6 +6076,7 @@ __metadata:
60756076
istanbul-reports: "npm:^3.1.5"
60766077
jest: "npm:^29.0.2"
60776078
jest-silent-reporter: "npm:^0.6.0"
6079+
luxon: "npm:^3.5.0"
60786080
marked: "npm:^12.0.1"
60796081
memfs: "npm:^3.4.13"
60806082
prettier: "npm:^3.3.3"

0 commit comments

Comments
 (0)