diff --git a/src/http-middlewares/before/add-query-params.js b/src/http-middlewares/before/add-query-params.js index 83ee03a..92eb96f 100644 --- a/src/http-middlewares/before/add-query-params.js +++ b/src/http-middlewares/before/add-query-params.js @@ -1,21 +1,7 @@ 'use strict'; const querystring = require('querystring'); - -const isCurlies = /{{.*?}}/g; -const shouldPruneQueryParam = value => - value === '' || - value === null || - value === undefined || - isCurlies.test(value); - -// Mutates the object (it'll be deleted later anyway) -const pruneMissingQueryParams = params => - Object.keys(params).forEach(param => { - if (shouldPruneQueryParam(params[param])) { - delete params[param]; - } - }); +const { normalizeEmptyParamField } = require('../../tools/cleaner'); const hasQueryParams = ({ params = {} }) => Object.keys(params).length; @@ -26,9 +12,7 @@ const addQueryParams = req => { if (hasQueryParams(req)) { const splitter = req.url.includes('?') ? '&' : '?'; - if (req.removeMissingValuesFrom.params) { - pruneMissingQueryParams(req.params); - } + Object.entries(req.params).forEach(normalizeEmptyParamField(req)); const stringifiedParams = querystring.stringify(req.params); diff --git a/src/http-middlewares/before/prepare-request.js b/src/http-middlewares/before/prepare-request.js index 0c2b842..08eb7df 100644 --- a/src/http-middlewares/before/prepare-request.js +++ b/src/http-middlewares/before/prepare-request.js @@ -4,7 +4,11 @@ const _ = require('lodash'); const stream = require('stream'); const querystring = require('querystring'); -const cleaner = require('../../tools/cleaner'); +const { + createBundleBank, + normalizeEmptyBodyField, + recurseReplaceBank +} = require('../../tools/cleaner'); const requestMerge = require('../../tools/request-merge'); const { getContentType, @@ -56,6 +60,8 @@ const coerceBody = req => { } else if (Buffer.isBuffer(req.body)) { // leave a buffer alone! } else if (req.body && !_.isString(req.body)) { + Object.entries(req.body).forEach(normalizeEmptyBodyField(req)); + // this is a general - popular fallback req.body = JSON.stringify(req.body); @@ -120,11 +126,8 @@ const prepareRequest = function(req) { // replace {{curlies}} in the request if (req.replace) { - const bank = cleaner.createBundleBank( - input._zapier.app, - input._zapier.event - ); - req = cleaner.recurseReplaceBank(req, bank); + const bank = createBundleBank(input._zapier.app, input._zapier.event); + req = recurseReplaceBank(req, bank); } req = coerceBody(req); diff --git a/src/tools/cleaner.js b/src/tools/cleaner.js index 2d079ad..633cc72 100644 --- a/src/tools/cleaner.js +++ b/src/tools/cleaner.js @@ -110,9 +110,39 @@ const createBundleBank = (appRaw, event = {}) => { const maskOutput = output => _.pick(output, 'results', 'status'); +const normalizeEmptyRequestField = (shouldCleanup, field) => req => { + const handleEmpty = req.removeMissingValuesFrom[field] + ? key => delete req[field][key] + : key => (req[field][key] = ''); + + return ([key, value]) => { + if (shouldCleanup(value)) { + handleEmpty(key); + } + }; +}; + +const isCurlies = /{{.*?}}/; +const isEmptyQueryParam = value => + value === '' || + value === null || + value === undefined || + isCurlies.test(value); + +const normalizeEmptyParamField = normalizeEmptyRequestField( + isEmptyQueryParam, + 'params' +); +const normalizeEmptyBodyField = normalizeEmptyRequestField( + v => isCurlies.test(v), + 'body' +); + module.exports = { - recurseCleanFuncs, - recurseReplaceBank, createBundleBank, - maskOutput + maskOutput, + normalizeEmptyBodyField, + normalizeEmptyParamField, + recurseCleanFuncs, + recurseReplaceBank }; diff --git a/test/create-request-client.js b/test/create-request-client.js index 240ac22..2b2edc5 100644 --- a/test/create-request-client.js +++ b/test/create-request-client.js @@ -374,7 +374,7 @@ describe('request client', () => { }); describe('adds query params', () => { - it('should not omit empty params by default', () => { + it('should replace remaining curly params with empty string by default', () => { const request = createAppRequestClient(input); return request({ url: 'https://httpbin.org/get', @@ -387,18 +387,18 @@ describe('request client', () => { const response = JSON.parse(JSON.stringify(responseBefore)); response.json.args.something.should.eql(''); - response.json.args.really.should.eql('{{bundle.inputData.really}}'); + response.json.args.really.should.eql(''); response.json.args.cool.should.eql('true'); response.status.should.eql(200); const body = JSON.parse(response.content); body.url.should.eql( - 'https://httpbin.org/get?something=&really={{bundle.inputData.really}}&cool=true' + 'https://httpbin.org/get?something=&really=&cool=true' ); }); }); - it('should not omit empty params when set as false', () => { + it('should replace remaining curly params with empty string when set as false', () => { const request = createAppRequestClient(input); return request({ url: 'https://httpbin.org/get', @@ -414,13 +414,13 @@ describe('request client', () => { const response = JSON.parse(JSON.stringify(responseBefore)); response.json.args.something.should.eql(''); - response.json.args.really.should.eql('{{bundle.inputData.really}}'); + response.json.args.really.should.eql(''); response.json.args.cool.should.eql('true'); response.status.should.eql(200); const body = JSON.parse(response.content); body.url.should.eql( - 'https://httpbin.org/get?something=&really={{bundle.inputData.really}}&cool=true' + 'https://httpbin.org/get?something=&really=&cool=true' ); }); }); @@ -460,6 +460,7 @@ describe('request client', () => { should(response.json.args.foo).eql(undefined); should(response.json.args.bar).eql(undefined); should(response.json.args.empty).eql(undefined); + should(response.json.args.really).eql(undefined); response.json.args.cool.should.eql('false'); response.json.args.zzz.should.eql('[]'); response.json.args.yyy.should.eql('{}'); @@ -574,6 +575,7 @@ describe('request client', () => { }).then(response => { const { json } = response.json; + should(json.empty).eql(undefined); json.number.should.eql(123); json.bool.should.eql(true); json.float.should.eql(123.456); @@ -581,6 +583,49 @@ describe('request client', () => { }); }); + it('should remove keys from body for empty values if configured to', () => { + const event = { + bundle: { + inputData: { + name: 'Burgundy' + } + } + }; + const bodyInput = createInput({}, event, testLogger); + const request = createAppRequestClient(bodyInput); + return request({ + url: 'https://httpbin.org/post', + method: 'POST', + body: { + name: '{{bundle.inputData.name}}', + empty: '{{bundle.inputData.empty}}' + }, + removeMissingValuesFrom: { + body: true + } + }).then(response => { + const { json } = response.json; + + should(json.empty).eql(undefined); + json.name.should.eql('Burgundy'); + }); + }); + + it('should replace curlies with an empty string by default', () => { + const request = createAppRequestClient(input); + return request({ + url: 'https://httpbin.org/post', + method: 'POST', + body: { + empty: '{{bundle.inputData.empty}}' + } + }).then(response => { + const { json } = response.json; + + should(json.empty).eql(''); + }); + }); + it('should interpolate strings', () => { const event = { bundle: {