diff --git a/packages/core/src/execution-engine/node-execution-context/utils/__tests__/validate-value-against-schema.test.ts b/packages/core/src/execution-engine/node-execution-context/utils/__tests__/validate-value-against-schema.test.ts index af6eb43395..60d4ca5367 100644 --- a/packages/core/src/execution-engine/node-execution-context/utils/__tests__/validate-value-against-schema.test.ts +++ b/packages/core/src/execution-engine/node-execution-context/utils/__tests__/validate-value-against-schema.test.ts @@ -1,4 +1,4 @@ -import type { IDataObject, INode, INodeType } from 'n8n-workflow'; +import { ExpressionError, type IDataObject, type INode, type INodeType } from 'n8n-workflow'; import { validateValueAgainstSchema } from '../validate-value-against-schema'; @@ -311,8 +311,86 @@ describe('validateValueAgainstSchema', () => { }); }); - describe('when attemptToConvertTypes is not set (=default)', () => { - test('should correctly validate and convert types', () => { + describe('when showTypeConversionOptions is not set (=default)', () => { + test('should correctly convert types', () => { + const nodeType = { + description: { + properties: [ + { + displayName: 'Columns', + name: 'columns', + type: 'resourceMapper', + required: true, + typeOptions: { + loadOptionsDependsOn: ['table.value', 'operation'], + resourceMapper: { + mode: 'upsert', + }, + }, + }, + ], + }, + } as unknown as INodeType; + + const node: INode = { + parameters: { + columns: { + mappingMode: 'defineBelow', + value: { + id: 2, + count: '={{ $json.count }}', + }, + matchingColumns: ['id'], + attemptToConvertTypes: false, + convertFieldsToString: true, + schema: [ + { + id: 'id', + displayName: 'id', + required: false, + defaultMatch: true, + display: true, + type: 'number', + canBeUsedToMatch: true, + }, + { + id: 'count', + displayName: 'count', + required: false, + defaultMatch: false, + display: true, + type: 'number', + canBeUsedToMatch: false, + }, + ], + }, + options: {}, + }, + id: '8d6cec63-8db1-440c-8966-4d6311ee69a9', + name: 'add products to DB', + type: 'n8n-nodes-base.postgres', + typeVersion: 2.3, + position: [420, 0], + }; + + const value = { + id: 2, + count: '23', + }; + + const parameterName = 'columns.value'; + + const result = validateValueAgainstSchema(node, nodeType, value, parameterName, 0, 0); + + expect(result).toEqual({ + id: 2, + count: 23, + }); + }); + }); + + describe('when showTypeConversionOptions is true', () => { + test('should throw an error', () => { const nodeType = { description: { properties: [ @@ -321,22 +399,10 @@ describe('validateValueAgainstSchema', () => { name: 'columns', type: 'resourceMapper', noDataExpression: true, - default: { - mappingMode: 'defineBelow', - value: null, - }, - required: true, typeOptions: { - loadOptionsDependsOn: ['table.value', 'operation'], resourceMapper: { - resourceMapperMethod: 'getMappingColumns', + showTypeConversionOptions: true, mode: 'upsert', - fieldWords: { - singular: 'column', - plural: 'columns', - }, - addAllFields: true, - multiKeyMatch: true, }, }, }, @@ -390,12 +456,9 @@ describe('validateValueAgainstSchema', () => { const parameterName = 'columns.value'; - const result = validateValueAgainstSchema(node, nodeType, value, parameterName, 0, 0); - - expect(result).toEqual({ - id: 2, - count: 23, - }); + expect(() => + validateValueAgainstSchema(node, nodeType, value, parameterName, 0, 0), + ).toThrow(new ExpressionError("Invalid input for 'count' [item 0]")); }); }); }); diff --git a/packages/core/src/execution-engine/node-execution-context/utils/validate-value-against-schema.ts b/packages/core/src/execution-engine/node-execution-context/utils/validate-value-against-schema.ts index f05929c456..73b5423f49 100644 --- a/packages/core/src/execution-engine/node-execution-context/utils/validate-value-against-schema.ts +++ b/packages/core/src/execution-engine/node-execution-context/utils/validate-value-against-schema.ts @@ -6,6 +6,7 @@ import type { INodePropertyCollection, INodePropertyOptions, INodeType, + ResourceMapperTypeOptions, } from 'n8n-workflow'; import { ExpressionError, @@ -20,9 +21,11 @@ const validateResourceMapperValue = ( parameterName: string, paramValues: { [key: string]: unknown }, node: INode, - skipRequiredCheck = false, + resourceMapperTypeOptions?: ResourceMapperTypeOptions, ): ExtendedValidationResult => { const result: ExtendedValidationResult = { valid: true, newValue: paramValues }; + const skipRequiredCheck = resourceMapperTypeOptions?.mode !== 'add'; + const enableTypeValidationOptions = Boolean(resourceMapperTypeOptions?.showTypeConversionOptions); const paramNameParts = parameterName.split('.'); if (paramNameParts.length !== 2) { return result; @@ -56,8 +59,8 @@ const validateResourceMapperValue = ( if (schemaEntry?.type) { const validationResult = validateFieldType(key, resolvedValue, schemaEntry.type, { valueOptions: schemaEntry.options, - strict: resourceMapperField.attemptToConvertTypes === false, - parseStrings: Boolean(resourceMapperField.convertFieldsToString), + strict: enableTypeValidationOptions && !resourceMapperField.attemptToConvertTypes, + parseStrings: enableTypeValidationOptions && resourceMapperField.convertFieldsToString, }); if (!validationResult.valid) { @@ -185,7 +188,7 @@ export const validateValueAgainstSchema = ( parameterName, parameterValue as { [key: string]: unknown }, node, - propertyDescription.typeOptions?.resourceMapper?.mode !== 'add', + propertyDescription.typeOptions?.resourceMapper, ); } else if (['fixedCollection', 'collection'].includes(propertyDescription.type)) { validationResult = validateCollection( diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 5322c82dc7..8b5f81c08a 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -2723,8 +2723,8 @@ export type ResourceMapperValue = { value: { [key: string]: string | number | boolean | null } | null; matchingColumns: string[]; schema: ResourceMapperField[]; - attemptToConvertTypes?: boolean; - convertFieldsToString?: boolean; + attemptToConvertTypes: boolean; + convertFieldsToString: boolean; }; export type FilterOperatorType =