n8n/packages/core/src/ExtractValue.ts

182 lines
5.4 KiB
TypeScript

import type {
INode,
INodeParameters,
INodeProperties,
INodePropertyCollection,
INodePropertyOptions,
INodeType,
NodeParameterValueType,
} from 'n8n-workflow';
import { NodeOperationError, NodeHelpers, LoggerProxy } from 'n8n-workflow';
function findPropertyFromParameterName(
parameterName: string,
nodeType: INodeType,
node: INode,
nodeParameters: INodeParameters,
): INodePropertyOptions | INodeProperties | INodePropertyCollection {
let property: INodePropertyOptions | INodeProperties | INodePropertyCollection | undefined;
const paramParts = parameterName.split('.');
let currentParamPath = '';
const findProp = (
name: string,
options: Array<INodePropertyOptions | INodeProperties | INodePropertyCollection>,
): INodePropertyOptions | INodeProperties | INodePropertyCollection | undefined => {
return options.find(
(i) =>
i.name === name &&
NodeHelpers.displayParameterPath(nodeParameters, i, currentParamPath, node),
);
};
// eslint-disable-next-line no-restricted-syntax
for (const p of paramParts) {
const param = p.split('[')[0];
if (!property) {
property = findProp(param, nodeType.description.properties);
} else if ('options' in property && property.options) {
property = findProp(param, property.options);
currentParamPath += `.${param}`;
} else if ('values' in property) {
property = findProp(param, property.values);
currentParamPath += `.${param}`;
} else {
throw new Error(`Couldn't not find property "${parameterName}"`);
}
if (!property) {
throw new Error(`Couldn't not find property "${parameterName}"`);
}
}
if (!property) {
throw new Error(`Couldn't not find property "${parameterName}"`);
}
return property;
}
function executeRegexExtractValue(
value: string,
regex: RegExp,
parameterName: string,
parameterDisplayName: string,
): NodeParameterValueType | object {
const extracted = regex.exec(value);
if (!extracted) {
throw new Error(
`ERROR: ${parameterDisplayName} parameter's value is invalid. This is likely because the URL entered is incorrect`,
);
}
if (extracted.length < 2 || extracted.length > 2) {
throw new Error(
`Property "${parameterName}" has an invalid extractValue regex "${regex.source}". extractValue expects exactly one group to be returned.`,
);
}
return extracted[1];
}
function extractValueRLC(
value: NodeParameterValueType | object,
property: INodeProperties,
parameterName: string,
): NodeParameterValueType | object {
// Not an RLC value
if (typeof value !== 'object' || !value || !('mode' in value) || !('value' in value)) {
return value;
}
const modeProp = (property.modes ?? []).find((i) => i.name === value.mode);
if (!modeProp) {
return value.value;
}
if (!('extractValue' in modeProp) || !modeProp.extractValue) {
return value.value;
}
if (typeof value.value !== 'string') {
let typeName: string | undefined = value.value?.constructor.name;
if (value.value === null) {
typeName = 'null';
} else if (typeName === undefined) {
typeName = 'undefined';
}
LoggerProxy.error(
`Only strings can be passed to extractValue. Parameter "${parameterName}" passed "${typeName}"`,
);
throw new Error(
`ERROR: ${property.displayName} parameter's value is invalid. Please enter a valid ${modeProp.displayName}.`,
);
}
if (modeProp.extractValue.type !== 'regex') {
throw new Error(
`Property "${parameterName}" has an unknown extractValue type "${
modeProp.extractValue.type as string
}"`,
);
}
const regex = new RegExp(modeProp.extractValue.regex);
return executeRegexExtractValue(value.value, regex, parameterName, property.displayName);
}
function extractValueOther(
value: NodeParameterValueType | object,
property: INodeProperties | INodePropertyCollection,
parameterName: string,
): NodeParameterValueType | object {
if (!('extractValue' in property) || !property.extractValue) {
return value;
}
if (typeof value !== 'string') {
let typeName: string | undefined = value?.constructor.name;
if (value === null) {
typeName = 'null';
} else if (typeName === undefined) {
typeName = 'undefined';
}
LoggerProxy.error(
`Only strings can be passed to extractValue. Parameter "${parameterName}" passed "${typeName}"`,
);
throw new Error(
`ERROR: ${property.displayName} parameter's value is invalid. Please enter a valid value.`,
);
}
if (property.extractValue.type !== 'regex') {
throw new Error(
`Property "${parameterName}" has an unknown extractValue type "${
property.extractValue.type as string
}"`,
);
}
const regex = new RegExp(property.extractValue.regex);
return executeRegexExtractValue(value, regex, parameterName, property.displayName);
}
export function extractValue(
value: NodeParameterValueType | object,
parameterName: string,
node: INode,
nodeType: INodeType,
): NodeParameterValueType | object {
let property: INodePropertyOptions | INodeProperties | INodePropertyCollection;
try {
property = findPropertyFromParameterName(parameterName, nodeType, node, node.parameters);
// Definitely doesn't have value extractor
if (!('type' in property)) {
return value;
}
if (property.type === 'resourceLocator') {
return extractValueRLC(value, property, parameterName);
}
return extractValueOther(value, property, parameterName);
} catch (error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
throw new NodeOperationError(node, error);
}
}