mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -08:00
refactor(core): Separate API response from error in execution error causes (no-changelog) (#7880)
Store the third-party API response error separately from the error stored as `cause` Follow-up to: https://github.com/n8n-io/n8n/pull/7820#discussion_r1406009154
This commit is contained in:
parent
b024cc52e7
commit
e0b7f89035
|
@ -196,7 +196,12 @@ export class MailchimpTrigger implements INodeType {
|
|||
try {
|
||||
await mailchimpApiRequest.call(this, endpoint, 'GET');
|
||||
} catch (error) {
|
||||
if (error instanceof NodeApiError && error.cause && 'isAxiosError' in error.cause) {
|
||||
if (
|
||||
error instanceof NodeApiError &&
|
||||
error.cause &&
|
||||
'isAxiosError' in error.cause &&
|
||||
'statusCode' in error.cause
|
||||
) {
|
||||
if (error.cause.statusCode === 404) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import type {
|
|||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
JsonObject,
|
||||
} from 'n8n-workflow';
|
||||
import { jsonParse, NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
|
@ -81,14 +82,14 @@ export class StopAndError implements INodeType {
|
|||
const errorType = this.getNodeParameter('errorType', 0) as 'errorMessage' | 'errorObject';
|
||||
const { id: workflowId, name: workflowName } = this.getWorkflow();
|
||||
|
||||
let toThrow: string | { name: string; message: string; [otherKey: string]: unknown };
|
||||
let toThrow: string | JsonObject;
|
||||
|
||||
if (errorType === 'errorMessage') {
|
||||
toThrow = this.getNodeParameter('errorMessage', 0) as string;
|
||||
} else {
|
||||
const json = this.getNodeParameter('errorObject', 0) as string;
|
||||
|
||||
const errorObject = jsonParse<any>(json);
|
||||
const errorObject = jsonParse<JsonObject>(json);
|
||||
|
||||
toThrow = {
|
||||
name: 'User-thrown error',
|
||||
|
|
|
@ -88,7 +88,7 @@ describe('Execute Stop and Error Node', () => {
|
|||
const stopAndError1RunData = result.data.resultData.runData['Stop and Error1'];
|
||||
const stopAndError1Object = (
|
||||
(stopAndError1RunData as unknown as IDataObject[])[0].error as IDataObject
|
||||
).cause;
|
||||
).errorResponse;
|
||||
|
||||
expect(stopAndError1Object).toEqual({
|
||||
code: 404,
|
||||
|
|
|
@ -2,16 +2,16 @@ import type { Functionality, IDataObject, JsonObject, Severity } from '../../Int
|
|||
import { ApplicationError } from '../application.error';
|
||||
|
||||
interface ExecutionBaseErrorOptions {
|
||||
cause?: Error | JsonObject;
|
||||
cause?: Error;
|
||||
errorResponse?: JsonObject;
|
||||
}
|
||||
|
||||
export abstract class ExecutionBaseError extends ApplicationError {
|
||||
description: string | null | undefined;
|
||||
|
||||
/**
|
||||
* @tech_debt Ensure `cause` can only be `Error` or `undefined`
|
||||
*/
|
||||
cause: Error | JsonObject | undefined;
|
||||
cause?: Error;
|
||||
|
||||
errorResponse?: JsonObject;
|
||||
|
||||
timestamp: number;
|
||||
|
||||
|
@ -23,7 +23,7 @@ export abstract class ExecutionBaseError extends ApplicationError {
|
|||
|
||||
functionality: Functionality = 'regular';
|
||||
|
||||
constructor(message: string, { cause }: ExecutionBaseErrorOptions) {
|
||||
constructor(message: string, { cause, errorResponse }: ExecutionBaseErrorOptions = {}) {
|
||||
const options = cause instanceof Error ? { cause } : {};
|
||||
super(message, options);
|
||||
|
||||
|
@ -35,6 +35,8 @@ export abstract class ExecutionBaseError extends ApplicationError {
|
|||
} else if (cause && !(cause instanceof Error)) {
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
if (errorResponse) this.errorResponse = errorResponse;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
|
|
@ -38,8 +38,12 @@ export abstract class NodeError extends ExecutionBaseError {
|
|||
node: INode;
|
||||
|
||||
constructor(node: INode, error: Error | JsonObject) {
|
||||
const message = error instanceof Error ? error.message : '';
|
||||
super(message, { cause: error });
|
||||
if (error instanceof Error) {
|
||||
super(error.message, { cause: error });
|
||||
} else {
|
||||
super('', { errorResponse: error });
|
||||
}
|
||||
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ export class NodeApiError extends NodeError {
|
|||
|
||||
constructor(
|
||||
node: INode,
|
||||
error: JsonObject,
|
||||
errorResponse: JsonObject,
|
||||
{
|
||||
message,
|
||||
description,
|
||||
|
@ -125,38 +125,44 @@ export class NodeApiError extends NodeError {
|
|||
messageMapping,
|
||||
}: NodeApiErrorOptions = {},
|
||||
) {
|
||||
super(node, error);
|
||||
super(node, errorResponse);
|
||||
|
||||
// only for request library error
|
||||
if (error.error) {
|
||||
removeCircularRefs(error.error as JsonObject);
|
||||
if (errorResponse.error) {
|
||||
removeCircularRefs(errorResponse.error as JsonObject);
|
||||
}
|
||||
|
||||
// if not description provided, try to find it in the error object
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
if (!description && (error.description || (error?.reason as IDataObject)?.description)) {
|
||||
if (
|
||||
!description &&
|
||||
(errorResponse.description || (errorResponse?.reason as IDataObject)?.description)
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
this.description = (error.description ||
|
||||
(error?.reason as IDataObject)?.description) as string;
|
||||
this.description = (errorResponse.description ||
|
||||
(errorResponse?.reason as IDataObject)?.description) as string;
|
||||
}
|
||||
|
||||
// if not message provided, try to find it in the error object or set description as message
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
if (!message && (error.message || (error?.reason as IDataObject)?.message || description)) {
|
||||
if (
|
||||
!message &&
|
||||
(errorResponse.message || (errorResponse?.reason as IDataObject)?.message || description)
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
this.message = (error.message ||
|
||||
this.message = (errorResponse.message ||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
(error?.reason as IDataObject)?.message ||
|
||||
(errorResponse?.reason as IDataObject)?.message ||
|
||||
description) as string;
|
||||
}
|
||||
|
||||
// if it's an error generated by axios
|
||||
// look for descriptions in the response object
|
||||
if (error.reason) {
|
||||
const reason: IDataObject = error.reason as unknown as IDataObject;
|
||||
if (errorResponse.reason) {
|
||||
const reason: IDataObject = errorResponse.reason as unknown as IDataObject;
|
||||
|
||||
if (reason.isAxiosError && reason.response) {
|
||||
error = reason.response as JsonObject;
|
||||
errorResponse = reason.response as JsonObject;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,7 +171,7 @@ export class NodeApiError extends NodeError {
|
|||
this.httpCode = httpCode;
|
||||
} else {
|
||||
this.httpCode =
|
||||
this.findProperty(error, ERROR_STATUS_PROPERTIES, ERROR_NESTING_PROPERTIES) ?? null;
|
||||
this.findProperty(errorResponse, ERROR_STATUS_PROPERTIES, ERROR_NESTING_PROPERTIES) ?? null;
|
||||
}
|
||||
|
||||
if (severity) {
|
||||
|
@ -181,10 +187,10 @@ export class NodeApiError extends NodeError {
|
|||
|
||||
if (!this.description) {
|
||||
if (parseXml) {
|
||||
this.setDescriptionFromXml(error.error as string);
|
||||
this.setDescriptionFromXml(errorResponse.error as string);
|
||||
} else {
|
||||
this.description = this.findProperty(
|
||||
error,
|
||||
errorResponse,
|
||||
ERROR_MESSAGE_PROPERTIES,
|
||||
ERROR_NESTING_PROPERTIES,
|
||||
);
|
||||
|
@ -209,8 +215,8 @@ export class NodeApiError extends NodeError {
|
|||
this.description,
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
this.httpCode ||
|
||||
(error?.code as string) ||
|
||||
((error?.reason as JsonObject)?.code as string) ||
|
||||
(errorResponse?.code as string) ||
|
||||
((errorResponse?.reason as JsonObject)?.code as string) ||
|
||||
undefined,
|
||||
messageMapping,
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { INode } from '..';
|
||||
import type { INode, JsonObject } from '..';
|
||||
import type { NodeOperationErrorOptions } from './node-api.error';
|
||||
import { NodeError } from './abstract/node.error';
|
||||
|
||||
|
@ -8,7 +8,11 @@ import { NodeError } from './abstract/node.error';
|
|||
export class NodeOperationError extends NodeError {
|
||||
lineNumber: number | undefined;
|
||||
|
||||
constructor(node: INode, error: Error | string, options: NodeOperationErrorOptions = {}) {
|
||||
constructor(
|
||||
node: INode,
|
||||
error: Error | string | JsonObject,
|
||||
options: NodeOperationErrorOptions = {},
|
||||
) {
|
||||
if (typeof error === 'string') {
|
||||
error = new Error(error);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { WorkflowOperationError } from './workflow-operation.error';
|
|||
export class SubworkflowOperationError extends WorkflowOperationError {
|
||||
description = '';
|
||||
|
||||
cause: { message: string; stack: string };
|
||||
cause: Error;
|
||||
|
||||
constructor(message: string, description: string) {
|
||||
super(message);
|
||||
|
@ -11,6 +11,7 @@ export class SubworkflowOperationError extends WorkflowOperationError {
|
|||
this.description = description;
|
||||
|
||||
this.cause = {
|
||||
name: this.name,
|
||||
message,
|
||||
stack: this.stack as string,
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue