-
Notifications
You must be signed in to change notification settings - Fork 67
Expand file tree
/
Copy pathgql.js
More file actions
140 lines (120 loc) · 4.49 KB
/
Copy pathgql.js
File metadata and controls
140 lines (120 loc) · 4.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { isPureObject } from '../../../common/util/type-check'
/**
* @typedef {object} GQLMetadata
* @property {string} operationName Name of the operation
* @property {string} operationType Type of the operation
* @property {string} operationFramework Framework responsible for the operation
*/
/**
* Parses and returns the graphql metadata from a network request. If the network
* request is not a graphql call, undefined will be returned.
* @param {object|string} body Ajax request body
* @param {string} query Ajax request query param string
* @returns {GQLMetadata | undefined}
*/
export function parseGQL ({ body, query } = {}) {
if (!body && !query) return
try {
const gqlBody = parseBatchGQL(parseGQLContents(body))
if (gqlBody) return gqlBody
const gqlQuery = parseSingleGQL(parseGQLQueryString(query))
if (gqlQuery) return gqlQuery
} catch (err) {
// parsing failed, return undefined
}
}
/**
* @param {string|Object} gql The GraphQL object body sent to a GQL server
* @returns {GQLMetadata}
*/
function parseSingleGQL (contents) {
if (typeof contents !== 'object' || !contents.query || typeof contents.query !== 'string') return
/** parses gql query string and returns [fullmatch, type match, name match] */
const matches = contents.query.trim().match(/^(query|mutation|subscription)\s?(\w*)/)
const operationType = matches?.[1]
if (!operationType) return
const operationName = contents.operationName || matches?.[2] || 'Anonymous'
return {
operationName, // the operation name of the indiv query
operationType, // query, mutation, or subscription,
operationFramework: 'GraphQL'
}
}
function parseBatchGQL (contents) {
if (!contents) return
if (!Array.isArray(contents)) contents = [contents]
const opNames = []
const opTypes = []
for (let content of contents) {
const operation = parseSingleGQL(content)
if (!operation) continue
opNames.push(operation.operationName)
opTypes.push(operation.operationType)
}
if (!opTypes.length) return
return {
operationName: opNames.join(','), // the operation name of the indiv query -- joined by ',' for batched results
operationType: opTypes.join(','), // query, mutation, or subscription -- joined by ',' for batched results
operationFramework: 'GraphQL'
}
}
function parseGQLContents (gqlContents) {
let contents
if (!gqlContents || (typeof gqlContents !== 'string' && typeof gqlContents !== 'object')) return
else if (typeof gqlContents === 'string') contents = JSON.parse(gqlContents)
else contents = gqlContents
if (!isPureObject(contents) && !Array.isArray(contents)) return
let isValid = false
if (Array.isArray(contents)) isValid = contents.some(x => validateGQLObject(x))
else isValid = validateGQLObject(contents)
if (!isValid) return
return contents
}
function parseGQLQueryString (gqlQueryString) {
if (!gqlQueryString || typeof gqlQueryString !== 'string') return
const params = new URLSearchParams(gqlQueryString)
return parseGQLContents(Object.fromEntries(params))
}
function validateGQLObject (obj) {
return !(typeof obj !== 'object' || !obj.query || typeof obj.query !== 'string')
}
/**
* Checks if a response object has valid GraphQL errors.
* @param {object} response - A single GraphQL response object
* @returns {boolean} True if the response has valid errors
*/
function hasValidGQLErrors (response) {
return Array.isArray(response?.errors) &&
response.errors.some(err =>
err && typeof err === 'object' && typeof err.message === 'string'
)
}
/**
* Checks if a response body contains GraphQL errors according to the GraphQL spec.
* A valid GraphQL error response contains an "errors" array with at least one error object.
* Supports both single and batched GraphQL responses.
* @param {string|object|array} [responseBody] The response body to check
* @returns {boolean} True if the response contains GraphQL errors
*/
export function hasGQLErrors (responseBody) {
if (!responseBody) return false
try {
let parsed = responseBody
// Parse string to object if needed
if (typeof responseBody === 'string') {
parsed = JSON.parse(responseBody)
}
// Handle batched GraphQL responses (array of response objects)
if (Array.isArray(parsed)) {
return parsed.some(hasValidGQLErrors)
}
// Handle single GraphQL response
return hasValidGQLErrors(parsed)
} catch (err) {
return false
}
}