fix(core): Only use new resource mapper type validation when it is enabled (#13099)

This commit is contained in:
Elias Meire 2025-02-06 13:21:13 +01:00 committed by GitHub
parent ef87da4c19
commit a37c8e8fb8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 94 additions and 28 deletions

View file

@ -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]"));
});
});
});

View file

@ -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(

View file

@ -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 =