Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 42 additions & 31 deletions packages/nodes-base/nodes/GraphQL/GraphQL.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,46 @@ export class GraphQL implements INodeType {
} else {
response = await this.helpers.request(requestOptions);
}

// Parse string responses
if (typeof response === 'string' && responseFormat !== 'string') {
try {
response = JSON.parse(response);
} catch (error) {
throw new NodeOperationError(
this.getNode(),
'Response body is not valid JSON. Change "Response Format" to "String"',
{ itemIndex },
);
}
}

// Check for GraphQL errors BEFORE adding to returnItems
let parsedForErrorCheck = response;
if (typeof response === 'string') {
try {
parsedForErrorCheck = JSON.parse(response);
} catch {
// Not JSON, no errors to check
parsedForErrorCheck = response;
}
}

if (
typeof parsedForErrorCheck === 'object' &&
parsedForErrorCheck !== null &&
'errors' in parsedForErrorCheck
) {
const errors = parsedForErrorCheck.errors;
if (Array.isArray(errors) && errors.length > 0) {
const message =
errors.map((error: IDataObject) => error.message).join(', ') || 'Unexpected error';
const errorPayload: JsonObject = { errors };
throw new NodeApiError(this.getNode(), errorPayload, { message });
}
}

// Only add to returnItems if there are no GraphQL errors
if (responseFormat === 'string') {
const dataPropertyName = this.getNodeParameter('dataPropertyName', 0);
returnItems.push({
Expand All @@ -522,41 +562,12 @@ export class GraphQL implements INodeType {
},
});
} else {
if (typeof response === 'string') {
try {
response = JSON.parse(response);
} catch (error) {
throw new NodeOperationError(
this.getNode(),
'Response body is not valid JSON. Change "Response Format" to "String"',
{ itemIndex },
);
}
}

const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(response as IDataObject),
{ itemData: { item: itemIndex } },
);
returnItems.push(...executionData);
}

// parse error string messages
if (typeof response === 'string' && response.startsWith('{"errors":')) {
try {
const errorResponse = JSON.parse(response) as IDataObject;
if (Array.isArray(errorResponse.errors)) {
response = errorResponse;
}
} catch (e) {}
}
// throw from response object.errors[]
if (typeof response === 'object' && response.errors) {
const message =
response.errors?.map((error: IDataObject) => error.message).join(', ') ||
'Unexpected error';
throw new NodeApiError(this.getNode(), response.errors as JsonObject, { message });
}
} catch (error) {
if (!this.continueOnFail()) {
throw error;
Expand All @@ -565,10 +576,10 @@ export class GraphQL implements INodeType {
const errorData = this.helpers.returnJsonArray({
error: error.message,
});
const exectionErrorWithMetaData = this.helpers.constructExecutionMetaData(errorData, {
const executionErrorWithMetaData = this.helpers.constructExecutionMetaData(errorData, {
itemData: { item: itemIndex },
});
returnItems.push(...exectionErrorWithMetaData);
returnItems.push(...executionErrorWithMetaData);
}
}
return [returnItems];
Expand Down
45 changes: 45 additions & 0 deletions packages/nodes-base/nodes/GraphQL/test/GraphQL.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,49 @@ describe('GraphQL Node', () => {
credentials,
});
});

describe('error output routing with continueOnFail', () => {
const baseUrl = 'http://test.example.com';

// Mock the first request that returns a GraphQL error
nock(baseUrl)
.post('/graphql', '{"query":"INVALID_QUERY","variables":{},"operationName":null}')
.reply(200, {
errors: [
{
message: 'Syntax Error: Invalid query syntax',
extensions: {
code: 'GRAPHQL_PARSE_FAILED',
},
},
],
});

// Mock the second request that returns successful data
nock(baseUrl)
.post(
'/graphql',
'{"query":"query { users { id name email } }","variables":{},"operationName":null}',
)
.reply(200, {
data: {
users: [
{
id: '1',
name: 'John Doe',
email: '[email protected]',
},
{
id: '2',
name: 'Jane Smith',
email: '[email protected]',
},
],
},
});

new NodeTestHarness().setupTests({
workflowFiles: ['workflow.error_output_routing.json'],
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
{
"name": "Test GraphQL Error Output Routing",
"nodes": [
{
"parameters": {},
"id": "manual-trigger",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [0, 0]
},
{
"parameters": {
"jsCode": "return [\n // This query will fail with a GraphQL error\n {\n query: 'INVALID_QUERY'\n },\n // This query will succeed\n {\n query: 'query { users { id name email } }'\n }\n]"
},
"id": "code-node",
"name": "Generate Queries",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [200, 0]
},
{
"parameters": {
"endpoint": "http://test.example.com/graphql",
"query": "={{ $json.query }}"
},
"id": "graphql-node",
"name": "GraphQL",
"type": "n8n-nodes-base.graphql",
"typeVersion": 1.1,
"position": [400, 0],
"onError": "continueErrorOutput"
},
{
"parameters": {},
"id": "success-output",
"name": "Success Output",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [600, -100]
},
{
"parameters": {},
"id": "error-output",
"name": "Error Output",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [600, 100]
}
],
"connections": {
"Manual Trigger": {
"main": [
[
{
"node": "Generate Queries",
"type": "main",
"index": 0
}
]
]
},
"Generate Queries": {
"main": [
[
{
"node": "GraphQL",
"type": "main",
"index": 0
}
]
]
},
"GraphQL": {
"main": [
[
{
"node": "Success Output",
"type": "main",
"index": 0
}
],
[
{
"node": "Error Output",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {
"Success Output": [
{
"data": {
"users": [
{
"id": "1",
"name": "John Doe",
"email": "[email protected]"
},
{
"id": "2",
"name": "Jane Smith",
"email": "[email protected]"
}
]
}
}
],
"Error Output": [
{
"query": "INVALID_QUERY",
"error": "Syntax Error: Invalid query syntax"
}
]
}
}