diff --git a/src/caip-types.ts b/src/caip-types.ts index 5ce5742f0..13a0d9fa8 100644 --- a/src/caip-types.ts +++ b/src/caip-types.ts @@ -1,5 +1,7 @@ -import type { Infer, Struct } from '@metamask/superstruct'; -import { is, pattern, string } from '@metamask/superstruct'; +import type { Infer } from '@metamask/superstruct'; +import { is } from '@metamask/superstruct'; + +import { definePattern } from './superstruct'; export const CAIP_CHAIN_ID_REGEX = /^(?[-a-z0-9]{3,8}):(?[-_a-zA-Z0-9]{1,32})$/u; @@ -28,38 +30,45 @@ export const CAIP_ASSET_ID_REGEX = /** * A CAIP-2 chain ID, i.e., a human-readable namespace and reference. */ -export const CaipChainIdStruct = pattern( - string(), +export const CaipChainIdStruct = definePattern<`${string}:${string}`>( + 'CaipChainId', CAIP_CHAIN_ID_REGEX, -) as Struct; -export type CaipChainId = `${string}:${string}`; +); +export type CaipChainId = Infer; /** * A CAIP-2 namespace, i.e., the first part of a CAIP chain ID. */ -export const CaipNamespaceStruct = pattern(string(), CAIP_NAMESPACE_REGEX); +export const CaipNamespaceStruct = definePattern( + 'CaipNamespace', + CAIP_NAMESPACE_REGEX, +); export type CaipNamespace = Infer; /** * A CAIP-2 reference, i.e., the second part of a CAIP chain ID. */ -export const CaipReferenceStruct = pattern(string(), CAIP_REFERENCE_REGEX); +export const CaipReferenceStruct = definePattern( + 'CaipReference', + CAIP_REFERENCE_REGEX, +); export type CaipReference = Infer; /** * A CAIP-10 account ID, i.e., a human-readable namespace, reference, and account address. */ -export const CaipAccountIdStruct = pattern( - string(), - CAIP_ACCOUNT_ID_REGEX, -) as Struct; -export type CaipAccountId = `${string}:${string}:${string}`; +export const CaipAccountIdStruct = + definePattern<`${string}:${string}:${string}`>( + 'CaipAccountId', + CAIP_ACCOUNT_ID_REGEX, + ); +export type CaipAccountId = Infer; /** * A CAIP-10 account address, i.e., the third part of the CAIP account ID. */ -export const CaipAccountAddressStruct = pattern( - string(), +export const CaipAccountAddressStruct = definePattern( + 'CaipAccountAddress', CAIP_ACCOUNT_ADDRESS_REGEX, ); export type CaipAccountAddress = Infer; @@ -67,8 +76,8 @@ export type CaipAccountAddress = Infer; /** * A CAIP-19 asset namespace, i.e., a namespace domain of an asset. */ -export const CaipAssetNamespaceStruct = pattern( - string(), +export const CaipAssetNamespaceStruct = definePattern( + 'CaipAssetNamespace', CAIP_ASSET_NAMESPACE_REGEX, ); export type CaipAssetNamespace = Infer; @@ -76,8 +85,8 @@ export type CaipAssetNamespace = Infer; /** * A CAIP-19 asset reference, i.e., an identifier for an asset within a given namespace. */ -export const CaipAssetReferenceStruct = pattern( - string(), +export const CaipAssetReferenceStruct = definePattern( + 'CaipAssetReference', CAIP_ASSET_REFERENCE_REGEX, ); export type CaipAssetReference = Infer; @@ -85,26 +94,31 @@ export type CaipAssetReference = Infer; /** * A CAIP-19 asset token ID, i.e., a unique identifier for an addressable asset of a given type */ -export const CaipTokenIdStruct = pattern(string(), CAIP_TOKEN_ID_REGEX); +export const CaipTokenIdStruct = definePattern( + 'CaipTokenId', + CAIP_TOKEN_ID_REGEX, +); export type CaipTokenId = Infer; /** * A CAIP-19 asset type identifier, i.e., a human-readable type of asset identifier. */ -export const CaipAssetTypeStruct = pattern( - string(), - CAIP_ASSET_TYPE_REGEX, -) as Struct; -export type CaipAssetType = `${string}:${string}/${string}:${string}`; +export const CaipAssetTypeStruct = + definePattern<`${string}:${string}/${string}:${string}`>( + 'CaipAssetType', + CAIP_ASSET_TYPE_REGEX, + ); +export type CaipAssetType = Infer; /** * A CAIP-19 asset ID identifier, i.e., a human-readable type of asset ID. */ -export const CaipAssetIdStruct = pattern( - string(), - CAIP_ASSET_ID_REGEX, -) as Struct; -export type CaipAssetId = `${string}:${string}/${string}:${string}/${string}`; +export const CaipAssetIdStruct = + definePattern<`${string}:${string}/${string}:${string}/${string}`>( + 'CaipAssetId', + CAIP_ASSET_ID_REGEX, + ); +export type CaipAssetId = Infer; /** Known CAIP namespaces. */ export enum KnownCaipNamespace { diff --git a/src/index.test.ts b/src/index.test.ts index 3ccf23082..d5cff297e 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -87,6 +87,7 @@ describe('index', () => { "createModuleLogger", "createNumber", "createProjectLogger", + "definePattern", "exactOptional", "getChecksumAddress", "getErrorMessage", diff --git a/src/index.ts b/src/index.ts index d3f0813ce..81c653a0d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,7 @@ export * from './misc'; export * from './number'; export * from './opaque'; export * from './promise'; +export * from './superstruct'; export * from './time'; export * from './transaction-types'; export * from './versions'; diff --git a/src/node.test.ts b/src/node.test.ts index d28d2f3bd..30383f464 100644 --- a/src/node.test.ts +++ b/src/node.test.ts @@ -88,6 +88,7 @@ describe('node', () => { "createNumber", "createProjectLogger", "createSandbox", + "definePattern", "directoryExists", "ensureDirectoryStructureExists", "exactOptional", diff --git a/src/superstruct.test.ts b/src/superstruct.test.ts new file mode 100644 index 000000000..79fe5020d --- /dev/null +++ b/src/superstruct.test.ts @@ -0,0 +1,23 @@ +import { assert, is, pattern, string } from '@metamask/superstruct'; + +import { definePattern } from './superstruct'; + +describe('definePattern', () => { + const hexPattern = /^0x[0-9a-f]+$/u; + const HexStringPattern = pattern(string(), hexPattern); + const HexString = definePattern('HexString', hexPattern); + + it('is similar to superstruct.pattern', () => { + expect(is('0xdeadbeef', HexStringPattern)).toBe(true); + expect(is('0xdeadbeef', HexString)).toBe(true); + expect(is('foobar', HexStringPattern)).toBe(false); + expect(is('foobar', HexString)).toBe(false); + }); + + it('throws and error if assert fails', () => { + const value = 'foobar'; + expect(() => assert(value, HexString)).toThrow( + `Expected a value of type \`HexString\`, but received: \`"foobar"\``, + ); + }); +}); diff --git a/src/superstruct.ts b/src/superstruct.ts new file mode 100644 index 000000000..bc4e4aa4e --- /dev/null +++ b/src/superstruct.ts @@ -0,0 +1,28 @@ +import type { Struct } from '@metamask/superstruct'; +import { define } from '@metamask/superstruct'; + +/** + * Defines a new string-struct matching a regular expression. + * + * @example + * const EthAddressStruct = definePattern('EthAddress', /^0x[0-9a-f]{40}$/iu); + * type EthAddress = Infer; // string + * + * const CaipChainIdStruct = defineTypedPattern<`${string}:${string}`>( + * 'CaipChainId', + * /^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32}$/u; + * ); + * type CaipChainId = Infer; // `${string}:${string}` + * @param name - Type name. + * @param pattern - Regular expression to match. + * @template Pattern - The pattern type, defaults to `string`. + * @returns A new string-struct that matches the given pattern. + */ +export function definePattern( + name: string, + pattern: RegExp, +): Struct { + return define(name, (value: unknown): boolean | string => { + return typeof value === 'string' && pattern.test(value); + }); +}