refactor(core): Deduplicate isObjectLiteral, add docs and tests (#12332)
Some checks failed
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Benchmark Docker Image CI / build (push) Has been cancelled

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2024-12-20 18:41:05 +01:00 committed by GitHub
parent 06b86af735
commit 724e08562f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 62 additions and 17 deletions

View file

@ -1,7 +1,7 @@
import { isObjectLiteral } from 'n8n-core';
import type { IDataObject, INodeExecutionData } from 'n8n-workflow';
import type { MigrationContext, IrreversibleMigration } from '@/databases/types';
import { isObjectLiteral } from '@/utils';
type OldPinnedData = { [nodeName: string]: IDataObject[] };
type NewPinnedData = { [nodeName: string]: INodeExecutionData[] };

View file

@ -2,7 +2,7 @@ import type { LogScope } from '@n8n/config';
import { GlobalConfig } from '@n8n/config';
import callsites from 'callsites';
import type { TransformableInfo } from 'logform';
import { InstanceSettings } from 'n8n-core';
import { InstanceSettings, isObjectLiteral } from 'n8n-core';
import { LoggerProxy, LOG_LEVELS } from 'n8n-workflow';
import path, { basename } from 'node:path';
import pc from 'picocolors';
@ -10,7 +10,6 @@ import { Service } from 'typedi';
import winston from 'winston';
import { inDevelopment, inProduction } from '@/constants';
import { isObjectLiteral } from '@/utils';
import { noOp } from './constants';
import type { LogLocationMetadata, LogLevel, LogMetadata } from './types';

View file

@ -1,9 +1,8 @@
import { plainToInstance, instanceToPlain } from 'class-transformer';
import { validate } from 'class-validator';
import { isObjectLiteral } from 'n8n-core';
import { ApplicationError, jsonParse } from 'n8n-workflow';
import { isObjectLiteral } from '@/utils';
export class BaseFilter {
protected static async toFilter(rawFilter: string, Filter: typeof BaseFilter) {
const dto = jsonParse(rawFilter, { errorMessage: 'Failed to parse filter JSON' });

View file

@ -4,7 +4,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
import get from 'lodash/get';
import { ErrorReporter, NodeExecuteFunctions, RoutingNode } from 'n8n-core';
import { ErrorReporter, NodeExecuteFunctions, RoutingNode, isObjectLiteral } from 'n8n-core';
import type {
ICredentialsDecrypted,
ICredentialTestFunction,
@ -34,7 +34,6 @@ import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-da
import { RESPONSE_ERROR_MESSAGES } from '../constants';
import { CredentialsHelper } from '../credentials-helper';
import { isObjectLiteral } from '../utils';
const { OAUTH2_CREDENTIAL_TEST_SUCCEEDED, OAUTH2_CREDENTIAL_TEST_FAILED } = RESPONSE_ERROR_MESSAGES;

View file

@ -58,10 +58,6 @@ export function isStringArray(value: unknown): value is string[] {
export const isIntegerString = (value: string) => /^\d+$/.test(value);
export function isObjectLiteral(item: unknown): item is { [key: string]: unknown } {
return typeof item === 'object' && item !== null && !Array.isArray(item);
}
export function removeTrailingSlash(path: string) {
return path.endsWith('/') ? path.slice(0, -1) : path;
}

View file

@ -5,7 +5,7 @@
import type { PushType } from '@n8n/api-types';
import { GlobalConfig } from '@n8n/config';
import { stringify } from 'flatted';
import { ErrorReporter, WorkflowExecute } from 'n8n-core';
import { ErrorReporter, WorkflowExecute, isObjectLiteral } from 'n8n-core';
import { ApplicationError, NodeOperationError, Workflow, WorkflowHooks } from 'n8n-workflow';
import type {
IDataObject,
@ -45,7 +45,7 @@ import type { IWorkflowErrorData, UpdateExecutionPayload } from '@/interfaces';
import { NodeTypes } from '@/node-types';
import { Push } from '@/push';
import { WorkflowStatisticsService } from '@/services/workflow-statistics.service';
import { findSubworkflowStart, isObjectLiteral, isWorkflowIdValid } from '@/utils';
import { findSubworkflowStart, isWorkflowIdValid } from '@/utils';
import * as WorkflowHelpers from '@/workflow-helpers';
import { WorkflowRepository } from './databases/repositories/workflow.repository';

View file

@ -1,3 +1,5 @@
import { isObjectLiteral } from './utils';
/** A nodejs Buffer gone through JSON.stringify */
export type SerializedBuffer = {
type: 'Buffer';
@ -9,10 +11,6 @@ export function toBuffer(serializedBuffer: SerializedBuffer): Buffer {
return Buffer.from(serializedBuffer.data);
}
function isObjectLiteral(item: unknown): item is { [key: string]: unknown } {
return typeof item === 'object' && item !== null && !Array.isArray(item);
}
export function isSerializedBuffer(candidate: unknown): candidate is SerializedBuffer {
return (
isObjectLiteral(candidate) &&

View file

@ -0,0 +1,35 @@
import { isObjectLiteral } from '@/utils';
describe('isObjectLiteral', () => {
test.each([
['empty object literal', {}, true],
['object with properties', { foo: 'bar', num: 123 }, true],
['nested object literal', { nested: { foo: 'bar' } }, true],
['object with symbol key', { [Symbol.for('foo')]: 'bar' }, true],
['null', null, false],
['empty array', [], false],
['array with values', [1, 2, 3], false],
['number', 42, false],
['string', 'string', false],
['boolean', true, false],
['undefined', undefined, false],
['Date object', new Date(), false],
['RegExp object', new RegExp(''), false],
['Map object', new Map(), false],
['Set object', new Set(), false],
['arrow function', () => {}, false],
['regular function', function () {}, false],
['class instance', new (class TestClass {})(), false],
['object with custom prototype', Object.create({ customMethod: () => {} }), true],
['Object.create(null)', Object.create(null), false],
['Buffer', Buffer.from('test'), false],
['Serialized Buffer', Buffer.from('test').toJSON(), true],
['Promise', new Promise(() => {}), false],
])('should return %s for %s', (_, input, expected) => {
expect(isObjectLiteral(input)).toBe(expected);
});
it('should return false for Error objects', () => {
expect(isObjectLiteral(new Error())).toBe(false);
});
});

View file

@ -25,3 +25,4 @@ export * from './node-execution-context';
export * from './PartialExecutionUtils';
export { ErrorReporter } from './error-reporter';
export * from './SerializedBuffer';
export { isObjectLiteral } from './utils';

View file

@ -0,0 +1,18 @@
type ObjectLiteral = { [key: string | symbol]: unknown };
/**
* Checks if the provided value is a plain object literal (not null, not an array, not a class instance, and not a primitive).
* This function serves as a type guard.
*
* @param candidate - The value to check
* @returns {boolean} True if the value is an object literal, false otherwise
*/
export function isObjectLiteral(candidate: unknown): candidate is ObjectLiteral {
return (
typeof candidate === 'object' &&
candidate !== null &&
!Array.isArray(candidate) &&
// eslint-disable-next-line @typescript-eslint/ban-types
(Object.getPrototypeOf(candidate) as Object)?.constructor?.name === 'Object'
);
}