From 1d57b1094211eda564b2db99006eb8fb9e791f5e Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Fri, 21 Oct 2022 21:52:43 +0300 Subject: [PATCH] refactor(core): fix for no-uncaught-json-parse warnings --- packages/@n8n_io/eslint-config/base.js | 4 +--- packages/cli/src/CurlConverterHelper.ts | 7 +++--- packages/cli/src/GenericHelpers.ts | 4 +++- packages/cli/src/LoadNodesAndCredentials.ts | 3 ++- .../v1/shared/services/pagination.service.ts | 5 ++--- packages/cli/src/Server.ts | 19 ++++++++-------- packages/cli/src/api/executions.api.ts | 4 ++-- .../cli/src/databases/utils/transformers.ts | 7 +++--- .../src/components/NodeDetailsView.vue | 3 ++- packages/editor-ui/src/components/helpers.ts | 4 ++-- .../nodes/AgileCrm/AgileCrm.node.ts | 11 +++++----- .../nodes-base/nodes/Amqp/AmqpTrigger.node.ts | 3 ++- .../nodes/Aws/AwsSnsTrigger.node.ts | 2 +- .../CertificateManager/GenericFunctions.ts | 22 +++++++------------ .../nodes/Cockpit/CollectionFunctions.ts | 10 ++++++--- .../nodes/Cockpit/GenericFunctions.ts | 4 ++-- .../nodes-base/nodes/Discord/Discord.node.ts | 9 ++++---- .../Elasticsearch/Elasticsearch.node.ts | 15 +++++++++++-- .../GoogleFirebaseCloudFirestore.node.ts | 3 ++- .../HttpRequest/V3/HttpRequestV3.node.ts | 15 ++++++++----- .../nodes/JotForm/JotFormTrigger.node.ts | 4 +++- .../nodes/Metabase/QuestionsDescription.ts | 5 ++--- .../MoveBinaryData/MoveBinaryData.node.ts | 5 +++-- .../nodes/NocoDB/GenericFunctions.ts | 3 ++- .../nodes/Notion/v2/NotionV2.node.ts | 3 ++- .../nodes/RenameKeys/RenameKeys.node.ts | 1 - .../RespondToWebhook/RespondToWebhook.node.ts | 5 ++++- .../nodes/SendInBlue/GenericFunctions.ts | 5 +++-- .../nodes/SseTrigger/SseTrigger.node.ts | 5 +++-- .../nodes/StopAndError/StopAndError.node.ts | 4 +++- .../SurveyMonkey/SurveyMonkeyTrigger.node.ts | 4 +++- .../nodes/TheHive/GenericFunctions.ts | 6 ++--- .../nodes/Todoist/OperationHandler.ts | 4 ++-- .../nodes/Wufoo/WufooTrigger.node.ts | 14 +++++++++--- packages/workflow/src/index.ts | 2 +- packages/workflow/src/utils.ts | 19 ++++++++++++++++ 36 files changed, 150 insertions(+), 93 deletions(-) diff --git a/packages/@n8n_io/eslint-config/base.js b/packages/@n8n_io/eslint-config/base.js index 7187bbf762..726b304bc2 100644 --- a/packages/@n8n_io/eslint-config/base.js +++ b/packages/@n8n_io/eslint-config/base.js @@ -319,9 +319,7 @@ const config = (module.exports = { // ---------------------------------- // eslint-plugin-n8n-local-rules // ---------------------------------- - - // TODO: set to `error` and fix offenses - 'n8n-local-rules/no-uncaught-json-parse': 'warn', + 'n8n-local-rules/no-uncaught-json-parse': 'error', 'n8n-local-rules/no-json-parse-json-stringify': 'error', diff --git a/packages/cli/src/CurlConverterHelper.ts b/packages/cli/src/CurlConverterHelper.ts index a58294630d..cee25c0d73 100644 --- a/packages/cli/src/CurlConverterHelper.ts +++ b/packages/cli/src/CurlConverterHelper.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import curlconverter from 'curlconverter'; import get from 'lodash.get'; +import { jsonParse } from 'n8n-workflow'; interface CurlJson { url: string; @@ -109,8 +110,7 @@ const DOWNLOAD_FILE_FLAGS = ['-O', '-o']; const IGNORE_SSL_ISSUES_FLAGS = ['-k', '--insecure']; const curlToJson = (curlCommand: string): CurlJson => { - // eslint-disable-next-line n8n-local-rules/no-uncaught-json-parse - return JSON.parse(curlconverter.toJsonString(curlCommand)) as CurlJson; + return jsonParse(curlconverter.toJsonString(curlCommand)); }; const isContentType = (headers: CurlJson['headers'], contentType: ContentTypes): boolean => { @@ -196,8 +196,7 @@ const extractQueries = (queries: CurlJson['queries'] = {}): HttpNodeQueries => { const extractJson = (body: CurlJson['data']) => //@ts-ignore - // eslint-disable-next-line n8n-local-rules/no-uncaught-json-parse - JSON.parse(Object.keys(body)[0]) as { [key: string]: string }; + jsonParse<{ [key: string]: string }>(Object.keys(body)[0]); const jsonBodyToNodeParameters = (body: CurlJson['data'] = {}): Parameter[] | [] => { const data = extractJson(body); diff --git a/packages/cli/src/GenericHelpers.ts b/packages/cli/src/GenericHelpers.ts index 40f227befc..6eebf937d9 100644 --- a/packages/cli/src/GenericHelpers.ts +++ b/packages/cli/src/GenericHelpers.ts @@ -13,6 +13,7 @@ import { IDataObject, INode, IRunExecutionData, + jsonParse, Workflow, WorkflowExecuteMode, } from 'n8n-workflow'; @@ -28,6 +29,7 @@ import { IPackageVersions, IWorkflowDb, ResponseHelper, + IN8nNodePackageJson, } from '.'; // eslint-disable-next-line import/order import { Like } from 'typeorm'; @@ -74,7 +76,7 @@ export async function getVersions(): Promise { const packageFile = await fsReadFile(pathJoin(__dirname, '../../package.json'), 'utf8'); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const packageData = JSON.parse(packageFile); + const packageData = jsonParse(packageFile); versionCache = { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment diff --git a/packages/cli/src/LoadNodesAndCredentials.ts b/packages/cli/src/LoadNodesAndCredentials.ts index 7c6494ae87..34e493055e 100644 --- a/packages/cli/src/LoadNodesAndCredentials.ts +++ b/packages/cli/src/LoadNodesAndCredentials.ts @@ -21,6 +21,7 @@ import { INodeTypeNameVersion, INodeVersionedType, LoggerProxy, + jsonParse, } from 'n8n-workflow'; import { @@ -509,7 +510,7 @@ class LoadNodesAndCredentialsClass { async readPackageJson(packagePath: string): Promise { // Get the absolute path of the package const packageFileString = await fsReadFile(path.join(packagePath, 'package.json'), 'utf8'); - return JSON.parse(packageFileString) as IN8nNodePackageJson; + return jsonParse(packageFileString); } /** diff --git a/packages/cli/src/PublicApi/v1/shared/services/pagination.service.ts b/packages/cli/src/PublicApi/v1/shared/services/pagination.service.ts index a06ca5e78a..2df7029ce2 100644 --- a/packages/cli/src/PublicApi/v1/shared/services/pagination.service.ts +++ b/packages/cli/src/PublicApi/v1/shared/services/pagination.service.ts @@ -1,3 +1,4 @@ +import { jsonParse } from 'n8n-workflow'; import { CursorPagination, OffsetPagination, @@ -6,9 +7,7 @@ import { } from '../../../types'; export const decodeCursor = (cursor: string): PaginationOffsetDecoded | PaginationCursorDecoded => { - return JSON.parse(Buffer.from(cursor, 'base64').toString()) as - | PaginationCursorDecoded - | PaginationOffsetDecoded; + return jsonParse(Buffer.from(cursor, 'base64').toString()); }; const encodeOffSetPagination = (pagination: OffsetPagination): string | null => { diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 7b561f86ee..4eb5b28858 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -67,6 +67,7 @@ import { ITelemetrySettings, LoggerProxy, NodeHelpers, + jsonParse, WebhookHttpMethod, WorkflowExecuteMode, } from 'n8n-workflow'; @@ -787,20 +788,20 @@ class App { `/${this.restEndpoint}/node-parameter-options`, ResponseHelper.send( async (req: NodeParameterOptionsRequest): Promise => { - const nodeTypeAndVersion = JSON.parse( + const nodeTypeAndVersion = jsonParse( req.query.nodeTypeAndVersion, ) as INodeTypeNameVersion; const { path, methodName } = req.query; - const currentNodeParameters = JSON.parse( + const currentNodeParameters = jsonParse( req.query.currentNodeParameters, ) as INodeParameters; let credentials: INodeCredentials | undefined; if (req.query.credentials) { - credentials = JSON.parse(req.query.credentials); + credentials = jsonParse(req.query.credentials); } const loadDataInstance = new LoadNodeParameterOptions( @@ -823,7 +824,7 @@ class App { if (req.query.loadOptions) { return loadDataInstance.getOptionsViaRequestProperty( // @ts-ignore - JSON.parse(req.query.loadOptions as string), + jsonParse(req.query.loadOptions as string), additionalData, ); } @@ -842,7 +843,7 @@ class App { req: NodeListSearchRequest, res: express.Response, ): Promise => { - const nodeTypeAndVersion = JSON.parse( + const nodeTypeAndVersion = jsonParse( req.query.nodeTypeAndVersion, ) as INodeTypeNameVersion; @@ -852,14 +853,14 @@ class App { throw new ResponseError('Parameter currentNodeParameters is required.', undefined, 400); } - const currentNodeParameters = JSON.parse( + const currentNodeParameters = jsonParse( req.query.currentNodeParameters, ) as INodeParameters; let credentials: INodeCredentials | undefined; if (req.query.credentials) { - credentials = JSON.parse(req.query.credentials); + credentials = jsonParse(req.query.credentials); } const listSearchInstance = new LoadNodeListSearch( @@ -1454,7 +1455,7 @@ class App { if (!sharedWorkflowIds.length) return []; if (req.query.filter) { - const { workflowId } = JSON.parse(req.query.filter); + const { workflowId } = jsonParse(req.query.filter); if (workflowId && sharedWorkflowIds.includes(workflowId)) { Object.assign(findOptions.where!, { workflowId }); } @@ -1481,7 +1482,7 @@ class App { const returnData: IExecutionsSummary[] = []; - const filter = req.query.filter ? JSON.parse(req.query.filter) : {}; + const filter = req.query.filter ? jsonParse(req.query.filter) : {}; const sharedWorkflowIds = await getSharedWorkflowIds(req.user).then((ids) => ids.map((id) => id.toString()), diff --git a/packages/cli/src/api/executions.api.ts b/packages/cli/src/api/executions.api.ts index 1e9d381e17..ebcac6bce9 100644 --- a/packages/cli/src/api/executions.api.ts +++ b/packages/cli/src/api/executions.api.ts @@ -11,7 +11,7 @@ import express from 'express'; import _, { cloneDeep } from 'lodash'; import { BinaryDataManager } from 'n8n-core'; -import { IDataObject, IWorkflowBase, LoggerProxy, Workflow } from 'n8n-workflow'; +import { IDataObject, IWorkflowBase, LoggerProxy, jsonParse, Workflow } from 'n8n-workflow'; import { FindManyOptions, In, IsNull, LessThanOrEqual, Not, Raw } from 'typeorm'; import { @@ -111,7 +111,7 @@ async function getExecutionsCount( executionsController.get( '/', ResponseHelper.send(async (req: ExecutionRequest.GetAll): Promise => { - const filter = req.query.filter ? JSON.parse(req.query.filter) : {}; + const filter = req.query.filter ? jsonParse(req.query.filter) : {}; const limit = req.query.limit ? parseInt(req.query.limit, 10) diff --git a/packages/cli/src/databases/utils/transformers.ts b/packages/cli/src/databases/utils/transformers.ts index af6e47ab9b..c890e16644 100644 --- a/packages/cli/src/databases/utils/transformers.ts +++ b/packages/cli/src/databases/utils/transformers.ts @@ -1,3 +1,4 @@ +import { jsonParse } from 'n8n-workflow'; import { ValueTransformer } from 'typeorm'; import config from '../../../config'; @@ -16,8 +17,7 @@ export const lowerCaser = { */ export const objectRetriever: ValueTransformer = { to: (value: object): object => value, - from: (value: string | object): object => - typeof value === 'string' ? (JSON.parse(value) as object) : value, + from: (value: string | object): object => (typeof value === 'string' ? jsonParse(value) : value), }; /** @@ -27,8 +27,7 @@ export const objectRetriever: ValueTransformer = { const jsonColumn: ValueTransformer = { to: (value: object): string | object => config.getEnv('database.type') === 'sqlite' ? JSON.stringify(value) : value, - from: (value: string | object): object => - typeof value === 'string' ? (JSON.parse(value) as object) : value, + from: (value: string | object): object => (typeof value === 'string' ? jsonParse(value) : value), }; export const sqlite = { jsonColumn }; diff --git a/packages/editor-ui/src/components/NodeDetailsView.vue b/packages/editor-ui/src/components/NodeDetailsView.vue index 22d372738e..6434720cd9 100644 --- a/packages/editor-ui/src/components/NodeDetailsView.vue +++ b/packages/editor-ui/src/components/NodeDetailsView.vue @@ -112,6 +112,7 @@ import { IRunData, IRunExecutionData, Workflow, + jsonParse, } from 'n8n-workflow'; import { IExecutionResponse, INodeUi, IUpdateInformation, TargetItem } from '../Interface'; @@ -554,7 +555,7 @@ export default mixins( return; } - this.$store.commit('pinData', { node: this.activeNode, data: JSON.parse(value) }); + this.$store.commit('pinData', { node: this.activeNode, data: jsonParse(value) }); } this.$store.commit('ui/setOutputPanelEditModeEnabled', false); diff --git a/packages/editor-ui/src/components/helpers.ts b/packages/editor-ui/src/components/helpers.ts index 8f93d1282d..773ee724ac 100644 --- a/packages/editor-ui/src/components/helpers.ts +++ b/packages/editor-ui/src/components/helpers.ts @@ -2,7 +2,7 @@ import { CORE_NODES_CATEGORY, ERROR_TRIGGER_NODE_TYPE, MAPPING_PARAMS, TEMPLATES import { INodeUi, ITemplatesNode } from '@/Interface'; import { isResourceLocatorValue } from '@/typeGuards'; import dateformat from 'dateformat'; -import {IDataObject, INodeProperties, INodeTypeDescription, NodeParameterValueType,INodeExecutionData} from 'n8n-workflow'; +import {IDataObject, INodeProperties, INodeTypeDescription, NodeParameterValueType,INodeExecutionData, parseJson} from 'n8n-workflow'; import { isJsonKeyObject } from "@/utils"; const CRED_KEYWORDS_TO_FILTER = ['API', 'OAuth1', 'OAuth2']; @@ -169,7 +169,7 @@ export const convertPath = (path: string): string => { }; export const clearJsonKey = (userInput: string | object) => { - const parsedUserInput = typeof userInput === 'string' ? JSON.parse(userInput) : userInput; + const parsedUserInput = typeof userInput === 'string' ? jsonParse(userInput) : userInput; if (!Array.isArray(parsedUserInput)) return parsedUserInput; diff --git a/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts b/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts index c9b6cccc2b..b941471c2b 100644 --- a/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts +++ b/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts @@ -5,6 +5,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + jsonParse, NodeOperationError, } from 'n8n-workflow'; @@ -149,7 +150,7 @@ export class AgileCrm implements INodeType { } else if (filterType === 'json') { const filterJsonRules = this.getNodeParameter('filterJson', i) as string; if (validateJSON(filterJsonRules) !== undefined) { - Object.assign(filterJson, JSON.parse(filterJsonRules) as IFilter); + Object.assign(filterJson, jsonParse(filterJsonRules) as IFilter); } else { throw new NodeOperationError(this.getNode(), 'Filter (JSON) must be a valid json', { itemIndex: i, @@ -203,7 +204,7 @@ export class AgileCrm implements INodeType { if (additionalFieldsJson !== '') { if (validateJSON(additionalFieldsJson) !== undefined) { - Object.assign(body, JSON.parse(additionalFieldsJson)); + Object.assign(body, jsonParse(additionalFieldsJson)); } else { throw new NodeOperationError( this.getNode(), @@ -355,7 +356,7 @@ export class AgileCrm implements INodeType { if (additionalFieldsJson !== '') { if (validateJSON(additionalFieldsJson) !== undefined) { - Object.assign(body, JSON.parse(additionalFieldsJson)); + Object.assign(body, jsonParse(additionalFieldsJson)); } else { throw new NodeOperationError( this.getNode(), @@ -533,7 +534,7 @@ export class AgileCrm implements INodeType { if (additionalFieldsJson !== '') { if (validateJSON(additionalFieldsJson) !== undefined) { - Object.assign(body, JSON.parse(additionalFieldsJson)); + Object.assign(body, jsonParse(additionalFieldsJson)); } else { throw new NodeOperationError( this.getNode(), @@ -573,7 +574,7 @@ export class AgileCrm implements INodeType { if (additionalFieldsJson !== '') { if (validateJSON(additionalFieldsJson) !== undefined) { - Object.assign(body, JSON.parse(additionalFieldsJson)); + Object.assign(body, jsonParse(additionalFieldsJson)); } else { throw new NodeOperationError( this.getNode(), diff --git a/packages/nodes-base/nodes/Amqp/AmqpTrigger.node.ts b/packages/nodes-base/nodes/Amqp/AmqpTrigger.node.ts index 999e0c6577..6488fe4abe 100644 --- a/packages/nodes-base/nodes/Amqp/AmqpTrigger.node.ts +++ b/packages/nodes-base/nodes/Amqp/AmqpTrigger.node.ts @@ -7,6 +7,7 @@ import { INodeType, INodeTypeDescription, ITriggerResponse, + jsonParse, NodeOperationError, } from 'n8n-workflow'; @@ -190,7 +191,7 @@ export class AmqpTrigger implements INodeType { } if (options.jsonParseBody === true) { - data.body = JSON.parse(data.body); + data.body = jsonParse(data.body); } if (options.onlyBody === true) { data = data.body; diff --git a/packages/nodes-base/nodes/Aws/AwsSnsTrigger.node.ts b/packages/nodes-base/nodes/Aws/AwsSnsTrigger.node.ts index 7c4d803055..629e85340f 100644 --- a/packages/nodes-base/nodes/Aws/AwsSnsTrigger.node.ts +++ b/packages/nodes-base/nodes/Aws/AwsSnsTrigger.node.ts @@ -178,7 +178,7 @@ export class AwsSnsTrigger implements INodeType { const topic = this.getNodeParameter('topic') as string; // @ts-ignore - const body = JSON.parse(req.rawBody.toString()); + const body = jsonParse(req.rawBody.toString()); if (body.Type === 'SubscriptionConfirmation' && body.TopicArn === topic) { const { Token } = body; diff --git a/packages/nodes-base/nodes/Aws/CertificateManager/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/CertificateManager/GenericFunctions.ts index e9beb709ef..aa11f10e54 100644 --- a/packages/nodes-base/nodes/Aws/CertificateManager/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/CertificateManager/GenericFunctions.ts @@ -7,7 +7,7 @@ import { IWebhookFunctions, } from 'n8n-core'; -import { IDataObject, IHttpRequestOptions, NodeApiError } from 'n8n-workflow'; +import { IDataObject, IHttpRequestOptions, jsonParse, NodeApiError } from 'n8n-workflow'; export async function awsApiRequest( this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, @@ -18,7 +18,7 @@ export async function awsApiRequest( query: IDataObject = {}, headers?: object, // tslint:disable-next-line:no-any - ): Promise { +): Promise { const credentials = await this.getCredentials('aws'); const requestOptions = { @@ -50,7 +50,7 @@ export async function awsApiRequestREST( query: IDataObject = {}, headers?: object, // tslint:disable-next-line:no-any - ): Promise { +): Promise { const response = await awsApiRequest.call(this, service, method, path, body, query, headers); try { return JSON.parse(response); @@ -70,23 +70,17 @@ export async function awsApiRequestAllItems( headers: IDataObject = {}, // tslint:disable-next-line:no-any ): Promise { - const returnData: IDataObject[] = []; let responseData; do { - responseData = await awsApiRequestREST.call( - this, - service, - method, - path, - body, - query, - headers, - ); + responseData = await awsApiRequestREST.call(this, service, method, path, body, query, headers); if (responseData.NextToken) { - const data = JSON.parse(body as string); + // tslint:disable-next-line:no-any + const data = jsonParse(body as string, { + errorMessage: 'Response body is not valid JSON', + }); data['NextToken'] = responseData.NextToken; } returnData.push.apply(returnData, get(responseData, propertyName)); diff --git a/packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts b/packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts index 51c0d5d199..1201105447 100644 --- a/packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts +++ b/packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts @@ -1,5 +1,5 @@ import { IExecuteFunctions, IExecuteSingleFunctions, ILoadOptionsFunctions } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { IDataObject, jsonParse } from 'n8n-workflow'; import { ICollection } from './CollectionInterface'; import { cockpitApiRequest } from './GenericFunctions'; @@ -46,7 +46,9 @@ export async function getAllCollectionEntries( } if (options.filter) { - body.filter = JSON.parse(options.filter.toString()); + body.filter = jsonParse(options.filter.toString(), { + errorMessage: "'Filter' option is not valid JSON", + }); } if (options.limit) { @@ -58,7 +60,9 @@ export async function getAllCollectionEntries( } if (options.sort) { - body.sort = JSON.parse(options.sort.toString()); + body.sort = jsonParse(options.sort.toString(), { + errorMessage: "'Sort' option is not valid JSON", + }); } if (options.populate) { diff --git a/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts b/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts index 2de2cffe3f..acd3582787 100644 --- a/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts @@ -1,5 +1,5 @@ import { IExecuteFunctions, IExecuteSingleFunctions, ILoadOptionsFunctions } from 'n8n-core'; -import { IDataObject, NodeApiError, NodeOperationError } from 'n8n-workflow'; +import { IDataObject, jsonParse, NodeApiError, NodeOperationError } from 'n8n-workflow'; import { OptionsWithUri } from 'request'; export async function cockpitApiRequest( @@ -48,7 +48,7 @@ export function createDataFromParameters( if (dataFieldsAreJson) { // Parameters are defined as JSON - return JSON.parse(this.getNodeParameter('dataFieldsJson', itemIndex, '{}') as string); + return jsonParse(this.getNodeParameter('dataFieldsJson', itemIndex, '{}') as string); } // Parameters are defined in UI diff --git a/packages/nodes-base/nodes/Discord/Discord.node.ts b/packages/nodes-base/nodes/Discord/Discord.node.ts index d540feedbb..5a7e199fd2 100644 --- a/packages/nodes-base/nodes/Discord/Discord.node.ts +++ b/packages/nodes-base/nodes/Discord/Discord.node.ts @@ -4,6 +4,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + jsonParse, NodeApiError, NodeOperationError, } from 'n8n-workflow'; @@ -171,7 +172,7 @@ export class Discord implements INodeType { if (options.allowed_mentions) { //@ts-expect-error - body.allowed_mentions = JSON.parse(options.allowed_mentions); + body.allowed_mentions = jsonParse(options.allowed_mentions); } if (options.avatarUrl) { @@ -188,12 +189,12 @@ export class Discord implements INodeType { if (options.payloadJson) { //@ts-expect-error - body.payload_json = JSON.parse(options.payloadJson); + body.payload_json = jsonParse(options.payloadJson); } if (options.attachments) { //@ts-expect-error - body.attachments = JSON.parse(options.attachments as DiscordAttachment[]); + body.attachments = jsonParse(options.attachments as DiscordAttachment[]); } //* Not used props, delete them from the payload as Discord won't need them :^ @@ -270,7 +271,7 @@ export class Discord implements INodeType { } const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray({success: true}), + this.helpers.returnJsonArray({ success: true }), { itemData: { item: i } }, ); returnData.push(...executionData); diff --git a/packages/nodes-base/nodes/Elastic/Elasticsearch/Elasticsearch.node.ts b/packages/nodes-base/nodes/Elastic/Elasticsearch/Elasticsearch.node.ts index 0c002fb08e..9c8da3fbd0 100644 --- a/packages/nodes-base/nodes/Elastic/Elasticsearch/Elasticsearch.node.ts +++ b/packages/nodes-base/nodes/Elastic/Elasticsearch/Elasticsearch.node.ts @@ -1,6 +1,12 @@ import { IExecuteFunctions } from 'n8n-core'; -import { IDataObject, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow'; +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, + jsonParse, +} from 'n8n-workflow'; import { elasticsearchApiRequest, elasticsearchApiRequestAllItems } from './GenericFunctions'; @@ -127,7 +133,12 @@ export class Elasticsearch implements INodeType { if (Object.keys(options).length) { const { query, ...rest } = options; - if (query) Object.assign(body, JSON.parse(query)); + if (query) { + Object.assign( + body, + jsonParse(query, { errorMessage: "Invalid JSON in 'Query' option" }), + ); + } Object.assign(qs, rest); qs._source = true; } diff --git a/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/GoogleFirebaseCloudFirestore.node.ts b/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/GoogleFirebaseCloudFirestore.node.ts index 078da3bb92..c2a23d497d 100644 --- a/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/GoogleFirebaseCloudFirestore.node.ts +++ b/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/GoogleFirebaseCloudFirestore.node.ts @@ -7,6 +7,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + jsonParse, } from 'n8n-workflow'; import { @@ -338,7 +339,7 @@ export class GoogleFirebaseCloudFirestore implements INodeType { this, 'POST', `/${projectId}/databases/${database}/documents:runQuery`, - JSON.parse(query), + jsonParse(query), ); responseData = responseData.map( diff --git a/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts b/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts index 8067495f96..1a5c286ff3 100644 --- a/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts +++ b/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts @@ -6,6 +6,7 @@ import { INodeType, INodeTypeBaseDescription, INodeTypeDescription, + jsonParse, NodeApiError, NodeOperationError, } from 'n8n-workflow'; @@ -951,7 +952,11 @@ export class HttpRequestV3 implements INodeType { itemIndex, [], ) as [{ name: string; value: string }]; - const specifyHeaders = this.getNodeParameter('specifyHeaders', itemIndex, 'keypair') as string; + const specifyHeaders = this.getNodeParameter( + 'specifyHeaders', + itemIndex, + 'keypair', + ) as string; const jsonHeadersParameter = this.getNodeParameter('jsonHeaders', itemIndex, '') as string; const { @@ -1097,7 +1102,7 @@ export class HttpRequestV3 implements INodeType { ); } - requestOptions.body = JSON.parse(jsonBodyParameter); + requestOptions.body = jsonParse(jsonBodyParameter); } else if (specifyBody === 'string') { //form urlencoded requestOptions.body = Object.fromEntries(new URLSearchParams(body)); @@ -1147,7 +1152,7 @@ export class HttpRequestV3 implements INodeType { ); } - requestOptions.qs = JSON.parse(jsonQueryParameter); + requestOptions.qs = jsonParse(jsonQueryParameter); } } @@ -1172,7 +1177,7 @@ export class HttpRequestV3 implements INodeType { ); } - requestOptions.headers = JSON.parse(jsonHeadersParameter); + requestOptions.headers = jsonParse(jsonHeadersParameter); } } @@ -1323,7 +1328,7 @@ export class HttpRequestV3 implements INodeType { const responseContentType = response.headers['content-type'] ?? ''; if (responseContentType.includes('application/json')) { responseFormat = 'json'; - response.body = JSON.parse(Buffer.from(response.body).toString()); + response.body = jsonParse(Buffer.from(response.body).toString()); } else if (binaryContentTypes.some((e) => responseContentType.includes(e))) { responseFormat = 'file'; } else { diff --git a/packages/nodes-base/nodes/JotForm/JotFormTrigger.node.ts b/packages/nodes-base/nodes/JotForm/JotFormTrigger.node.ts index f71df59222..068c465792 100644 --- a/packages/nodes-base/nodes/JotForm/JotFormTrigger.node.ts +++ b/packages/nodes-base/nodes/JotForm/JotFormTrigger.node.ts @@ -9,6 +9,7 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + jsonParse, } from 'n8n-workflow'; import { jotformApiRequest } from './GenericFunctions'; @@ -167,7 +168,8 @@ export class JotFormTrigger implements INodeType { return new Promise((resolve, reject) => { form.parse(req, async (err, data, files) => { - const rawRequest = JSON.parse(data.rawRequest as string); + // tslint:disable-next-line:no-any + const rawRequest = jsonParse(data.rawRequest as string); data.rawRequest = rawRequest; let returnData: IDataObject; diff --git a/packages/nodes-base/nodes/Metabase/QuestionsDescription.ts b/packages/nodes-base/nodes/Metabase/QuestionsDescription.ts index 2717b6ac0b..14a9c491de 100644 --- a/packages/nodes-base/nodes/Metabase/QuestionsDescription.ts +++ b/packages/nodes-base/nodes/Metabase/QuestionsDescription.ts @@ -4,6 +4,7 @@ import { IN8nHttpFullResponse, INodeExecutionData, INodeProperties, + jsonParse, } from 'n8n-workflow'; export const questionsOperations: INodeProperties[] = [ @@ -74,9 +75,7 @@ export const questionsOperations: INodeProperties[] = [ } items[i] = newItem; if (this.getNode().parameters.format === 'json') { - items[i].json = JSON.parse( - items[i].json as unknown as string, - )[0] as unknown as IDataObject; + items[i].json = jsonParse(items[i].json as unknown as string)[0]; console.log(items[i].json); delete items[i].binary; } else { diff --git a/packages/nodes-base/nodes/MoveBinaryData/MoveBinaryData.node.ts b/packages/nodes-base/nodes/MoveBinaryData/MoveBinaryData.node.ts index e3b2c106a4..ec58033557 100644 --- a/packages/nodes-base/nodes/MoveBinaryData/MoveBinaryData.node.ts +++ b/packages/nodes-base/nodes/MoveBinaryData/MoveBinaryData.node.ts @@ -11,6 +11,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + jsonParse, NodeOperationError, } from 'n8n-workflow'; @@ -362,7 +363,7 @@ export class MoveBinaryData implements INodeType { convertedValue = iconv.decode(buffer, encoding, { stripBOM: options.stripBOM as boolean, }); - newItem.json = JSON.parse(convertedValue); + newItem.json = jsonParse(convertedValue); } else { // Does get added to existing data so copy it first newItem.json = deepCopy(item.json); @@ -376,7 +377,7 @@ export class MoveBinaryData implements INodeType { } if (options.jsonParse) { - convertedValue = JSON.parse(convertedValue); + convertedValue = jsonParse(convertedValue); } const destinationKey = this.getNodeParameter('destinationKey', itemIndex, '') as string; diff --git a/packages/nodes-base/nodes/NocoDB/GenericFunctions.ts b/packages/nodes-base/nodes/NocoDB/GenericFunctions.ts index 79df16ca71..5f286b4a78 100644 --- a/packages/nodes-base/nodes/NocoDB/GenericFunctions.ts +++ b/packages/nodes-base/nodes/NocoDB/GenericFunctions.ts @@ -7,6 +7,7 @@ import { IDataObject, INodeExecutionData, IPollFunctions, + jsonParse, NodeApiError, NodeOperationError, } from 'n8n-workflow'; @@ -115,7 +116,7 @@ export async function downloadRecordAttachments( for (const fieldName of fieldNames) { if (record[fieldName]) { for (const [index, attachment] of ( - JSON.parse(record[fieldName] as string) as IAttachment[] + jsonParse(record[fieldName] as string) as IAttachment[] ).entries()) { const file = await apiRequest.call(this, 'GET', '', {}, {}, attachment.url, { json: false, diff --git a/packages/nodes-base/nodes/Notion/v2/NotionV2.node.ts b/packages/nodes-base/nodes/Notion/v2/NotionV2.node.ts index 5e1c3d60b3..745c1f55a7 100644 --- a/packages/nodes-base/nodes/Notion/v2/NotionV2.node.ts +++ b/packages/nodes-base/nodes/Notion/v2/NotionV2.node.ts @@ -12,6 +12,7 @@ import { INodeType, INodeTypeBaseDescription, INodeTypeDescription, + jsonParse, NodeApiError, } from 'n8n-workflow'; @@ -528,7 +529,7 @@ export class NotionV2 implements INodeType { } else if (filterType === 'json') { const filterJson = this.getNodeParameter('filterJson', i) as string; if (validateJSON(filterJson) !== undefined) { - body.filter = JSON.parse(filterJson); + body.filter = jsonParse(filterJson); } else { throw new NodeApiError(this.getNode(), { message: 'Filters (JSON) must be a valid json', diff --git a/packages/nodes-base/nodes/RenameKeys/RenameKeys.node.ts b/packages/nodes-base/nodes/RenameKeys/RenameKeys.node.ts index 823706953f..152ade565e 100644 --- a/packages/nodes-base/nodes/RenameKeys/RenameKeys.node.ts +++ b/packages/nodes-base/nodes/RenameKeys/RenameKeys.node.ts @@ -8,7 +8,6 @@ import { } from 'n8n-workflow'; import { get, set, unset } from 'lodash'; -import { options } from 'rhea'; interface IRenameKey { currentKey: string; diff --git a/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts b/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts index 49b4edd0bc..e7a75158c8 100644 --- a/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts +++ b/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + jsonParse, NodeOperationError, } from 'n8n-workflow'; @@ -206,7 +207,9 @@ export class RespondToWebhook implements INodeType { if (respondWith === 'json') { const responseBodyParameter = this.getNodeParameter('responseBody', 0) as string; if (responseBodyParameter) { - responseBody = JSON.parse(responseBodyParameter); + responseBody = jsonParse(responseBodyParameter, { + errorMessage: "Invalid JSON in 'Response Body' field", + }); } } else if (respondWith === 'firstIncomingItem') { responseBody = items[0].json; diff --git a/packages/nodes-base/nodes/SendInBlue/GenericFunctions.ts b/packages/nodes-base/nodes/SendInBlue/GenericFunctions.ts index 9d40c5bf99..c73dc28472 100644 --- a/packages/nodes-base/nodes/SendInBlue/GenericFunctions.ts +++ b/packages/nodes-base/nodes/SendInBlue/GenericFunctions.ts @@ -4,6 +4,7 @@ import { IHttpRequestOptions, IWebhookFunctions, JsonObject, + jsonParse, NodeOperationError, } from 'n8n-workflow'; import { OptionsWithUri } from 'request'; @@ -337,7 +338,7 @@ export namespace SendInBlueWebhookApi { options, )) as string; - return JSON.parse(webhooks) as Webhooks; + return jsonParse(webhooks) as Webhooks; }; export const createWebHook = async ( @@ -367,7 +368,7 @@ export namespace SendInBlueWebhookApi { options, ); - return JSON.parse(webhookId) as WebhookId; + return jsonParse(webhookId) as WebhookId; }; export const deleteWebhook = async (ref: IHookFunctions, webhookId: string) => { diff --git a/packages/nodes-base/nodes/SseTrigger/SseTrigger.node.ts b/packages/nodes-base/nodes/SseTrigger/SseTrigger.node.ts index c8995d89ef..8478bc7c0e 100644 --- a/packages/nodes-base/nodes/SseTrigger/SseTrigger.node.ts +++ b/packages/nodes-base/nodes/SseTrigger/SseTrigger.node.ts @@ -1,6 +1,6 @@ import EventSource from 'eventsource'; import { ITriggerFunctions } from 'n8n-core'; -import { INodeType, INodeTypeDescription, ITriggerResponse } from 'n8n-workflow'; +import { INodeType, INodeTypeDescription, ITriggerResponse, jsonParse } from 'n8n-workflow'; export class SseTrigger implements INodeType { description: INodeTypeDescription = { @@ -37,7 +37,8 @@ export class SseTrigger implements INodeType { const eventSource = new EventSource(url); eventSource.onmessage = (event) => { - const eventData = JSON.parse(event.data); + // tslint:disable-next-line:no-any + const eventData = jsonParse(event.data, { errorMessage: 'Invalid JSON for event data' }); this.emit([this.helpers.returnJsonArray([eventData])]); }; diff --git a/packages/nodes-base/nodes/StopAndError/StopAndError.node.ts b/packages/nodes-base/nodes/StopAndError/StopAndError.node.ts index 68cfbb1acf..e04d2d0b09 100644 --- a/packages/nodes-base/nodes/StopAndError/StopAndError.node.ts +++ b/packages/nodes-base/nodes/StopAndError/StopAndError.node.ts @@ -4,6 +4,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + jsonParse, NodeOperationError, } from 'n8n-workflow'; @@ -91,7 +92,8 @@ export class StopAndError implements INodeType { toThrow = this.getNodeParameter('errorMessage', 0) as string; } else { const json = this.getNodeParameter('errorObject', 0) as string; - const errorObject = JSON.parse(json); + // tslint:disable-next-line:no-any + const errorObject = jsonParse(json); toThrow = { name: 'User-thrown error', diff --git a/packages/nodes-base/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.ts b/packages/nodes-base/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.ts index 63321a6ac6..0d4b7d63f7 100644 --- a/packages/nodes-base/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.ts +++ b/packages/nodes-base/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.ts @@ -9,6 +9,7 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + jsonParse, NodeApiError, NodeOperationError, } from 'n8n-workflow'; @@ -514,7 +515,8 @@ export class SurveyMonkeyTrigger implements INodeType { return {}; } - let responseData = JSON.parse(data.join('')); + // tslint:disable-next-line:no-any + let responseData = jsonParse(data.join('')); let endpoint = ''; let returnItem: INodeExecutionData[] = [ diff --git a/packages/nodes-base/nodes/TheHive/GenericFunctions.ts b/packages/nodes-base/nodes/TheHive/GenericFunctions.ts index 406206d9a8..c769000dd3 100644 --- a/packages/nodes-base/nodes/TheHive/GenericFunctions.ts +++ b/packages/nodes-base/nodes/TheHive/GenericFunctions.ts @@ -2,7 +2,7 @@ import { OptionsWithUri } from 'request'; import { IExecuteFunctions, IHookFunctions, ILoadOptionsFunctions } from 'n8n-core'; -import { IDataObject, NodeApiError, NodeOperationError } from 'n8n-workflow'; +import { IDataObject, jsonParse, NodeApiError, NodeOperationError } from 'n8n-workflow'; import moment from 'moment'; import { Eq } from './QueryFunctions'; @@ -79,7 +79,7 @@ export function prepareOptional(optionals: IDataObject): IDataObject { response[key] = Date.parse(optionals[key] as string); } else if (key === 'artifacts') { try { - response[key] = JSON.parse(optionals[key] as string); + response[key] = jsonParse(optionals[key] as string); } catch (error) { throw new Error('Invalid JSON for artifacts'); } @@ -107,7 +107,7 @@ export async function prepareCustomFields( if (typeof customFieldsJson === 'string') { try { - customFieldsJson = JSON.parse(customFieldsJson); + customFieldsJson = jsonParse(customFieldsJson); } catch (error) { throw new Error('Invalid JSON for customFields'); } diff --git a/packages/nodes-base/nodes/Todoist/OperationHandler.ts b/packages/nodes-base/nodes/Todoist/OperationHandler.ts index 206e704ea8..367272a192 100644 --- a/packages/nodes-base/nodes/Todoist/OperationHandler.ts +++ b/packages/nodes-base/nodes/Todoist/OperationHandler.ts @@ -1,4 +1,4 @@ -import { IDataObject } from 'n8n-workflow'; +import { IDataObject, jsonParse } from 'n8n-workflow'; import { Context, FormatDueDatetime, @@ -228,7 +228,7 @@ export class SyncHandler implements OperationHandler { const commandsJson = ctx.getNodeParameter('commands', itemIndex) as string; const projectId = ctx.getNodeParameter('project', itemIndex) as number; const sections = await getSectionIds(ctx, projectId); - const commands: Command[] = JSON.parse(commandsJson); + const commands: Command[] = jsonParse(commandsJson); const tempIdMapping = new Map(); for (let i = 0; i < commands.length; i++) { diff --git a/packages/nodes-base/nodes/Wufoo/WufooTrigger.node.ts b/packages/nodes-base/nodes/Wufoo/WufooTrigger.node.ts index a5ae15acf7..e0f15e6bbe 100644 --- a/packages/nodes-base/nodes/Wufoo/WufooTrigger.node.ts +++ b/packages/nodes-base/nodes/Wufoo/WufooTrigger.node.ts @@ -7,6 +7,7 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + jsonParse, } from 'n8n-workflow'; import { wufooApiRequest } from './GenericFunctions'; @@ -153,7 +154,10 @@ export class WufooTrigger implements INodeType { return {}; } - const fieldsObject = JSON.parse(req.body.FieldStructure); + // tslint:disable-next-line:no-any + const fieldsObject = jsonParse(req.body.FieldStructure, { + errorMessage: "Invalid JSON in request body field 'FieldStructure'", + }); fieldsObject.Fields.map((field: IField) => { // TODO @@ -206,8 +210,12 @@ export class WufooTrigger implements INodeType { entryId: req.body.EntryId as number, dateCreated: req.body.DateCreated as Date, formId: req.body.FormId as string, - formStructure: JSON.parse(req.body.FormStructure), - fieldStructure: JSON.parse(req.body.FieldStructure), + formStructure: jsonParse(req.body.FormStructure, { + errorMessage: "Invalid JSON in request body field 'FormStructure'", + }), + fieldStructure: jsonParse(req.body.FieldStructure, { + errorMessage: "Invalid JSON in request body field 'FieldStructure'", + }), entries, }; diff --git a/packages/workflow/src/index.ts b/packages/workflow/src/index.ts index 49dbae5957..02a858a2f8 100644 --- a/packages/workflow/src/index.ts +++ b/packages/workflow/src/index.ts @@ -16,4 +16,4 @@ export * from './WorkflowDataProxy'; export * from './WorkflowErrors'; export * from './WorkflowHooks'; export { LoggerProxy, NodeHelpers, ObservableObject, TelemetryHelpers }; -export { deepCopy } from './utils'; +export { deepCopy, jsonParse } from './utils'; diff --git a/packages/workflow/src/utils.ts b/packages/workflow/src/utils.ts index fa79ce4110..400cfc3523 100644 --- a/packages/workflow/src/utils.ts +++ b/packages/workflow/src/utils.ts @@ -30,3 +30,22 @@ export const deepCopy = (source: T): T => { return clone; }; // eslint-enable +type ErrorMessage = { errorMessage: string }; +type FallbackValue = { fallbackValue: T }; + +export const jsonParse = ( + jsonString: string, + options: ErrorMessage | FallbackValue | {} = {}, +): T => { + try { + return JSON.parse(jsonString) as T; + } catch (error) { + if ('fallbackValue' in options) { + return options.fallbackValue; + } + if ('errorMessage' in options) { + throw new Error(options.errorMessage); + } + throw error; + } +};