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 _ = require('lodash');
const { const {
indentString, indentString,
outdentString, outdentString
get
} = require('./utils'); } = require('./utils');
const inlineTag = require('./inline-tag'); 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) { if(body && body.text) {
body.text = outdentString(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"); 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 = { module.exports = {
get,
safeParseJson, safeParseJson,
indentString, indentString,
outdentString outdentString

View File

@ -35,16 +35,4 @@ describe('utils', () => {
expect(outdentString(input)).toBe(expectedOutput); 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 ohm = require("ohm-js");
const _ = require('lodash'); const _ = require('lodash');
const {
outdentString
} = require('../../v1/src/utils');
/** /**
* A Bru file is made up of blocks. * A Bru file is made up of blocks.
@ -21,7 +24,7 @@ const _ = require('lodash');
* *
*/ */
const grammar = ohm.grammar(`Bru { 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 bodies = bodyjson | bodytext | bodyxml | bodygraphql | bodygraphqlvars | bodyforms
bodyforms = bodyformurlencodeddisabled | bodyformurlencoded | bodymultipartdisabled | bodymultipart bodyforms = bodyformurlencodeddisabled | bodyformurlencoded | bodymultipartdisabled | bodymultipart
@ -81,7 +84,7 @@ const grammar = ohm.grammar(`Bru {
bodymultipartdisabled = "body:multipart-form:disabled" dictionary bodymultipartdisabled = "body:multipart-form:disabled" dictionary
script = "script" st* "{" nl* textblock tagend script = "script" st* "{" nl* textblock tagend
test = "test" st* "{" nl* textblock tagend tests = "tests" st* "{" nl* textblock tagend
docs = "docs" st* "{" nl* textblock tagend docs = "docs" st* "{" nl* textblock tagend
}`); }`);
@ -148,7 +151,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
get(_1, dictionary) { get(_1, dictionary) {
return { return {
http: { http: {
method: 'GET', method: 'get',
...mapPairListToKeyValPair(dictionary.ast) ...mapPairListToKeyValPair(dictionary.ast)
} }
}; };
@ -156,7 +159,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
post(_1, dictionary) { post(_1, dictionary) {
return { return {
http: { http: {
method: 'POST', method: 'post',
...mapPairListToKeyValPair(dictionary.ast) ...mapPairListToKeyValPair(dictionary.ast)
} }
}; };
@ -164,7 +167,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
put(_1, dictionary) { put(_1, dictionary) {
return { return {
http: { http: {
method: 'PUT', method: 'put',
...mapPairListToKeyValPair(dictionary.ast) ...mapPairListToKeyValPair(dictionary.ast)
} }
}; };
@ -172,7 +175,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
delete(_1, dictionary) { delete(_1, dictionary) {
return { return {
http: { http: {
method: 'DELETE', method: 'delete',
...mapPairListToKeyValPair(dictionary.ast) ...mapPairListToKeyValPair(dictionary.ast)
} }
}; };
@ -180,7 +183,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
options(_1, dictionary) { options(_1, dictionary) {
return { return {
http: { http: {
method: 'OPTIONS', method: 'options',
...mapPairListToKeyValPair(dictionary.ast) ...mapPairListToKeyValPair(dictionary.ast)
} }
}; };
@ -188,7 +191,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
head(_1, dictionary) { head(_1, dictionary) {
return { return {
http: { http: {
method: 'HEAD', method: 'head',
...mapPairListToKeyValPair(dictionary.ast) ...mapPairListToKeyValPair(dictionary.ast)
} }
}; };
@ -196,7 +199,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
connect(_1, dictionary) { connect(_1, dictionary) {
return { return {
http: { http: {
method: 'CONNECT', method: 'connect',
...mapPairListToKeyValPair(dictionary.ast) ...mapPairListToKeyValPair(dictionary.ast)
} }
}; };
@ -252,21 +255,21 @@ const sem = grammar.createSemantics().addAttribute('ast', {
bodyjson(_1, _2, _3, _4, textblock, _5) { bodyjson(_1, _2, _3, _4, textblock, _5) {
return { return {
body: { body: {
json: textblock.sourceString json: outdentString(textblock.sourceString)
} }
}; };
}, },
bodytext(_1, _2, _3, _4, textblock, _5) { bodytext(_1, _2, _3, _4, textblock, _5) {
return { return {
body: { body: {
text: textblock.sourceString text: outdentString(textblock.sourceString)
} }
}; };
}, },
bodyxml(_1, _2, _3, _4, textblock, _5) { bodyxml(_1, _2, _3, _4, textblock, _5) {
return { return {
body: { body: {
xml: textblock.sourceString xml: outdentString(textblock.sourceString)
} }
}; };
}, },
@ -274,7 +277,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
return { return {
body: { body: {
graphql: { graphql: {
query: textblock.sourceString query: outdentString(textblock.sourceString)
} }
} }
}; };
@ -283,29 +286,37 @@ const sem = grammar.createSemantics().addAttribute('ast', {
return { return {
body: { body: {
graphql: { graphql: {
variables: textblock.sourceString variables: outdentString(textblock.sourceString)
} }
} }
}; };
}, },
vars(_1, dictionary) { vars(_1, dictionary) {
const vars = mapPairListToKeyValPairs(dictionary.ast);
_.each(vars, (val) => { val.local = false; });
return { return {
vars: mapPairListToKeyValPairs(dictionary.ast) vars
}; };
}, },
varsdisabled(_1, dictionary) { varsdisabled(_1, dictionary) {
const vars = mapPairListToKeyValPairs(dictionary.ast, false);
_.each(vars, (val) => { val.local = false; });
return { return {
vars: mapPairListToKeyValPairs(dictionary.ast, false) vars
}; };
}, },
varslocal(_1, dictionary) { varslocal(_1, dictionary) {
const varsLocal = mapPairListToKeyValPairs(dictionary.ast);
_.each(varsLocal, (val) => { val.local = true; });
return { return {
varsLocal: mapPairListToKeyValPairs(dictionary.ast) vars: varsLocal
}; };
}, },
varslocaldisabled(_1, dictionary) { varslocaldisabled(_1, dictionary) {
const varsLocal = mapPairListToKeyValPairs(dictionary.ast, false);
_.each(varsLocal, (val) => { val.local = true; });
return { return {
varsLocal: mapPairListToKeyValPairs(dictionary.ast, false) vars: varsLocal
}; };
}, },
assert(_1, dictionary) { assert(_1, dictionary) {
@ -320,17 +331,17 @@ const sem = grammar.createSemantics().addAttribute('ast', {
}, },
script(_1, _2, _3, _4, textblock, _5) { script(_1, _2, _3, _4, textblock, _5) {
return { return {
script: textblock.sourceString script: outdentString(textblock.sourceString)
}; };
}, },
test(_1, _2, _3, _4, textblock, _5) { tests(_1, _2, _3, _4, textblock, _5) {
return { return {
test: textblock.sourceString tests: outdentString(textblock.sourceString)
};; };;
}, },
docs(_1, _2, _3, _4, textblock, _5) { docs(_1, _2, _3, _4, textblock, _5) {
return { return {
docs: textblock.sourceString docs: outdentString(textblock.sourceString)
}; };
}, },
textblock(line, _1, rest) { 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}} 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 { body:form-urlencoded {
apikey: secret apikey: secret
numbers: +91998877665 numbers: +91998877665
@ -45,23 +62,6 @@ body:multipart-form:disabled {
message: hello 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 { body:graphql {
{ {
launchesPast { launchesPast {
@ -103,7 +103,11 @@ assert:disabled {
$res.body.message: success $res.body.message: success
} }
test { script {
const foo = 'bar';
}
tests {
function onResponse(request, response) { function onResponse(request, response) {
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
} }
@ -111,4 +115,4 @@ test {
docs { docs {
This request needs auth token to be set in the headers. This request needs auth token to be set in the headers.
} }

View File

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

View File

@ -14,7 +14,7 @@ script {
`; `;
const output = parser(input); 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); expect(output.script).toEqual(expected);
}); });
}); });