diff --git a/apps/api/src/workflows/services/workflow.service.ts b/apps/api/src/workflows/services/workflow.service.ts index 322bfe38..b29a8b3e 100644 --- a/apps/api/src/workflows/services/workflow.service.ts +++ b/apps/api/src/workflows/services/workflow.service.ts @@ -1,5 +1,5 @@ import { BaseService } from '@app/common/base/base.service' -import { replaceTemplateFields } from '@app/definitions/utils/field.utils' +import { fixObjectTypes, replaceTemplateFields } from '@app/definitions/utils/field.utils' import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common' import { DeepPartial, DeleteOneOptions, UpdateOneOptions } from '@ptc-org/nestjs-query-core' import { ReturnModelType } from '@typegoose/typegoose' @@ -224,7 +224,10 @@ export class WorkflowService extends BaseService { name: trigger.name, inputs: { ...templateInputs, - ...replaceTemplateFields(idsMap, trigger.inputs ?? {}, templateInputs), + ...fixObjectTypes( + replaceTemplateFields(idsMap, trigger.inputs ?? {}, templateInputs), + workflow.templateSchema ?? {}, + ), }, credentials: credentialsForTrigger?.id, schedule: { @@ -274,7 +277,10 @@ export class WorkflowService extends BaseService { name: action.name, inputs: { ...templateInputs, - ...replaceTemplateFields(idsMap, action.inputs ?? {}, templateInputs), + ...fixObjectTypes( + replaceTemplateFields(idsMap, action.inputs ?? {}, templateInputs), + workflow.templateSchema ?? {}, + ), }, previousAction: idsMap.get(previousAction?.id ?? '') as any, previousActionCondition: previousAction?.condition, diff --git a/libs/definitions/src/utils/field.utils.spec.ts b/libs/definitions/src/utils/field.utils.spec.ts index 55db7fe0..4f28f5d9 100644 --- a/libs/definitions/src/utils/field.utils.spec.ts +++ b/libs/definitions/src/utils/field.utils.spec.ts @@ -1,4 +1,5 @@ -import { getInterpolatedVariables, replaceTemplateFields } from './field.utils' +import { JSONSchema7 } from 'json-schema' +import { fixObjectTypes, getInterpolatedVariables, replaceTemplateFields } from './field.utils' describe('FieldUtils', () => { describe('getInterpolatedVariables', () => { @@ -125,4 +126,189 @@ describe('FieldUtils', () => { }) }) }) + + describe('fixObjectTypes', () => { + const schema: JSONSchema7 = { + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'integer' }, + active: { type: 'boolean' }, + score: { type: 'number' }, + hobbies: { + type: 'array', + items: { type: 'string' }, + }, + address: { + type: 'object', + properties: { + street: { type: 'string' }, + city: { type: 'string' }, + }, + }, + friends: { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'integer' }, + active: { type: 'boolean' }, + }, + }, + }, + }, + } + + it('should fix object types according to schema', () => { + const input = { + name: 'John Doe', + age: '30', + active: 'true', + score: '100.5', + hobbies: ['reading', 'sports'], + address: { + street: '123 Main St', + city: 'New York', + }, + } + + const expectedOutput = { + name: 'John Doe', + age: 30, + active: true, + score: 100.5, + hobbies: ['reading', 'sports'], + address: { + street: '123 Main St', + city: 'New York', + }, + } + + expect(fixObjectTypes(input, schema)).toEqual(expectedOutput) + }) + + it('should not change the value of items not following the schema', () => { + const input = { + name: 'John Doe', + age: '{{template.age}}', + active: '{{template.active}}', + score: '{{template.score}}', + hobbies: '{{template.hobbies}}', + address: '{{template.address}}', + } + + const expectedOutput = { + name: 'John Doe', + age: '{{template.age}}', + active: '{{template.active}}', + score: '{{template.score}}', + hobbies: '{{template.hobbies}}', + address: '{{template.address}}', + } + + expect(fixObjectTypes(input, schema)).toEqual(expectedOutput) + }) + + it('should not modify values with the correct type', () => { + const input = { + name: 'Jane Doe', + age: 28, + active: false, + hobbies: ['painting', 'music'], + address: { + street: '456 Elm St', + city: 'Los Angeles', + }, + } + + expect(fixObjectTypes(input, schema)).toEqual(input) + }) + + it('should handle nested objects and arrays', () => { + const input = { + name: 'John Doe', + age: '30', + active: 'true', + hobbies: ['reading', 'sports'], + address: { + street: '123 Main St', + city: 'New York', + }, + friends: [ + { + name: 'Alice', + age: '35', + active: 'false', + }, + { + name: 'Bob', + age: '32', + active: 'true', + }, + ], + } + + const expectedOutput = { + name: 'John Doe', + age: 30, + active: true, + hobbies: ['reading', 'sports'], + address: { + street: '123 Main St', + city: 'New York', + }, + friends: [ + { + name: 'Alice', + age: 35, + active: false, + }, + { + name: 'Bob', + age: 32, + active: true, + }, + ], + } + + expect(fixObjectTypes(input, schema)).toEqual(expectedOutput) + }) + + it('should handle empty objects and arrays', () => { + const input = { + name: 'John Doe', + age: '30', + active: 'true', + hobbies: [], + address: {}, + } + + const expectedOutput = { + name: 'John Doe', + age: 30, + active: true, + hobbies: [], + address: {}, + } + + expect(fixObjectTypes(input, schema)).toEqual(expectedOutput) + }) + + it('should handle missing properties', () => { + const input = { + name: 'John Doe', + age: '30', + active: 'true', + } + + const expectedOutput = { + name: 'John Doe', + age: 30, + active: true, + } + + expect(fixObjectTypes(input, schema)).toEqual(expectedOutput) + }) + }) }) diff --git a/libs/definitions/src/utils/field.utils.ts b/libs/definitions/src/utils/field.utils.ts index 5a518ca3..8b5ca5a4 100644 --- a/libs/definitions/src/utils/field.utils.ts +++ b/libs/definitions/src/utils/field.utils.ts @@ -1,4 +1,5 @@ import { getAddress, isAddress } from 'ethers/lib/utils' +import { JSONSchema7, JSONSchema7TypeName } from 'json-schema' import { VarEvm } from '../operation-evm' export function hasInterpolation(str: string) { @@ -135,3 +136,51 @@ export function replaceTemplateFields( } return result } + +export function fixObjectTypes(obj: Record, schema: JSONSchema7): Record { + if (!schema?.properties) { + return obj + } + if (!obj || typeof obj !== 'object') { + return obj + } + for (const [key, value] of Object.entries(obj)) { + const type: JSONSchema7TypeName = schema.properties[key] && (schema.properties[key] as any).type + switch (type) { + case 'object': + obj[key] = fixObjectTypes(value, schema.properties![key] as JSONSchema7) + break + case 'array': + if (Array.isArray(value)) { + obj[key] = value.map((item) => { + if (typeof item === 'object') { + return fixObjectTypes(item, (schema.properties![key] as any).items as JSONSchema7) + } else { + return item + } + }) + } else { + obj[key] = value + } + + break + case 'boolean': + obj[key] = value === 'true' ? true : value === 'false' ? false : value + break + case 'number': + obj[key] = Number.isFinite(Number(value)) ? Number(value) : value + break + case 'integer': + obj[key] = Number.isInteger(Number(value)) ? Number(value) : value + break + case 'null': + obj[key] = value === 'null' ? null : value + break + case 'string': + default: + obj[key] = value + break + } + } + return obj +}