mirror of
https://github.com/usebruno/bruno.git
synced 2025-07-31 05:03:36 +02:00
424 lines
9.3 KiB
JavaScript
424 lines
9.3 KiB
JavaScript
const ohm = require("ohm-js");
|
|
const _ = require('lodash');
|
|
const {
|
|
outdentString
|
|
} = require('../../v1/src/utils');
|
|
|
|
/**
|
|
* A Bru file is made up of blocks.
|
|
* There are two types of blocks
|
|
*
|
|
* 1. Dictionary Blocks - These are blocks that have key value pairs
|
|
* ex:
|
|
* headers {
|
|
* content-type: application/json
|
|
* }
|
|
*
|
|
* 2. Text Blocks - These are blocks that have text
|
|
* ex:
|
|
* body:json {
|
|
* {
|
|
* "username": "John Nash",
|
|
* "password": "governingdynamics
|
|
* }
|
|
*
|
|
*/
|
|
const grammar = ohm.grammar(`Bru {
|
|
BruFile = (meta | http | query | headers | bodies | varsandassert | script | tests | docs)*
|
|
bodies = bodyjson | bodytext | bodyxml | bodygraphql | bodygraphqlvars | bodyforms | body
|
|
bodyforms = bodyformurlencoded | bodymultipart
|
|
|
|
nl = "\\r"? "\\n"
|
|
st = " " | "\\t"
|
|
stnl = st | nl
|
|
tagend = nl "}"
|
|
optionalnl = ~tagend nl
|
|
keychar = ~(tagend | st | nl | ":") any
|
|
valuechar = ~(nl | tagend) any
|
|
|
|
// Dictionary Blocks
|
|
dictionary = st* "{" pairlist? tagend
|
|
pairlist = optionalnl* pair (~tagend stnl* pair)* (~tagend space)*
|
|
pair = st* key st* ":" st* value st*
|
|
key = keychar*
|
|
value = valuechar*
|
|
|
|
// Dictionary for Assert Block
|
|
assertdictionary = st* "{" assertpairlist? tagend
|
|
assertpairlist = optionalnl* assertpair (~tagend stnl* assertpair)* (~tagend space)*
|
|
assertpair = st* assertkey st* ":" st* value st*
|
|
assertkey = ~tagend assertkeychar*
|
|
assertkeychar = ~(tagend | nl | ":") any
|
|
|
|
// Text Blocks
|
|
textblock = textline (~tagend nl textline)*
|
|
textline = textchar*
|
|
textchar = ~nl any
|
|
|
|
meta = "meta" dictionary
|
|
|
|
http = get | post | put | delete | patch | options | head | connect | trace
|
|
get = "get" dictionary
|
|
post = "post" dictionary
|
|
put = "put" dictionary
|
|
delete = "delete" dictionary
|
|
patch = "patch" dictionary
|
|
options = "options" dictionary
|
|
head = "head" dictionary
|
|
connect = "connect" dictionary
|
|
trace = "trace" dictionary
|
|
|
|
headers = "headers" dictionary
|
|
|
|
query = "query" dictionary
|
|
|
|
varsandassert = varsreq | varsres | assert
|
|
varsreq = "vars:pre-request" dictionary
|
|
varsres = "vars:post-response" dictionary
|
|
assert = "assert" assertdictionary
|
|
|
|
body = "body" st* "{" nl* textblock tagend
|
|
bodyjson = "body:json" st* "{" nl* textblock tagend
|
|
bodytext = "body:text" st* "{" nl* textblock tagend
|
|
bodyxml = "body:xml" st* "{" nl* textblock tagend
|
|
bodygraphql = "body:graphql" st* "{" nl* textblock tagend
|
|
bodygraphqlvars = "body:graphql:vars" st* "{" nl* textblock tagend
|
|
|
|
bodyformurlencoded = "body:form-urlencoded" dictionary
|
|
bodymultipart = "body:multipart-form" dictionary
|
|
|
|
script = scriptreq | scriptres
|
|
scriptreq = "script:pre-request" st* "{" nl* textblock tagend
|
|
scriptres = "script:post-response" st* "{" nl* textblock tagend
|
|
tests = "tests" st* "{" nl* textblock tagend
|
|
docs = "docs" st* "{" nl* textblock tagend
|
|
}`);
|
|
|
|
const mapPairListToKeyValPairs = (pairList = []) => {
|
|
if(!pairList.length) {
|
|
return [];
|
|
}
|
|
return _.map(pairList[0], pair => {
|
|
let name = _.keys(pair)[0];
|
|
let value = pair[name];
|
|
let enabled = true;
|
|
if (name && name.length && name.charAt(0) === "~") {
|
|
name = name.slice(1);
|
|
enabled = false;
|
|
}
|
|
|
|
return {
|
|
name,
|
|
value,
|
|
enabled
|
|
};
|
|
});
|
|
};
|
|
|
|
const concatArrays = (objValue, srcValue) => {
|
|
if (_.isArray(objValue) && _.isArray(srcValue)) {
|
|
return objValue.concat(srcValue);
|
|
}
|
|
};
|
|
|
|
const mapPairListToKeyValPair = (pairList = []) => {
|
|
if(!pairList || !pairList.length) {
|
|
return {};
|
|
}
|
|
|
|
return _.merge({}, ...pairList[0]);
|
|
}
|
|
|
|
const sem = grammar.createSemantics().addAttribute('ast', {
|
|
BruFile(tags) {
|
|
if(!tags || !tags.ast || !tags.ast.length) {
|
|
return {};
|
|
}
|
|
|
|
return _.reduce(tags.ast, (result, item) => {
|
|
return _.mergeWith(result, item, concatArrays);
|
|
}, {});
|
|
},
|
|
dictionary(_1, _2, pairlist, _3) {
|
|
return pairlist.ast;
|
|
},
|
|
pairlist(_1, pair, _2, rest, _3) {
|
|
return [pair.ast, ...rest.ast];
|
|
},
|
|
pair(_1, key, _2, _3, _4, value, _5) {
|
|
let res = {};
|
|
res[key.ast] = value.ast ? value.ast.trim() : '';
|
|
return res;
|
|
},
|
|
key(chars) {
|
|
return chars.sourceString ? chars.sourceString.trim() : '';
|
|
},
|
|
value(chars) {
|
|
return chars.sourceString ? chars.sourceString.trim() : '';
|
|
},
|
|
assertdictionary(_1, _2, pairlist, _3) {
|
|
return pairlist.ast;
|
|
},
|
|
assertpairlist(_1, pair, _2, rest, _3) {
|
|
return [pair.ast, ...rest.ast];
|
|
},
|
|
assertpair(_1, key, _2, _3, _4, value, _5) {
|
|
let res = {};
|
|
res[key.ast] = value.ast ? value.ast.trim() : '';
|
|
return res;
|
|
},
|
|
assertkey(chars) {
|
|
return chars.sourceString ? chars.sourceString.trim() : '';
|
|
},
|
|
textblock(line, _1, rest) {
|
|
return [line.ast, ...rest.ast].join('\n');
|
|
},
|
|
textline(chars) {
|
|
return chars.sourceString;
|
|
},
|
|
textchar(char) {
|
|
return char.sourceString;
|
|
},
|
|
nl(_1, _2) {
|
|
return '';
|
|
},
|
|
st(_) {
|
|
return '';
|
|
},
|
|
tagend(_1 ,_2) {
|
|
return '';
|
|
},
|
|
_iter(...elements) {
|
|
return elements.map(e => e.ast);
|
|
},
|
|
meta(_1, dictionary) {
|
|
let meta = mapPairListToKeyValPair(dictionary.ast);
|
|
|
|
if(!meta.seq) {
|
|
meta.seq = 1;
|
|
}
|
|
|
|
if(!meta.type) {
|
|
meta.type = 'http';
|
|
}
|
|
|
|
return {
|
|
meta
|
|
};
|
|
},
|
|
get(_1, dictionary) {
|
|
return {
|
|
http: {
|
|
method: 'get',
|
|
...mapPairListToKeyValPair(dictionary.ast)
|
|
}
|
|
};
|
|
},
|
|
post(_1, dictionary) {
|
|
return {
|
|
http: {
|
|
method: 'post',
|
|
...mapPairListToKeyValPair(dictionary.ast)
|
|
}
|
|
};
|
|
},
|
|
put(_1, dictionary) {
|
|
return {
|
|
http: {
|
|
method: 'put',
|
|
...mapPairListToKeyValPair(dictionary.ast)
|
|
}
|
|
};
|
|
},
|
|
delete(_1, dictionary) {
|
|
return {
|
|
http: {
|
|
method: 'delete',
|
|
...mapPairListToKeyValPair(dictionary.ast)
|
|
}
|
|
};
|
|
},
|
|
patch(_1, dictionary) {
|
|
return {
|
|
http: {
|
|
method: 'patch',
|
|
...mapPairListToKeyValPair(dictionary.ast)
|
|
}
|
|
};
|
|
},
|
|
options(_1, dictionary) {
|
|
return {
|
|
http: {
|
|
method: 'options',
|
|
...mapPairListToKeyValPair(dictionary.ast)
|
|
}
|
|
};
|
|
},
|
|
head(_1, dictionary) {
|
|
return {
|
|
http: {
|
|
method: 'head',
|
|
...mapPairListToKeyValPair(dictionary.ast)
|
|
}
|
|
};
|
|
},
|
|
connect(_1, dictionary) {
|
|
return {
|
|
http: {
|
|
method: 'connect',
|
|
...mapPairListToKeyValPair(dictionary.ast)
|
|
}
|
|
};
|
|
},
|
|
query(_1, dictionary) {
|
|
return {
|
|
query: mapPairListToKeyValPairs(dictionary.ast)
|
|
};
|
|
},
|
|
headers(_1, dictionary) {
|
|
return {
|
|
headers: mapPairListToKeyValPairs(dictionary.ast)
|
|
};
|
|
},
|
|
bodyformurlencoded(_1, dictionary) {
|
|
return {
|
|
body: {
|
|
formUrlEncoded: mapPairListToKeyValPairs(dictionary.ast)
|
|
}
|
|
};
|
|
},
|
|
bodymultipart(_1, dictionary) {
|
|
return {
|
|
body: {
|
|
multipartForm: mapPairListToKeyValPairs(dictionary.ast)
|
|
}
|
|
};
|
|
},
|
|
body(_1, _2, _3, _4, textblock, _5) {
|
|
return {
|
|
http: {
|
|
body: 'json'
|
|
},
|
|
body: {
|
|
json: outdentString(textblock.sourceString)
|
|
}
|
|
};
|
|
},
|
|
bodyjson(_1, _2, _3, _4, textblock, _5) {
|
|
return {
|
|
body: {
|
|
json: outdentString(textblock.sourceString)
|
|
}
|
|
};
|
|
},
|
|
bodytext(_1, _2, _3, _4, textblock, _5) {
|
|
return {
|
|
body: {
|
|
text: outdentString(textblock.sourceString)
|
|
}
|
|
};
|
|
},
|
|
bodyxml(_1, _2, _3, _4, textblock, _5) {
|
|
return {
|
|
body: {
|
|
xml: outdentString(textblock.sourceString)
|
|
}
|
|
};
|
|
},
|
|
bodygraphql(_1, _2, _3, _4, textblock, _5) {
|
|
return {
|
|
body: {
|
|
graphql: {
|
|
query: outdentString(textblock.sourceString)
|
|
}
|
|
}
|
|
};
|
|
},
|
|
bodygraphqlvars(_1, _2, _3, _4, textblock, _5) {
|
|
return {
|
|
body: {
|
|
graphql: {
|
|
variables: outdentString(textblock.sourceString)
|
|
}
|
|
}
|
|
};
|
|
},
|
|
varsreq(_1, dictionary) {
|
|
const vars = mapPairListToKeyValPairs(dictionary.ast);
|
|
_.each(vars, (v) => {
|
|
let name = v.name;
|
|
if (name && name.length && name.charAt(0) === "@") {
|
|
v.name = name.slice(1);
|
|
v.local = true;
|
|
} else {
|
|
v.local = false;
|
|
}
|
|
});
|
|
|
|
return {
|
|
vars: {
|
|
req: vars
|
|
}
|
|
};
|
|
},
|
|
varsres(_1, dictionary) {
|
|
const vars = mapPairListToKeyValPairs(dictionary.ast);
|
|
_.each(vars, (v) => {
|
|
let name = v.name;
|
|
if (name && name.length && name.charAt(0) === "@") {
|
|
v.name = name.slice(1);
|
|
v.local = true;
|
|
} else {
|
|
v.local = false;
|
|
}
|
|
});
|
|
|
|
return {
|
|
vars: {
|
|
res: vars
|
|
}
|
|
};
|
|
},
|
|
assert(_1, dictionary) {
|
|
return {
|
|
assert: mapPairListToKeyValPairs(dictionary.ast)
|
|
};
|
|
},
|
|
scriptreq(_1, _2, _3, _4, textblock, _5) {
|
|
return {
|
|
script: {
|
|
req: outdentString(textblock.sourceString)
|
|
}
|
|
};
|
|
},
|
|
scriptres(_1, _2, _3, _4, textblock, _5) {
|
|
return {
|
|
script: {
|
|
res: outdentString(textblock.sourceString)
|
|
}
|
|
};
|
|
},
|
|
tests(_1, _2, _3, _4, textblock, _5) {
|
|
return {
|
|
tests: outdentString(textblock.sourceString)
|
|
};;
|
|
},
|
|
docs(_1, _2, _3, _4, textblock, _5) {
|
|
return {
|
|
docs: outdentString(textblock.sourceString)
|
|
};
|
|
}
|
|
});
|
|
|
|
const parser = (input) => {
|
|
const match = grammar.match(input);
|
|
|
|
if(match.succeeded()) {
|
|
return sem(match).ast;
|
|
} else {
|
|
throw new Error(match.message);
|
|
}
|
|
}
|
|
|
|
module.exports = parser;
|