fix(Read Binary File Node): Do not crash the execution when the source file does not exist (#5100)

This issue was introduced in https://github.com/n8n-io/n8n/pull/5069
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2023-01-06 14:15:46 +01:00 committed by GitHub
parent c4df2049a8
commit c97f3cad59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 41 additions and 40 deletions

View file

@ -67,6 +67,7 @@ import {
ITriggerFunctions, ITriggerFunctions,
IWebhookFunctions, IWebhookFunctions,
BinaryMetadata, BinaryMetadata,
FileSystemHelperFunctions,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { Agent } from 'https'; import { Agent } from 'https';
@ -93,6 +94,8 @@ import axios, {
} from 'axios'; } from 'axios';
import url, { URL, URLSearchParams } from 'url'; import url, { URL, URLSearchParams } from 'url';
import type { Readable } from 'stream'; import type { Readable } from 'stream';
import { access as fsAccess } from 'fs/promises';
import { createReadStream } from 'fs';
import { BinaryDataManager } from './BinaryDataManager'; import { BinaryDataManager } from './BinaryDataManager';
import type { IResponseError, IWorkflowSettings } from './Interfaces'; import type { IResponseError, IWorkflowSettings } from './Interfaces';
@ -1997,6 +2000,21 @@ const getRequestHelperFunctions = (
}, },
}); });
const getFileSystemHelperFunctions = (node: INode): FileSystemHelperFunctions => ({
async createReadStream(filePath) {
try {
await fsAccess(filePath);
} catch (error) {
throw error.code === 'ENOENT'
? new NodeOperationError(node, error, {
message: `The file "${String(filePath)}" could not be accessed.`,
})
: error;
}
return createReadStream(filePath);
},
});
const getBinaryHelperFunctions = ({ const getBinaryHelperFunctions = ({
executionId, executionId,
}: IWorkflowExecuteAdditionalData): BinaryHelperFunctions => ({ }: IWorkflowExecuteAdditionalData): BinaryHelperFunctions => ({
@ -2292,6 +2310,7 @@ export function getExecuteFunctions(
}, },
helpers: { helpers: {
...getRequestHelperFunctions(workflow, node, additionalData), ...getRequestHelperFunctions(workflow, node, additionalData),
...getFileSystemHelperFunctions(node),
...getBinaryHelperFunctions(additionalData), ...getBinaryHelperFunctions(additionalData),
getBinaryDataBuffer: async (itemIndex, propertyName, inputIndex = 0) => getBinaryDataBuffer: async (itemIndex, propertyName, inputIndex = 0) =>
getBinaryDataBuffer(inputData, itemIndex, propertyName, inputIndex), getBinaryDataBuffer(inputData, itemIndex, propertyName, inputIndex),

View file

@ -1,12 +1,5 @@
import { IExecuteFunctions } from 'n8n-core'; import type { IExecuteFunctions } from 'n8n-core';
import { import type { INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
INodeExecutionData,
INodeType,
INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow';
import { createReadStream } from 'fs';
export class ReadBinaryFile implements INodeType { export class ReadBinaryFile implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
@ -53,23 +46,6 @@ export class ReadBinaryFile implements INodeType {
for (let itemIndex = 0; itemIndex < length; itemIndex++) { for (let itemIndex = 0; itemIndex < length; itemIndex++) {
try { try {
item = items[itemIndex]; item = items[itemIndex];
const dataPropertyName = this.getNodeParameter('dataPropertyName', itemIndex) as string;
const filePath = this.getNodeParameter('filePath', itemIndex) as string;
let data;
try {
data = createReadStream(filePath);
} catch (error) {
if (error.code === 'ENOENT') {
throw new NodeOperationError(
this.getNode(),
`The file "${filePath}" could not be found.`,
);
}
throw error;
}
const newItem: INodeExecutionData = { const newItem: INodeExecutionData = {
json: item.json, json: item.json,
binary: {}, binary: {},
@ -85,7 +61,10 @@ export class ReadBinaryFile implements INodeType {
Object.assign(newItem.binary, item.binary); Object.assign(newItem.binary, item.binary);
} }
newItem.binary![dataPropertyName] = await this.helpers.prepareBinaryData(data, filePath); const filePath = this.getNodeParameter('filePath', itemIndex) as string;
const stream = await this.helpers.createReadStream(filePath);
const dataPropertyName = this.getNodeParameter('dataPropertyName', itemIndex) as string;
newItem.binary![dataPropertyName] = await this.helpers.prepareBinaryData(stream, filePath);
returnData.push(newItem); returnData.push(newItem);
} catch (error) { } catch (error) {
if (this.continueOnFail()) { if (this.continueOnFail()) {

View file

@ -1,8 +1,6 @@
import { IExecuteFunctions } from 'n8n-core'; import type { IExecuteFunctions } from 'n8n-core';
import { INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow'; import type { INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
import glob from 'fast-glob'; import glob from 'fast-glob';
import { createReadStream } from 'fs';
import type { Readable } from 'stream';
export class ReadBinaryFiles implements INodeType { export class ReadBinaryFiles implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
@ -46,22 +44,17 @@ export class ReadBinaryFiles implements INodeType {
const files = await glob(fileSelector); const files = await glob(fileSelector);
const items: INodeExecutionData[] = []; const items: INodeExecutionData[] = [];
let item: INodeExecutionData;
let data: Readable;
for (const filePath of files) { for (const filePath of files) {
data = createReadStream(filePath); const stream = await this.helpers.createReadStream(filePath);
items.push({
item = {
binary: { binary: {
[dataPropertyName]: await this.helpers.prepareBinaryData(data, filePath), [dataPropertyName]: await this.helpers.prepareBinaryData(stream, filePath),
}, },
json: {}, json: {},
pairedItem: { pairedItem: {
item: 0, item: 0,
}, },
}; });
items.push(item);
} }
return this.prepareOutputData(items); return this.prepareOutputData(items);

View file

@ -15,6 +15,7 @@ import type { WorkflowActivationError } from './WorkflowActivationError';
import type { WorkflowOperationError } from './WorkflowErrors'; import type { WorkflowOperationError } from './WorkflowErrors';
import type { NodeApiError, NodeOperationError } from './NodeErrors'; import type { NodeApiError, NodeOperationError } from './NodeErrors';
import type { ExpressionError } from './ExpressionError'; import type { ExpressionError } from './ExpressionError';
import { PathLike } from 'fs';
export interface IAdditionalCredentialOptions { export interface IAdditionalCredentialOptions {
oauth2?: IOAuth2Options; oauth2?: IOAuth2Options;
@ -640,6 +641,10 @@ export interface JsonHelperFunctions {
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[]; returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
} }
export interface FileSystemHelperFunctions {
createReadStream(path: PathLike): Promise<Readable>;
}
export interface BinaryHelperFunctions { export interface BinaryHelperFunctions {
prepareBinaryData( prepareBinaryData(
binaryData: Buffer | Readable, binaryData: Buffer | Readable,
@ -725,6 +730,7 @@ export type IExecuteFunctions = ExecuteFunctions.GetNodeParameterFn &
helpers: RequestHelperFunctions & helpers: RequestHelperFunctions &
BinaryHelperFunctions & BinaryHelperFunctions &
FileSystemHelperFunctions &
JsonHelperFunctions & { JsonHelperFunctions & {
normalizeItems(items: INodeExecutionData | INodeExecutionData[]): INodeExecutionData[]; normalizeItems(items: INodeExecutionData | INodeExecutionData[]): INodeExecutionData[];
constructExecutionMetaData( constructExecutionMetaData(

View file

@ -217,6 +217,7 @@ abstract class NodeError extends ExecutionBaseError {
} }
interface NodeOperationErrorOptions { interface NodeOperationErrorOptions {
message?: string;
description?: string; description?: string;
runIndex?: number; runIndex?: number;
itemIndex?: number; itemIndex?: number;
@ -234,6 +235,9 @@ export class NodeOperationError extends NodeError {
} }
super(node, error); super(node, error);
if (options.message) {
this.message = options.message;
}
this.description = options.description; this.description = options.description;
this.context.runIndex = options.runIndex; this.context.runIndex = options.runIndex;
this.context.itemIndex = options.itemIndex; this.context.itemIndex = options.itemIndex;