From 638dbd5e0d07a0e9baaea1b967ee0eca01e95da9 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Aug 2024 16:24:04 +0200 Subject: [PATCH 1/2] build: add peer dependency candid to library (#139) --- package-lock.json | 1 + package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/package-lock.json b/package-lock.json index e73d3a35..9ecaf55a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "node": ">=20" }, "peerDependencies": { + "@dfinity/candid": "^2.0.0", "@dfinity/principal": "^2.0.0", "@dfinity/utils": "^2.4.0-next-2024-08-28", "zod": "^3.23.8" diff --git a/package.json b/package.json index f17ee3ec..06c37235 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "node": ">=20" }, "peerDependencies": { + "@dfinity/candid": "^2.0.0", "@dfinity/principal": "^2.0.0", "@dfinity/utils": "^2.4.0-next-2024-08-28", "zod": "^3.23.8" From 8f4aa4d4a19a673e927543b72f1ef99645dc3bd5 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Aug 2024 17:06:37 +0200 Subject: [PATCH 2/2] feat: wallet call canister arg and argType params (#140) * feat: wallet call canister arg and argType params * chore: lint --- src/types/wallet-request.spec.ts | 143 +++++++++++++++++++++++++++---- src/types/wallet-request.ts | 36 ++++++++ 2 files changed, 161 insertions(+), 18 deletions(-) diff --git a/src/types/wallet-request.spec.ts b/src/types/wallet-request.spec.ts index 7bd6c5f9..550cbd63 100644 --- a/src/types/wallet-request.spec.ts +++ b/src/types/wallet-request.spec.ts @@ -1,28 +1,135 @@ -import {WalletRequestOptionsSchema} from './wallet-request'; +import {IDL} from '@dfinity/candid'; +import {mockPrincipalText} from '../constants/icrc-accounts.mocks'; +import { + WalletRequestOptionsSchema, + extendIcrcCallCanisterRequestParamsSchema, + type WalletCallParams +} from './wallet-request'; describe('WalletRequest', () => { - it('should validate with a specified timeoutInMilliseconds', () => { - const validData = { - timeoutInMilliseconds: 3000 - }; + describe('Options', () => { + it('should validate with a specified timeoutInMilliseconds', () => { + const validData = { + timeoutInMilliseconds: 3000 + }; - const result = WalletRequestOptionsSchema.safeParse(validData); - expect(result.success).toBe(true); - }); + const result = WalletRequestOptionsSchema.safeParse(validData); + expect(result.success).toBe(true); + }); + + it('should validate without specifying timeoutInMilliseconds', () => { + const validData = {}; + + const result = WalletRequestOptionsSchema.safeParse(validData); + expect(result.success).toBe(true); + }); - it('should validate without specifying timeoutInMilliseconds', () => { - const validData = {}; + it('should fail validation with a non-numeric timeoutInMilliseconds', () => { + const invalidData = { + timeoutInMilliseconds: 'three thousand' + }; - const result = WalletRequestOptionsSchema.safeParse(validData); - expect(result.success).toBe(true); + const result = WalletRequestOptionsSchema.safeParse(invalidData); + expect(result.success).toBe(false); + }); }); - it('should fail validation with a non-numeric timeoutInMilliseconds', () => { - const invalidData = { - timeoutInMilliseconds: 'three thousand' - }; + describe('Call', () => { + interface MyTest { + hello: string; + } + + const argType = IDL.Record({ + hello: IDL.Text + }); + + const schema = extendIcrcCallCanisterRequestParamsSchema(); + + it('should validate correct parameters', () => { + const validParams: WalletCallParams<{hello: string}> = { + canisterId: mockPrincipalText, + sender: mockPrincipalText, + method: 'some_method', + arg: {hello: 'world'}, + argType + }; + + const result = schema.safeParse(validParams); + expect(result.success).toBe(true); + }); + + // TODO: not sure how to solve this with zod that's why this test succeed. + it('should validate incorrect "arg" type', () => { + const invalidParams = { + canisterId: mockPrincipalText, + sender: mockPrincipalText, + method: 'some_method', + arg: 'invalid_arg', + argType + }; + + const result = schema.safeParse(invalidParams); + expect(result.success).toBe(true); + }); + + it('should fail validation with missing "argType"', () => { + const invalidParams = { + canisterId: mockPrincipalText, + sender: mockPrincipalText, + method: 'some_method', + arg: {hello: 'world'} + }; + + const result = schema.safeParse(invalidParams); + expect(result.success).toBe(false); + }); + + it('should fail validation with missing "arg"', () => { + const invalidParams = { + canisterId: mockPrincipalText, + sender: mockPrincipalText, + method: 'some_method', + argType + }; + + const result = schema.safeParse(invalidParams); + expect(result.success).toBe(false); + }); + + it('should fail validation with missing "canisterId"', () => { + const invalidParams = { + sender: mockPrincipalText, + method: 'some_method', + arg: {hello: 'world'}, + argType + }; + + const result = schema.safeParse(invalidParams); + expect(result.success).toBe(false); + }); + + it('should fail validation with missing "sender"', () => { + const invalidParams = { + canisterId: mockPrincipalText, + method: 'some_method', + arg: {hello: 'world'}, + argType + }; + + const result = schema.safeParse(invalidParams); + expect(result.success).toBe(false); + }); + + it('should fail validation with missing "method"', () => { + const invalidParams = { + canisterId: mockPrincipalText, + sender: mockPrincipalText, + arg: {hello: 'world'}, + argType + }; - const result = WalletRequestOptionsSchema.safeParse(invalidData); - expect(result.success).toBe(false); + const result = schema.safeParse(invalidParams); + expect(result.success).toBe(false); + }); }); }); diff --git a/src/types/wallet-request.ts b/src/types/wallet-request.ts index b3d5acd1..bbcb79be 100644 --- a/src/types/wallet-request.ts +++ b/src/types/wallet-request.ts @@ -1,4 +1,7 @@ +import {Type} from '@dfinity/candid/lib/cjs/idl'; +import {nonNullish} from '@dfinity/utils'; import {z} from 'zod'; +import {IcrcCallCanisterRequestParamsSchema} from './icrc-requests'; import {RpcIdSchema} from './rpc'; export const WalletRequestOptionsTimeoutSchema = z.object({ @@ -29,3 +32,36 @@ export const WalletRequestOptionsWithTimeoutSchema = WalletRequestOptionsSchema. }).merge(WalletRequestOptionsTimeoutSchema); export type WalletRequestOptionsWithTimeout = z.infer; + +/** + * Creates an extended schema for ICRC call canister request parameters. + * + * This function generates a schema that extends the base `IcrcCallCanisterRequestParamsSchema` + * by replacing the `arg` field with a custom type `T` and adding an `argType` field that must be + * an instance of the `Type` class from `@dfinity/candid`. + * + * @template T - The type of the `arg` field. + * @returns {z.ZodObject} - A Zod schema object that includes the base fields and the custom `arg` and `argType`. + */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +export const extendIcrcCallCanisterRequestParamsSchema = () => { + return IcrcCallCanisterRequestParamsSchema.omit({arg: true}).extend({ + arg: z.custom().refine(nonNullish, { + message: 'arg is required' + }), + argType: z.instanceof(Type) as z.ZodType + }); +}; + +/** + * Represents the type of parameters used in a wallet call, based on the + * extended ICRC call canister request schema. + * + * This type is inferred from the return type of `extendIcrcCallCanisterRequestParamsSchema`, + * meaning it includes all fields of `IcrcCallCanisterRequestParamsSchema` with a generic `arg` type. + * + * @template T - The type of the `arg` field in the schema. + */ +export type WalletCallParams = z.infer< + ReturnType> +>;