@usebruno/cli
Version:
With Bruno CLI, you can now run your API collections with ease using simple command line commands.
282 lines (249 loc) • 10.6 kB
JavaScript
const { interpolate } = require('@usebruno/common');
const { each, forOwn, cloneDeep, find } = require('lodash');
const FormData = require('form-data');
const getContentType = (headers = {}) => {
let contentType = '';
forOwn(headers, (value, key) => {
if (key && key.toLowerCase() === 'content-type') {
contentType = value;
}
});
return contentType;
};
const interpolateVars = (
request,
envVariables = {},
runtimeVariables = {},
processEnvVars = {},
externalSecretVariables = {}
) => {
const collectionVariables = request?.collectionVariables || {};
const folderVariables = request?.folderVariables || {};
const requestVariables = request?.requestVariables || {};
const oauth2CredentialVariables = request?.oauth2CredentialVariables || {};
// we clone envVars because we don't want to modify the original object
envVariables = cloneDeep(envVariables);
// envVars can inturn have values as {{process.env.VAR_NAME}}
// so we need to interpolate envVars first with processEnvVars
forOwn(envVariables, (value, key) => {
envVariables[key] = interpolate(value, {
process: {
env: {
...processEnvVars
}
}
});
});
const _interpolate = (str, { escapeJSONStrings } = {}) => {
if (!str || !str.length || typeof str !== 'string') {
return str;
}
// runtimeVariables take precedence over envVars
const combinedVars = {
...collectionVariables,
...envVariables,
...folderVariables,
...requestVariables,
...oauth2CredentialVariables,
...runtimeVariables,
process: {
env: {
...processEnvVars
}
},
...externalSecretVariables
};
return interpolate(str, combinedVars, { escapeJSONStrings });
};
request.url = _interpolate(request.url);
forOwn(request.headers, (value, key) => {
delete request.headers[key];
request.headers[_interpolate(key)] = _interpolate(value);
});
const contentType = getContentType(request.headers);
if (contentType.includes('json')) {
if (typeof request.data === 'object') {
try {
let parsed = JSON.stringify(request.data);
parsed = _interpolate(parsed, { escapeJSONStrings: true });
request.data = JSON.parse(parsed);
} catch (err) {}
}
if (typeof request.data === 'string') {
if (request?.data?.length) {
request.data = _interpolate(request.data, { escapeJSONStrings: true });
}
}
} 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) && !(request.data instanceof FormData)) {
try {
request.data = request?.data?.map(d => ({
...d,
value: _interpolate(d?.value)
}));
} catch (err) {}
}
} else {
request.data = _interpolate(request.data);
}
each(request?.pathParams, (param) => {
param.value = _interpolate(param.value);
});
if (request?.pathParams?.length) {
let url = request.url;
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = `http://${url}`;
}
try {
url = new URL(url);
} catch (e) {
throw { message: 'Invalid URL format', originalError: e.message };
}
const interpolatedUrlPath = url.pathname
.split('/')
.filter((path) => path !== '')
.map((path) => {
// traditional path parameters
if (path.startsWith(':')) {
const paramName = path.slice(1);
const existingPathParam = request.pathParams.find(param => param.name === paramName);
if (!existingPathParam) {
return '/' + path;
}
return '/' + existingPathParam.value;
}
// for OData-style parameters (parameters inside parentheses)
// Check if path matches valid OData syntax:
// 1. EntitySet('key') or EntitySet(key)
// 2. EntitySet(Key1=value1,Key2=value2)
// 3. Function(param=value)
if (/^[A-Za-z0-9_.-]+\([^)]*\)$/.test(path)) {
const paramRegex = /[:](\w+)/g;
let match;
let result = path;
while ((match = paramRegex.exec(path))) {
if (match[1]) {
let name = match[1].replace(/[')"`]+$/, '');
name = name.replace(/^[('"`]+/, '');
if (name) {
const existingPathParam = request.pathParams.find((param) => param.name === name);
if (existingPathParam) {
result = result.replace(':' + match[1], existingPathParam.value);
}
}
}
}
return '/' + result;
}
return '/' + path;
})
.join('');
const trailingSlash = url.pathname.endsWith('/') ? '/' : '';
request.url = url.origin + interpolatedUrlPath + trailingSlash + url.search;
}
if (request.proxy) {
request.proxy.protocol = _interpolate(request.proxy.protocol);
request.proxy.hostname = _interpolate(request.proxy.hostname);
request.proxy.port = _interpolate(request.proxy.port);
if (request.proxy.auth) {
request.proxy.auth.username = _interpolate(request.proxy.auth.username);
request.proxy.auth.password = _interpolate(request.proxy.auth.password);
}
}
// todo: we have things happening in two places w.r.t basic auth
// need to refactor this in the future
// the request.auth (basic auth) object gets set inside the prepare-request.js file
if (request.basicAuth) {
const username = _interpolate(request.basicAuth.username) || '';
const password = _interpolate(request.basicAuth.password) || '';
// use auth header based approach and delete the request.auth object
request.headers['Authorization'] = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
delete request.basicAuth;
}
if (request?.oauth2?.grantType) {
switch (request.oauth2.grantType) {
case 'password':
request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || '';
request.oauth2.refreshTokenUrl = _interpolate(request.oauth2.refreshTokenUrl) || '';
request.oauth2.username = _interpolate(request.oauth2.username) || '';
request.oauth2.password = _interpolate(request.oauth2.password) || '';
request.oauth2.clientId = _interpolate(request.oauth2.clientId) || '';
request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || '';
request.oauth2.scope = _interpolate(request.oauth2.scope) || '';
request.oauth2.credentialsPlacement = _interpolate(request.oauth2.credentialsPlacement) || '';
request.oauth2.credentialsId = _interpolate(request.oauth2.credentialsId) || '';
request.oauth2.tokenPlacement = _interpolate(request.oauth2.tokenPlacement) || '';
request.oauth2.tokenHeaderPrefix = _interpolate(request.oauth2.tokenHeaderPrefix) || '';
request.oauth2.tokenQueryKey = _interpolate(request.oauth2.tokenQueryKey) || '';
break;
case 'client_credentials':
request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || '';
request.oauth2.refreshTokenUrl = _interpolate(request.oauth2.refreshTokenUrl) || '';
request.oauth2.clientId = _interpolate(request.oauth2.clientId) || '';
request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || '';
request.oauth2.scope = _interpolate(request.oauth2.scope) || '';
request.oauth2.credentialsPlacement = _interpolate(request.oauth2.credentialsPlacement) || '';
request.oauth2.credentialsId = _interpolate(request.oauth2.credentialsId) || '';
request.oauth2.tokenPlacement = _interpolate(request.oauth2.tokenPlacement) || '';
request.oauth2.tokenHeaderPrefix = _interpolate(request.oauth2.tokenHeaderPrefix) || '';
request.oauth2.tokenQueryKey = _interpolate(request.oauth2.tokenQueryKey) || '';
break;
default:
break;
}
// Interpolate additional parameters for all OAuth2 grant types
if (request.oauth2.additionalParameters) {
// Interpolate authorization parameters
if (Array.isArray(request.oauth2.additionalParameters.authorization)) {
request.oauth2.additionalParameters.authorization.forEach((param) => {
if (param && param.enabled !== false) {
param.name = _interpolate(param.name) || '';
param.value = _interpolate(param.value) || '';
}
});
}
// Interpolate token parameters
if (Array.isArray(request.oauth2.additionalParameters.token)) {
request.oauth2.additionalParameters.token.forEach((param) => {
if (param && param.enabled !== false) {
param.name = _interpolate(param.name) || '';
param.value = _interpolate(param.value) || '';
}
});
}
// Interpolate refresh parameters
if (Array.isArray(request.oauth2.additionalParameters.refresh)) {
request.oauth2.additionalParameters.refresh.forEach((param) => {
if (param && param.enabled !== false) {
param.name = _interpolate(param.name) || '';
param.value = _interpolate(param.value) || '';
}
});
}
}
}
if (request.awsv4config) {
request.awsv4config.accessKeyId = _interpolate(request.awsv4config.accessKeyId) || '';
request.awsv4config.secretAccessKey = _interpolate(request.awsv4config.secretAccessKey) || '';
request.awsv4config.sessionToken = _interpolate(request.awsv4config.sessionToken) || '';
request.awsv4config.service = _interpolate(request.awsv4config.service) || '';
request.awsv4config.region = _interpolate(request.awsv4config.region) || '';
request.awsv4config.profileName = _interpolate(request.awsv4config.profileName) || '';
}
// interpolate vars for ntlmConfig auth
if (request.ntlmConfig) {
request.ntlmConfig.username = _interpolate(request.ntlmConfig.username) || '';
request.ntlmConfig.password = _interpolate(request.ntlmConfig.password) || '';
request.ntlmConfig.domain = _interpolate(request.ntlmConfig.domain) || '';
}
if(request?.auth) delete request.auth;
if (request) return request;
};
module.exports = interpolateVars;