mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-20 19:32:15 +00:00
refactor(core): Improve top-level key validation in task runner (#16882)
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
import { DateTime, Duration, Interval } from 'luxon';
|
import { DateTime, Duration, Interval } from 'luxon';
|
||||||
import type { IBinaryData } from 'n8n-workflow';
|
import {
|
||||||
import { setGlobalState, type CodeExecutionMode, type IDataObject } from 'n8n-workflow';
|
type IBinaryData,
|
||||||
|
setGlobalState,
|
||||||
|
type CodeExecutionMode,
|
||||||
|
type IDataObject,
|
||||||
|
} from 'n8n-workflow';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import { builtinModules } from 'node:module';
|
import { builtinModules } from 'node:module';
|
||||||
|
|
||||||
@@ -26,6 +30,7 @@ import {
|
|||||||
withPairedItem,
|
withPairedItem,
|
||||||
wrapIntoJson,
|
wrapIntoJson,
|
||||||
} from './test-data';
|
} from './test-data';
|
||||||
|
import { ReservedKeyFoundError } from '../errors/reserved-key-not-found.error';
|
||||||
|
|
||||||
jest.mock('ws');
|
jest.mock('ws');
|
||||||
|
|
||||||
@@ -805,6 +810,15 @@ describe('JsTaskRunner', () => {
|
|||||||
}),
|
}),
|
||||||
).rejects.toThrow(ValidationError);
|
).rejects.toThrow(ValidationError);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw a ReservedKeyFoundError if there are unknown keys alongside reserved keys', async () => {
|
||||||
|
await expect(
|
||||||
|
executeForAllItems({
|
||||||
|
code: 'return [{json: {b: 1}, objectId: "123"}]',
|
||||||
|
inputItems: [{ a: 1 }],
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(ReservedKeyFoundError);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return static items', async () => {
|
it('should return static items', async () => {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { ValidationError } from './validation-error';
|
||||||
|
|
||||||
|
export class ReservedKeyFoundError extends ValidationError {
|
||||||
|
constructor(reservedKey: string, itemIndex: number) {
|
||||||
|
super({
|
||||||
|
message: 'Invalid output format',
|
||||||
|
description: `An output item contains the reserved key <code>${reservedKey}</code>. To get around this, please wrap each item in an object, under a key called <code>json</code>. <a href="https://docs.n8n.io/data/data-structure/#data-structure" target="_blank">Example</a>`,
|
||||||
|
itemIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { normalizeItems } from 'n8n-core';
|
import { normalizeItems } from 'n8n-core';
|
||||||
import type { INodeExecutionData } from 'n8n-workflow';
|
import type { INodeExecutionData } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { ReservedKeyFoundError } from './errors/reserved-key-not-found.error';
|
||||||
import { ValidationError } from './errors/validation-error';
|
import { ValidationError } from './errors/validation-error';
|
||||||
import { isObject } from './obj-utils';
|
import { isObject } from './obj-utils';
|
||||||
|
|
||||||
@@ -19,17 +20,28 @@ export const REQUIRED_N8N_ITEM_KEYS = new Set([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
function validateTopLevelKeys(item: INodeExecutionData, itemIndex: number) {
|
function validateTopLevelKeys(item: INodeExecutionData, itemIndex: number) {
|
||||||
|
let foundReservedKey: string | null = null;
|
||||||
|
const unknownKeys: string[] = [];
|
||||||
|
|
||||||
for (const key in item) {
|
for (const key in item) {
|
||||||
if (Object.prototype.hasOwnProperty.call(item, key)) {
|
if (!Object.prototype.hasOwnProperty.call(item, key)) continue;
|
||||||
if (REQUIRED_N8N_ITEM_KEYS.has(key)) continue;
|
|
||||||
|
if (REQUIRED_N8N_ITEM_KEYS.has(key)) {
|
||||||
|
foundReservedKey ??= key;
|
||||||
|
} else {
|
||||||
|
unknownKeys.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unknownKeys.length > 0) {
|
||||||
|
if (foundReservedKey) throw new ReservedKeyFoundError(foundReservedKey, itemIndex);
|
||||||
|
|
||||||
throw new ValidationError({
|
throw new ValidationError({
|
||||||
message: `Unknown top-level item key: ${key}`,
|
message: `Unknown top-level item key: ${unknownKeys[0]}`,
|
||||||
description: 'Access the properties of an item under `.json`, e.g. `item.json`',
|
description: 'Access the properties of an item under `.json`, e.g. `item.json`',
|
||||||
itemIndex,
|
itemIndex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateItem({ json, binary }: INodeExecutionData, itemIndex: number) {
|
function validateItem({ json, binary }: INodeExecutionData, itemIndex: number) {
|
||||||
|
|||||||
@@ -185,13 +185,37 @@ export abstract class Sandbox extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private validateTopLevelKeys(item: INodeExecutionData, itemIndex: number) {
|
private validateTopLevelKeys(item: INodeExecutionData, itemIndex: number) {
|
||||||
Object.keys(item).forEach((key) => {
|
let foundReservedKey: string | null = null;
|
||||||
if (REQUIRED_N8N_ITEM_KEYS.has(key)) return;
|
const unknownKeys: string[] = [];
|
||||||
|
|
||||||
|
for (const key in item) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(item, key)) continue;
|
||||||
|
|
||||||
|
if (REQUIRED_N8N_ITEM_KEYS.has(key)) {
|
||||||
|
foundReservedKey ??= key;
|
||||||
|
} else {
|
||||||
|
unknownKeys.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unknownKeys.length > 0) {
|
||||||
|
if (foundReservedKey) throw new ReservedKeyFoundError(foundReservedKey, itemIndex);
|
||||||
|
|
||||||
throw new ValidationError({
|
throw new ValidationError({
|
||||||
message: `Unknown top-level item key: ${key}`,
|
message: `Unknown top-level item key: ${unknownKeys[0]}`,
|
||||||
description: 'Access the properties of an item under `.json`, e.g. `item.json`',
|
description: 'Access the properties of an item under `.json`, e.g. `item.json`',
|
||||||
itemIndex,
|
itemIndex,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReservedKeyFoundError extends ValidationError {
|
||||||
|
constructor(reservedKey: string, itemIndex: number) {
|
||||||
|
super({
|
||||||
|
message: 'Invalid output format',
|
||||||
|
description: `An output item contains the reserved key <code>${reservedKey}</code>. To get around this, please wrap each item in an object, under a key called <code>json</code>. <a href="https://docs.n8n.io/data/data-structure/#data-structure" target="_blank">Example</a>`,
|
||||||
|
itemIndex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user