feat: bru lang - jsonToBru functionality

This commit is contained in:
Anoop M D 2023-02-05 00:27:18 +05:30
parent d24f1a1054
commit 4a4208f272
9 changed files with 330 additions and 86 deletions

View File

@ -6,8 +6,7 @@ const {
const _ = require('lodash');
const {
indentString,
outdentString,
get
outdentString
} = require('./utils');
const inlineTag = require('./inline-tag');
@ -64,7 +63,7 @@ const bruToJson = (fileContents) => {
}
};
const body = get(json, 'request.body');
const body = _.get(json, 'request.body');
if(body && body.text) {
body.text = outdentString(body.text);

View File

@ -23,21 +23,7 @@ const outdentString = (str) => {
return str.split("\n").map(line => line.replace(/^ /, '')).join("\n");
};
// implement lodash _.get functionality
const get = (obj, path, defaultValue) => {
const pathParts = path.split('.');
let current = obj;
for(let i = 0; i < pathParts.length; i++) {
if(current[pathParts[i]] === undefined) {
return defaultValue;
}
current = current[pathParts[i]];
}
return current;
};
module.exports = {
get,
safeParseJson,
indentString,
outdentString

View File

@ -35,16 +35,4 @@ describe('utils', () => {
expect(outdentString(input)).toBe(expectedOutput);
});
});
describe('get', () => {
it('returns the value at the given path', () => {
const input = { a: { b: { c: 1 } } };
expect(get(input, 'a.b.c')).toBe(1);
});
it('returns the defaultValue if the path does not exist', () => {
const input = { a: { b: { c: 1 } } };
expect(get(input, 'a.b.d', 2)).toBe(2);
});
});
});

View File

@ -1,5 +1,8 @@
const ohm = require("ohm-js");
const _ = require('lodash');
const {
outdentString
} = require('../../v1/src/utils');
/**
* A Bru file is made up of blocks.
@ -21,7 +24,7 @@ const _ = require('lodash');
*
*/
const grammar = ohm.grammar(`Bru {
BruFile = (meta | http | querydisabled | query | headersdisabled | headers | bodies | varsandassert | script | test | docs)*
BruFile = (meta | http | querydisabled | query | headersdisabled | headers | bodies | varsandassert | script | tests | docs)*
bodies = bodyjson | bodytext | bodyxml | bodygraphql | bodygraphqlvars | bodyforms
bodyforms = bodyformurlencodeddisabled | bodyformurlencoded | bodymultipartdisabled | bodymultipart
@ -81,7 +84,7 @@ const grammar = ohm.grammar(`Bru {
bodymultipartdisabled = "body:multipart-form:disabled" dictionary
script = "script" st* "{" nl* textblock tagend
test = "test" st* "{" nl* textblock tagend
tests = "tests" st* "{" nl* textblock tagend
docs = "docs" st* "{" nl* textblock tagend
}`);
@ -148,7 +151,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
get(_1, dictionary) {
return {
http: {
method: 'GET',
method: 'get',
...mapPairListToKeyValPair(dictionary.ast)
}
};
@ -156,7 +159,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
post(_1, dictionary) {
return {
http: {
method: 'POST',
method: 'post',
...mapPairListToKeyValPair(dictionary.ast)
}
};
@ -164,7 +167,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
put(_1, dictionary) {
return {
http: {
method: 'PUT',
method: 'put',
...mapPairListToKeyValPair(dictionary.ast)
}
};
@ -172,7 +175,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
delete(_1, dictionary) {
return {
http: {
method: 'DELETE',
method: 'delete',
...mapPairListToKeyValPair(dictionary.ast)
}
};
@ -180,7 +183,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
options(_1, dictionary) {
return {
http: {
method: 'OPTIONS',
method: 'options',
...mapPairListToKeyValPair(dictionary.ast)
}
};
@ -188,7 +191,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
head(_1, dictionary) {
return {
http: {
method: 'HEAD',
method: 'head',
...mapPairListToKeyValPair(dictionary.ast)
}
};
@ -196,7 +199,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
connect(_1, dictionary) {
return {
http: {
method: 'CONNECT',
method: 'connect',
...mapPairListToKeyValPair(dictionary.ast)
}
};
@ -252,21 +255,21 @@ const sem = grammar.createSemantics().addAttribute('ast', {
bodyjson(_1, _2, _3, _4, textblock, _5) {
return {
body: {
json: textblock.sourceString
json: outdentString(textblock.sourceString)
}
};
},
bodytext(_1, _2, _3, _4, textblock, _5) {
return {
body: {
text: textblock.sourceString
text: outdentString(textblock.sourceString)
}
};
},
bodyxml(_1, _2, _3, _4, textblock, _5) {
return {
body: {
xml: textblock.sourceString
xml: outdentString(textblock.sourceString)
}
};
},
@ -274,7 +277,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
return {
body: {
graphql: {
query: textblock.sourceString
query: outdentString(textblock.sourceString)
}
}
};
@ -283,29 +286,37 @@ const sem = grammar.createSemantics().addAttribute('ast', {
return {
body: {
graphql: {
variables: textblock.sourceString
variables: outdentString(textblock.sourceString)
}
}
};
},
vars(_1, dictionary) {
const vars = mapPairListToKeyValPairs(dictionary.ast);
_.each(vars, (val) => { val.local = false; });
return {
vars: mapPairListToKeyValPairs(dictionary.ast)
vars
};
},
varsdisabled(_1, dictionary) {
const vars = mapPairListToKeyValPairs(dictionary.ast, false);
_.each(vars, (val) => { val.local = false; });
return {
vars: mapPairListToKeyValPairs(dictionary.ast, false)
vars
};
},
varslocal(_1, dictionary) {
const varsLocal = mapPairListToKeyValPairs(dictionary.ast);
_.each(varsLocal, (val) => { val.local = true; });
return {
varsLocal: mapPairListToKeyValPairs(dictionary.ast)
vars: varsLocal
};
},
varslocaldisabled(_1, dictionary) {
const varsLocal = mapPairListToKeyValPairs(dictionary.ast, false);
_.each(varsLocal, (val) => { val.local = true; });
return {
varsLocal: mapPairListToKeyValPairs(dictionary.ast, false)
vars: varsLocal
};
},
assert(_1, dictionary) {
@ -320,17 +331,17 @@ const sem = grammar.createSemantics().addAttribute('ast', {
},
script(_1, _2, _3, _4, textblock, _5) {
return {
script: textblock.sourceString
script: outdentString(textblock.sourceString)
};
},
test(_1, _2, _3, _4, textblock, _5) {
tests(_1, _2, _3, _4, textblock, _5) {
return {
test: textblock.sourceString
tests: outdentString(textblock.sourceString)
};;
},
docs(_1, _2, _3, _4, textblock, _5) {
return {
docs: textblock.sourceString
docs: outdentString(textblock.sourceString)
};
},
textblock(line, _1, rest) {

View File

@ -0,0 +1,243 @@
const _ = require('lodash');
const {
indentString,
} = require('../../v1/src/utils');
const enabled = (items = []) => items.filter(item => item.enabled);
const disabled = (items = []) => items.filter(item => !item.enabled);
// remove the last line if two new lines are found
const stripLastLine = (text) => {
if(!text || !text.length) return text;
return text.replace(/(\r?\n)$/, '');
};
const jsonToBru = (json) => {
const {
meta,
http,
query,
headers,
body,
script,
tests,
vars,
assert,
docs
} = json;
let bru = '';
if(meta) {
bru += 'meta {\n';
for (const key in meta) {
bru += ` ${key}: ${meta[key]}\n`;
}
bru += '}\n\n';
}
if(http && http.method) {
bru += `${http.method} {
url: ${http.url}`;
if(http.body && http.body.length) {
bru += `
body: ${http.body}`;
}
bru += `
}
`;
}
if(query && query.length && enabled(query).length) {
bru += `query {
${indentString(enabled(query).map(item => `${item.name}: ${item.value}`).join('\n'))}
}
`;
}
if(query && query.length && disabled(query).length) {
bru += `query:disabled {
${indentString(disabled(query).map(item => `${item.name}: ${item.value}`).join('\n'))}
}
`;
}
if(headers && headers.length && enabled(headers).length) {
bru += `headers {
${indentString(enabled(headers).map(header => `${header.name}: ${header.value}`).join('\n'))}
}
`;
}
if(headers && headers.length && disabled(headers).length) {
bru += `headers:disabled {
${indentString(disabled(headers).map(header => `${header.name}: ${header.value}`).join('\n'))}
}
`;
}
if(body && body.json && body.json.length) {
bru += `body:json {
${indentString(body.json)}
}
`;
}
if(body && body.text && body.text.length) {
bru += `body:text {
${indentString(body.text)}
}
`;
}
if(body && body.xml && body.xml.length) {
bru += `body:xml {
${indentString(body.xml)}
}
`;
}
if(body && body.formUrlEncoded && enabled(body.formUrlEncoded).length) {
bru += `body:form-urlencoded {
${indentString(enabled(body.formUrlEncoded).map(item => `${item.name}: ${item.value}`).join('\n'))}
}
`;
}
if(body && body.formUrlEncoded && disabled(body.formUrlEncoded).length) {
bru += `body:form-urlencoded:disabled {
${indentString(disabled(body.formUrlEncoded).map(item => `${item.name}: ${item.value}`).join('\n'))}
}
`;
}
if(body && body.multipartForm && enabled(body.multipartForm).length) {
bru += `body:multipart-form {
${indentString(enabled(body.multipartForm).map(item => `${item.name}: ${item.value}`).join('\n'))}
}
`;
}
if(body && body.multipartForm && disabled(body.multipartForm).length) {
bru += `body:multipart-form:disabled {
${indentString(disabled(body.multipartForm).map(item => `${item.name}: ${item.value}`).join('\n'))}
}
`;
}
if(body && body.graphql && body.graphql.query) {
bru += `body:graphql {
${indentString(body.graphql.query)}
}
`;
}
if(body && body.graphql && body.graphql.variables) {
bru += `body:graphql:vars {
${indentString(body.graphql.variables)}
}
`;
}
if(vars && vars.length) {
const varsEnabled = _.filter(vars, (v) => v.enabled && !v.local);
const varsDisabled = _.filter(vars, (v) => !v.enabled && !v.local);
const varsLocalEnabled = _.filter(vars, (v) => v.enabled && v.local);
const varsLocalDisabled = _.filter(vars, (v) => !v.enabled && v.local);
if(varsEnabled.length) {
bru += `vars {
${indentString(varsEnabled.map(item => `${item.name}: ${item.value}`).join('\n'))}
}
`;
}
if(varsDisabled.length) {
bru += `vars:disabled {
${indentString(varsDisabled.map(item => `${item.name}: ${item.value}`).join('\n'))}
}
`;
}
if(varsLocalEnabled.length) {
bru += `vars:local {
${indentString(varsLocalEnabled.map(item => `${item.name}: ${item.value}`).join('\n'))}
}
`;
}
if(varsLocalDisabled.length) {
bru += `vars:local:disabled {
${indentString(varsLocalDisabled.map(item => `${item.name}: ${item.value}`).join('\n'))}
}
`;
}
}
if(assert && enabled(assert).length) {
bru += `assert {
${indentString(enabled(assert).map(item => `${item.name}: ${item.value}`).join('\n'))}
}
`;
}
if(assert && disabled(assert).length) {
bru += `assert:disabled {
${indentString(disabled(assert).map(item => `${item.name}: ${item.value}`).join('\n'))}
}
`;
}
if(script && script.length) {
bru += `script {
${indentString(script)}
}
`;
}
if(tests && tests.length) {
bru += `tests {
${indentString(tests)}
}
`;
}
if(docs && docs.length) {
bru += `docs {
${indentString(docs)}
}
`;
}
return stripLastLine(bru);
};
module.exports = jsonToBru;

View File

@ -27,6 +27,23 @@ headers:disabled {
transaction-id: {{transactionId}}
}
body:json {
{
"hello": "world"
}
}
body:text {
This is a text body
}
body:xml {
<xml>
<name>John</name>
<age>30</age>
</xml>
}
body:form-urlencoded {
apikey: secret
numbers: +91998877665
@ -45,23 +62,6 @@ body:multipart-form:disabled {
message: hello
}
body:json {
{
"hello": "world"
}
}
body:text {
This is a text body
}
body:xml {
<xml>
<name>John</name>
<age>30</age>
</xml>
}
body:graphql {
{
launchesPast {
@ -103,7 +103,11 @@ assert:disabled {
$res.body.message: success
}
test {
script {
const foo = 'bar';
}
tests {
function onResponse(request, response) {
expect(response.status).to.equal(200);
}
@ -111,4 +115,4 @@ test {
docs {
This request needs auth token to be set in the headers.
}
}

View File

@ -5,7 +5,7 @@
"seq": "1"
},
"http": {
"method": "GET",
"method": "get",
"url": "https://api.textlocal.in/send/",
"body": "json"
},
@ -40,12 +40,12 @@
}
],
"body": {
"json": " {\n \"hello\": \"world\"\n }",
"text": " This is a text body",
"xml": " <xml>\n <name>John</name>\n <age>30</age>\n </xml>",
"json": "{\n \"hello\": \"world\"\n}",
"text": "This is a text body",
"xml": "<xml>\n <name>John</name>\n <age>30</age>\n</xml>",
"graphql": {
"query": " {\n launchesPast {\n launch_site {\n site_name\n }\n launch_success\n }\n }",
"variables": " {\n \"limit\": 5\n }"
"query": "{\n launchesPast {\n launch_site {\n site_name\n }\n launch_success\n }\n}",
"variables": "{\n \"limit\": 5\n}"
},
"formUrlEncoded": [
{
@ -86,23 +86,25 @@
{
"name": "token",
"value": "$res.body.token",
"local": false,
"enabled": true
},
{
"name": "petId",
"value": "$res.body.id",
"local": false,
"enabled": false
}
],
"varsLocal": [
},
{
"name": "orderNumber",
"value": "$res.body.orderNumber",
"local": true,
"enabled": true
},
{
"name": "transactionId",
"value": "$res.body.transactionId",
"local": true,
"enabled": false
}
],
@ -118,6 +120,7 @@
"enabled": false
}
],
"test": " function onResponse(request, response) {\n expect(response.status).to.equal(200);\n }",
"docs": " This request needs auth token to be set in the headers."
"script": "const foo = 'bar';",
"tests": "function onResponse(request, response) {\n expect(response.status).to.equal(200);\n}",
"docs": "This request needs auth token to be set in the headers."
}

View File

@ -1,14 +1,24 @@
const fs = require("fs");
const path = require("path");
const parser = require("../src/bruToJson");
const bruToJson = require("../src/bruToJson");
const jsonToBru = require("../src/jsonToBru");
describe("parser", () => {
describe("bruToJson", () => {
it("should parse the bru file", () => {
const input = fs.readFileSync(path.join(__dirname, 'fixtures', 'request.bru'), 'utf8');
const expected = require("./fixtures/request.json");
const output = parser(input);
const output = bruToJson(input);
expect(output).toEqual(expected);
});
});
describe("jsonToBru", () => {
it("should parse the bru file", () => {
const input = require("./fixtures/request.json");
const expected = fs.readFileSync(path.join(__dirname, 'fixtures', 'request.bru'), 'utf8');
const output = jsonToBru(input);
// console.log(JSON.stringify(output, null, 2));
expect(output).toEqual(expected);
});
});

View File

@ -14,7 +14,7 @@ script {
`;
const output = parser(input);
const expected = " function onResponse(request, response) {\n expect(response.status).to.equal(200);\n }";
const expected = "function onResponse(request, response) {\n expect(response.status).to.equal(200);\n}";
expect(output.script).toEqual(expected);
});
});