diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js index baa5b4ead..5c03e8ef4 100644 --- a/packages/bruno-lang/v2/src/bruToJson.js +++ b/packages/bruno-lang/v2/src/bruToJson.js @@ -56,7 +56,13 @@ const grammar = ohm.grammar(`Bru { // Dictionary Blocks dictionary = st* "{" pairlist? tagend pairlist = optionalnl* pair (~tagend stnl* pair)* (~tagend space)* - pair = st* key st* ":" st* value st* + pair = st* (quoted_key | key) st* ":" st* value st* + disable_char = "~" + quote_char = "\\"" + esc_char = "\\\\" + esc_quote_char = esc_char quote_char + quoted_key_char = ~(quote_char | esc_quote_char | nl) any + quoted_key = disable_char? quote_char (esc_quote_char | quoted_key_char)* quote_char key = keychar* value = list | multilinetextblock | valuechar* @@ -301,6 +307,14 @@ const sem = grammar.createSemantics().addAttribute('ast', { res[key.ast] = value.ast ? value.ast.trim() : ''; return res; }, + esc_quote_char(_1, quote) { + // unescape + return quote.sourceString; + }, + quoted_key(disabled, _1, chars, _2) { + // unquote + return (disabled? disabled.sourceString : "") + chars.ast.join(""); + }, key(chars) { return chars.sourceString ? chars.sourceString.trim() : ''; }, @@ -364,6 +378,9 @@ const sem = grammar.createSemantics().addAttribute('ast', { tagend(_1, _2) { return ''; }, + _terminal(){ + return this.sourceString; + }, multilinetextblockdelimiter(_) { return ''; }, diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js index 26de19b51..ec046674a 100644 --- a/packages/bruno-lang/v2/src/jsonToBru.js +++ b/packages/bruno-lang/v2/src/jsonToBru.js @@ -4,6 +4,10 @@ const { indentString } = require('./utils'); const enabled = (items = [], key = "enabled") => items.filter((item) => item[key]); const disabled = (items = [], key = "enabled") => items.filter((item) => !item[key]); +const quoteKey = (key) => { + const quotableChars = [':', '"', '{', '}', ' ']; + return quotableChars.some(char => key.includes(char)) ? ('"' + key.replaceAll('"', '\\"') + '"') : key; +} // remove the last line if two new lines are found const stripLastLine = (text) => { @@ -121,7 +125,7 @@ const jsonToBru = (json) => { if (enabled(queryParams).length) { bru += `\n${indentString( enabled(queryParams) - .map((item) => `${item.name}: ${item.value}`) + .map((item) => `${quoteKey(item.name)}: ${item.value}`) .join('\n') )}`; } @@ -129,7 +133,7 @@ const jsonToBru = (json) => { if (disabled(queryParams).length) { bru += `\n${indentString( disabled(queryParams) - .map((item) => `~${item.name}: ${item.value}`) + .map((item) => `~${quoteKey(item.name)}: ${item.value}`) .join('\n') )}`; } @@ -151,7 +155,7 @@ const jsonToBru = (json) => { if (enabled(headers).length) { bru += `\n${indentString( enabled(headers) - .map((item) => `${item.name}: ${item.value}`) + .map((item) => `${quoteKey(item.name)}: ${item.value}`) .join('\n') )}`; } @@ -159,7 +163,7 @@ const jsonToBru = (json) => { if (disabled(headers).length) { bru += `\n${indentString( disabled(headers) - .map((item) => `~${item.name}: ${item.value}`) + .map((item) => `~${quoteKey(item.name)}: ${item.value}`) .join('\n') )}`; } @@ -246,7 +250,7 @@ ${indentString(`domain: ${auth?.ntlm?.domain || ''}`)} } `; - } + } if (auth && auth.oauth2) { switch (auth?.oauth2?.grantType) { @@ -496,14 +500,14 @@ ${indentString(body.sparql)} if (enabled(body.formUrlEncoded).length) { const enabledValues = enabled(body.formUrlEncoded) - .map((item) => `${item.name}: ${getValueString(item.value)}`) + .map((item) => `${quoteKey(item.name)}: ${getValueString(item.value)}`) .join('\n'); bru += `${indentString(enabledValues)}\n`; } if (disabled(body.formUrlEncoded).length) { const disabledValues = disabled(body.formUrlEncoded) - .map((item) => `~${item.name}: ${getValueString(item.value)}`) + .map((item) => `~${quoteKey(item.name)}: ${getValueString(item.value)}`) .join('\n'); bru += `${indentString(disabledValues)}\n`; } @@ -524,7 +528,7 @@ ${indentString(body.sparql)} item.contentType && item.contentType !== '' ? ' @contentType(' + item.contentType + ')' : ''; if (item.type === 'text') { - return `${enabled}${item.name}: ${getValueString(item.value)}${contentType}`; + return `${enabled}${quoteKey(item.name)}: ${getValueString(item.value)}${contentType}`; } if (item.type === 'file') { @@ -532,7 +536,7 @@ ${indentString(body.sparql)} const filestr = filepaths.join('|'); const value = `@file(${filestr})`; - return `${enabled}${item.name}: ${value}${contentType}`; + return `${enabled}${quoteKey(item.name)}: ${value}${contentType}`; } }) .join('\n') diff --git a/packages/bruno-lang/v2/tests/dictionary.spec.js b/packages/bruno-lang/v2/tests/dictionary.spec.js index dd9ddd472..8e9197576 100644 --- a/packages/bruno-lang/v2/tests/dictionary.spec.js +++ b/packages/bruno-lang/v2/tests/dictionary.spec.js @@ -81,6 +81,25 @@ headers { expect(output).toEqual(expected); }); + it('should parse single header with empty key', () => { + const input = ` +headers { + : world +}`; + + const output = parser(input); + const expected = { + headers: [ + { + name: '', + value: 'world', + enabled: true + } + ] + }; + expect(output).toEqual(expected); + }); + it('should parse multi headers', () => { const input = ` headers { diff --git a/packages/bruno-lang/v2/tests/fixtures/request.bru b/packages/bruno-lang/v2/tests/fixtures/request.bru index dc70da54b..c10982f6e 100644 --- a/packages/bruno-lang/v2/tests/fixtures/request.bru +++ b/packages/bruno-lang/v2/tests/fixtures/request.bru @@ -17,6 +17,11 @@ get { params:query { apiKey: secret numbers: 998877665 + "key with spaces": is allowed + "colon:parameter": is allowed + "nested escaped \"quote\"": is allowed + "{braces}": is allowed + ~"disabled:colon:parameter": is allowed ~message: hello } @@ -27,6 +32,11 @@ params:path { headers { content-type: application/json Authorization: Bearer 123 + "key with spaces": is allowed + "colon:header": is allowed + "{braces}": is allowed + "nested escaped \"quote\"": is allowed + ~"disabled:colon:header": is allowed ~transaction-id: {{transactionId}} } @@ -104,13 +114,23 @@ body:sparql { body:form-urlencoded { apikey: secret numbers: +91998877665 + "key with spaces": is allowed + "colon:parameter": is allowed + "nested escaped \"quote\"": is allowed + "{braces}": is allowed ~message: hello + ~"disabled colon:parameter": is allowed } body:multipart-form { apikey: secret numbers: +91998877665 + "key with spaces": is allowed + "colon:part": is allowed + "nested escaped \"quote\"": is allowed + "{braces}": is allowed ~message: hello + ~"disabled colon:part": is allowed } body:file { diff --git a/packages/bruno-lang/v2/tests/fixtures/request.json b/packages/bruno-lang/v2/tests/fixtures/request.json index 539eae116..1b6e66497 100644 --- a/packages/bruno-lang/v2/tests/fixtures/request.json +++ b/packages/bruno-lang/v2/tests/fixtures/request.json @@ -24,6 +24,36 @@ "type": "query", "enabled": true }, + { + "name": "key with spaces", + "value": "is allowed", + "type": "query", + "enabled": true + }, + { + "name" : "colon:parameter", + "value" : "is allowed", + "type": "query", + "enabled": true + }, + { + "name" : "nested escaped \"quote\"", + "value" : "is allowed", + "type": "query", + "enabled": true + }, + { + "name": "{braces}", + "value": "is allowed", + "type": "query", + "enabled": true + }, + { + "name" : "disabled:colon:parameter", + "value" : "is allowed", + "type": "query", + "enabled": false + }, { "name": "message", "value": "hello", @@ -48,6 +78,31 @@ "value": "Bearer 123", "enabled": true }, + { + "name": "key with spaces", + "value": "is allowed", + "enabled": true + }, + { + "name": "colon:header", + "value": "is allowed", + "enabled": true + }, + { + "name": "{braces}", + "value": "is allowed", + "enabled": true + }, + { + "name": "nested escaped \"quote\"", + "value": "is allowed", + "enabled": true + }, + { + "name": "disabled:colon:header", + "value": "is allowed", + "enabled": false + }, { "name": "transaction-id", "value": "{{transactionId}}", @@ -118,10 +173,35 @@ "value": "+91998877665", "enabled": true }, + { + "name": "key with spaces", + "value": "is allowed", + "enabled": true + }, + { + "name": "colon:parameter", + "value": "is allowed", + "enabled": true + }, + { + "name": "nested escaped \"quote\"", + "value": "is allowed", + "enabled": true + }, + { + "name": "{braces}", + "value": "is allowed", + "enabled": true + }, { "name": "message", "value": "hello", "enabled": false + }, + { + "name": "disabled colon:parameter", + "value": "is allowed", + "enabled": false } ], "multipartForm": [ @@ -139,12 +219,47 @@ "enabled": true, "type": "text" }, + { + "contentType": "", + "name": "key with spaces", + "value": "is allowed", + "enabled": true, + "type": "text" + }, + { + "contentType": "", + "name": "colon:part", + "value": "is allowed", + "enabled": true, + "type": "text" + }, + { + "contentType": "", + "name": "nested escaped \"quote\"", + "value": "is allowed", + "enabled": true, + "type": "text" + }, + { + "contentType": "", + "name": "{braces}", + "value": "is allowed", + "enabled": true, + "type": "text" + }, { "contentType": "", "name": "message", "value": "hello", "enabled": false, "type": "text" + }, + { + "contentType": "", + "name": "disabled colon:part", + "value": "is allowed", + "enabled": false, + "type": "text" } ], "file" : [