mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-08 06:28:33 +00:00
fix: graphQL variables interpolation consistency (UI and CLI) (#7049)
* feat: enhance GraphQL request handling with variable interpolation
This commit is contained in:
@@ -67,40 +67,47 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
});
|
||||
|
||||
const contentType = getContentType(request.headers);
|
||||
const isGraphqlRequest = request.mode === 'graphql';
|
||||
|
||||
if (contentType.includes('json')) {
|
||||
// Skip interpolation if data is a Buffer (e.g., gzip-compressed data)
|
||||
if (typeof request.data === 'object' && !Buffer.isBuffer(request.data)) {
|
||||
try {
|
||||
let parsed = JSON.stringify(request.data);
|
||||
parsed = _interpolate(parsed, { escapeJSONStrings: true });
|
||||
request.data = JSON.parse(parsed);
|
||||
} catch (err) {}
|
||||
}
|
||||
// GraphQL: interpolate query and variables in place. We do not stringify the whole body and interpolate that, because variables is a JSON string. Full-body stringify would nest it and double-escape any {{var}} inside.
|
||||
if (isGraphqlRequest && request.data && typeof request.data === 'object') {
|
||||
request.data.query = _interpolate(request.data.query, { escapeJSONStrings: true });
|
||||
request.data.variables = _interpolate(request.data.variables, { escapeJSONStrings: true });
|
||||
}
|
||||
|
||||
if (typeof request.data === 'string') {
|
||||
if (request?.data?.length) {
|
||||
request.data = _interpolate(request.data, { escapeJSONStrings: true });
|
||||
// Skip body interpolation for GraphQL requests.
|
||||
if (!isGraphqlRequest) {
|
||||
if (contentType.includes('json') && !Buffer.isBuffer(request.data)) {
|
||||
if (typeof request.data === 'string') {
|
||||
if (request?.data?.length) {
|
||||
request.data = _interpolate(request.data, { escapeJSONStrings: true });
|
||||
}
|
||||
} else if (typeof request.data === 'object') {
|
||||
try {
|
||||
let parsed = JSON.stringify(request.data);
|
||||
parsed = _interpolate(parsed, { escapeJSONStrings: true });
|
||||
request.data = JSON.parse(parsed);
|
||||
} catch (err) {}
|
||||
}
|
||||
}
|
||||
} else if (contentType === 'application/x-www-form-urlencoded') {
|
||||
if (request.data && Array.isArray(request.data)) {
|
||||
request.data = request.data.map((d) => ({
|
||||
...d,
|
||||
value: _interpolate(d?.value)
|
||||
}));
|
||||
}
|
||||
} else if (contentType === 'multipart/form-data') {
|
||||
if (Array.isArray(request?.data) && !isFormData(request.data)) {
|
||||
try {
|
||||
request.data = request?.data?.map((d) => ({
|
||||
} else if (contentType === 'application/x-www-form-urlencoded') {
|
||||
if (request.data && Array.isArray(request.data)) {
|
||||
request.data = request.data.map((d) => ({
|
||||
...d,
|
||||
value: _interpolate(d?.value)
|
||||
}));
|
||||
} catch (err) {}
|
||||
}
|
||||
} else if (contentType === 'multipart/form-data') {
|
||||
if (Array.isArray(request?.data) && !isFormData(request.data)) {
|
||||
try {
|
||||
request.data = request?.data?.map((d) => ({
|
||||
...d,
|
||||
value: _interpolate(d?.value)
|
||||
}));
|
||||
} catch (err) {}
|
||||
}
|
||||
} else {
|
||||
request.data = _interpolate(request.data);
|
||||
}
|
||||
} else {
|
||||
request.data = _interpolate(request.data);
|
||||
}
|
||||
|
||||
each(request?.pathParams, (param) => {
|
||||
|
||||
@@ -45,7 +45,8 @@ const prepareRequest = async (item = {}, collection = {}) => {
|
||||
tags: item.tags || [],
|
||||
pathParams: request.params?.filter((param) => param.type === 'path'),
|
||||
settings: item.settings,
|
||||
responseType: 'arraybuffer'
|
||||
responseType: 'arraybuffer',
|
||||
mode: request.body?.mode
|
||||
};
|
||||
|
||||
const collectionRoot = collection?.draft?.root || collection?.root || {};
|
||||
@@ -371,7 +372,8 @@ const prepareRequest = async (item = {}, collection = {}) => {
|
||||
if (request.body.mode === 'graphql') {
|
||||
const graphqlQuery = {
|
||||
query: get(request, 'body.graphql.query'),
|
||||
variables: JSON.parse(decomment(get(request, 'body.graphql.variables') || '{}'))
|
||||
// Parse variables only after interpolation (github.com/usebruno/bruno/issues/884)
|
||||
variables: decomment(get(request, 'body.graphql.variables') || '{}')
|
||||
};
|
||||
if (!contentTypeDefined) {
|
||||
axiosRequest.headers['content-type'] = 'application/json';
|
||||
|
||||
@@ -308,6 +308,16 @@ const runSingleRequest = async function (
|
||||
// interpolate variables inside request
|
||||
interpolateVars(request, envVariables, runtimeVariables, processEnvVars);
|
||||
|
||||
// if this is a graphql request, parse the variables, only after interpolation
|
||||
// https://github.com/usebruno/bruno/issues/884
|
||||
if (request.mode === 'graphql' && typeof request.data?.variables === 'string') {
|
||||
try {
|
||||
request.data.variables = JSON.parse(request.data.variables);
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to parse GraphQL variables: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (request.settings?.encodeUrl) {
|
||||
request.url = encodeUrl(request.url);
|
||||
}
|
||||
|
||||
@@ -600,4 +600,50 @@ describe('prepare-request: prepareRequest', () => {
|
||||
expect(readFileSyncSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GraphQL request', () => {
|
||||
it('keeps variables as string for interpolation', async () => {
|
||||
const item = {
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: [],
|
||||
params: [],
|
||||
url: 'https://example.com',
|
||||
body: {
|
||||
mode: 'graphql',
|
||||
graphql: {
|
||||
query: 'query { x }',
|
||||
variables: '{"apiPermissions": {{permissionsJSON}}}'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const result = await prepareRequest(item);
|
||||
expect(result.mode).toBe('graphql');
|
||||
expect(result.data).toMatchObject({ query: 'query { x }' });
|
||||
expect(typeof result.data.variables).toBe('string');
|
||||
expect(result.data.variables).toBe('{"apiPermissions": {{permissionsJSON}}}');
|
||||
});
|
||||
|
||||
it('defaults variables to "{}" when missing', async () => {
|
||||
const item = {
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: [],
|
||||
params: [],
|
||||
url: 'https://example.com',
|
||||
body: {
|
||||
mode: 'graphql',
|
||||
graphql: {
|
||||
query: 'query { x }',
|
||||
variables: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const result = await prepareRequest(item);
|
||||
expect(typeof result.data.variables).toBe('string');
|
||||
expect(result.data.variables).toBe('{}');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user