diff --git a/packages/schema/src/tests/utils/idl.spec.ts b/packages/schema/src/tests/utils/idl.spec.ts index 28f3310f8..afdf88e61 100644 --- a/packages/schema/src/tests/utils/idl.spec.ts +++ b/packages/schema/src/tests/utils/idl.spec.ts @@ -52,10 +52,10 @@ describe('idl', () => { expect( schemaToIdl({ schema: Schema, - value: {username: 'David', status: {type: 'active', owner: 'abc'}} + value: {username: 'Hello', status: {type: 'active', owner: 'abc'}} }) ).toEqual({ - username: 'David', + username: 'Hello', status: {active: {owner: 'abc'}} }); }); @@ -132,13 +132,94 @@ describe('idl', () => { expect( schemaFromIdl({ schema: Schema, - value: {username: 'David', status: {active: {owner: 'abc'}}} + value: {username: 'Hello', status: {active: {owner: 'abc'}}} }) ).toEqual({ - username: 'David', + username: 'Hello', status: {type: 'active', owner: 'abc'} }); }); }); }); + + describe('camel case', () => { + describe('object with camelCase fields', () => { + const Schema = z.object({firstName: z.string(), lastName: z.string()}); + + it('converts camelCase keys to snake_case', () => { + expect( + schemaToIdl({schema: Schema, value: {firstName: 'Hello', lastName: 'World'}}) + ).toEqual({ + first_name: 'Hello', + last_name: 'World' + }); + }); + }); + + describe('object with camelCase fields', () => { + const Schema = z.object({firstName: z.string(), lastName: z.string()}); + + it('converts snake_case keys back to camelCase', () => { + expect( + schemaFromIdl({schema: Schema, value: {first_name: 'Hello', last_name: 'World'}}) + ).toEqual({ + firstName: 'Hello', + lastName: 'World' + }); + }); + }); + + describe('nested object with camelCase fields', () => { + const Schema = z.object({ + userId: z.string(), + userProfile: z.object({displayName: z.string()}) + }); + + it('converts nested camelCase keys to snake_case', () => { + expect( + schemaToIdl({ + schema: Schema, + value: {userId: 'abc', userProfile: {displayName: 'Hello'}} + }) + ).toEqual({ + user_id: 'abc', + user_profile: {display_name: 'Hello'} + }); + }); + + it('converts nested snake_case keys back to camelCase', () => { + expect( + schemaFromIdl({ + schema: Schema, + value: {user_id: 'abc', user_profile: {display_name: 'Hello'}} + }) + ).toEqual({ + userId: 'abc', + userProfile: {displayName: 'Hello'} + }); + }); + }); + }); + + describe('snake_case fields', () => { + const Schema = z.object({first_name: z.string(), last_name: z.string()}); + + it('keeps snake_case keys unchanged in schemaToIdl', () => { + expect( + schemaToIdl({schema: Schema, value: {first_name: 'Hello', last_name: 'World'}}) + ).toEqual({ + first_name: 'Hello', + last_name: 'World' + }); + }); + + it('keeps snake_case keys unchanged in schemaFromIdl', () => { + expect( + schemaFromIdl({schema: Schema, value: {first_name: 'Hello', last_name: 'World'}}) + ).toEqual({ + first_name: 'Hello', + last_name: 'World' + }); + }); + }); }); diff --git a/packages/schema/src/utils/idl.ts b/packages/schema/src/utils/idl.ts index bb6a997b8..b43bf4ce1 100644 --- a/packages/schema/src/utils/idl.ts +++ b/packages/schema/src/utils/idl.ts @@ -5,6 +5,10 @@ export interface IdlParams { value: unknown; } +// Duplicate utils to avoid to reference library utils in schema +const convertCamelToSnake = (str: string): string => + str.replace(/([a-zA-Z])(?=[A-Z])/g, '$1_').toLowerCase(); + /** * Recursively converts a JavaScript value to its Candid IDL representation, * guided by a Zod schema. @@ -53,7 +57,7 @@ export const schemaToIdl = ({schema, value}: IdlParams): unknown => { if (schema instanceof z.ZodObject) { return Object.fromEntries( Object.entries(schema._zod.def.shape).map(([k, t]) => [ - k, + convertCamelToSnake(k), schemaToIdl({schema: t as z.core.$ZodType, value: (value as Record)[k]}) ]) ); @@ -129,7 +133,10 @@ export const schemaFromIdl = ({schema, value}: IdlParams): unknown => { return Object.fromEntries( Object.entries(schema._zod.def.shape).map(([k, t]) => [ k, - schemaFromIdl({schema: t as z.core.$ZodType, value: (value as Record)[k]}) + schemaFromIdl({ + schema: t as z.core.$ZodType, + value: (value as Record)[convertCamelToSnake(k)] + }) ]) ); }