@usebruno/converters
Version:
The converters package is responsible for converting collections from one format to a Bruno collection. It can be used as a standalone package or as a part of the Bruno framework.
212 lines (181 loc) • 6.14 kB
JavaScript
import each from 'lodash/each';
import get from 'lodash/get';
import { customAlphabet } from 'nanoid';
import cloneDeep from 'lodash/cloneDeep';
import { collectionSchema } from '@usebruno/schema';
export const safeParseJSON = (str) => {
if (!str || !str.length || typeof str !== 'string') {
return str;
}
try {
return JSON.parse(str);
} catch (e) {
return str;
}
};
export const safeStringifyJSON = (obj, indent = false) => {
if (obj === undefined) {
return obj;
}
try {
if (indent) {
return JSON.stringify(obj, null, 2);
}
return JSON.stringify(obj);
} catch (e) {
return obj;
}
};
export const isItemARequest = (item) => {
return item.hasOwnProperty('request') && ['http-request', 'graphql-request'].includes(item.type) && !item.items;
};
// a customized version of nanoid without using _ and -
export const uuid = () => {
// https://github.com/ai/nanoid/blob/main/url-alphabet/index.js
const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict';
const customNanoId = customAlphabet(urlAlphabet, 21);
return customNanoId();
};
export const validateSchema = (collection = {}) => {
try {
collectionSchema.validateSync(collection);
return collection;
} catch (err) {
console.log("Error validating schema", err);
throw new Error('The Collection has an invalid schema');
}
};
export const updateUidsInCollection = (_collection) => {
const collection = cloneDeep(_collection);
collection.uid = uuid();
const updateItemUids = (items = []) => {
each(items, (item) => {
item.uid = uuid();
each(get(item, 'request.headers'), (header) => (header.uid = uuid()));
each(get(item, 'request.params'), (param) => (param.uid = uuid()));
each(get(item, 'request.vars.req'), (v) => (v.uid = uuid()));
each(get(item, 'request.vars.res'), (v) => (v.uid = uuid()));
each(get(item, 'request.assertions'), (a) => (a.uid = uuid()));
each(get(item, 'request.body.multipartForm'), (param) => (param.uid = uuid()));
each(get(item, 'request.body.formUrlEncoded'), (param) => (param.uid = uuid()));
each(get(item, 'request.body.file'), (param) => (param.uid = uuid()));
if (item.items && item.items.length) {
updateItemUids(item.items);
}
});
};
updateItemUids(collection.items);
const updateEnvUids = (envs = []) => {
each(envs, (env) => {
env.uid = uuid();
each(env.variables, (variable) => (variable.uid = uuid()));
});
};
updateEnvUids(collection.environments);
return collection;
};
// todo
// need to eventually get rid of supporting old collection app models
// 1. start with making request type a constant fetched from a single place
// 2. move references of param and replace it with query inside the app
export const transformItemsInCollection = (collection) => {
const transformItems = (items = []) => {
each(items, (item) => {
if (['http', 'graphql'].includes(item.type)) {
item.type = `${item.type}-request`;
if (item.request.query) {
item.request.params = item.request.query.map((queryItem) => ({
...queryItem,
type: 'query',
uid: queryItem.uid || uuid()
}));
}
delete item.request.query;
// from 5 feb 2024, multipartFormData needs to have a type
// this was introduced when we added support for file uploads
// below logic is to make older collection exports backward compatible
let multipartFormData = get(item, 'request.body.multipartForm');
if (multipartFormData) {
each(multipartFormData, (form) => {
if (!form.type) {
form.type = 'text';
}
});
}
}
if (item.items && item.items.length) {
transformItems(item.items);
}
});
};
transformItems(collection.items);
return collection;
};
export const hydrateSeqInCollection = (collection) => {
const hydrateSeq = (items = []) => {
let index = 1;
each(items, (item) => {
if (isItemARequest(item) && !item.seq) {
item.seq = index;
index++;
}
if (item.items && item.items.length) {
hydrateSeq(item.items);
}
});
};
hydrateSeq(collection.items);
return collection;
};
export const deleteUidsInItems = (items) => {
each(items, (item) => {
delete item.uid;
if (['http-request', 'graphql-request'].includes(item.type)) {
each(get(item, 'request.headers'), (header) => delete header.uid);
each(get(item, 'request.params'), (param) => delete param.uid);
each(get(item, 'request.vars.req'), (v) => delete v.uid);
each(get(item, 'request.vars.res'), (v) => delete v.uid);
each(get(item, 'request.vars.assertions'), (a) => delete a.uid);
each(get(item, 'request.body.multipartForm'), (param) => delete param.uid);
each(get(item, 'request.body.formUrlEncoded'), (param) => delete param.uid);
each(get(item, 'request.body.file'), (param) => delete param.uid);
}
if (item.items && item.items.length) {
deleteUidsInItems(item.items);
}
});
};
/**
* Some of the models in the app are not consistent with the Collection Json format
* This function is used to transform the models to the Collection Json format
*/
export const transformItem = (items = []) => {
each(items, (item) => {
if (['http-request', 'graphql-request'].includes(item.type)) {
if (item.type === 'graphql-request') {
item.type = 'graphql';
}
if (item.type === 'http-request') {
item.type = 'http';
}
}
if (item.items && item.items.length) {
transformItem(item.items);
}
});
};
export const deleteUidsInEnvs = (envs) => {
each(envs, (env) => {
delete env.uid;
each(env.variables, (variable) => delete variable.uid);
});
};
export const deleteSecretsInEnvs = (envs) => {
each(envs, (env) => {
each(env.variables, (variable) => {
if (variable.secret) {
variable.value = '';
}
});
});
};