Skip to content
This repository has been archived by the owner on Jul 12, 2019. It is now read-only.

Commit

Permalink
[PDE-683] Resolve curlies to their original data type
Browse files Browse the repository at this point in the history
  • Loading branch information
Steve Gardner committed Feb 24, 2019
1 parent 4400bee commit d21cdee
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 8 deletions.
35 changes: 29 additions & 6 deletions src/tools/cleaner.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
const _ = require('lodash');
const { defaults, pick, pipe } = require('lodash/fp');

const isPlainObj = require('./data').isPlainObj;
const recurseReplace = require('./data').recurseReplace;
const flattenPaths = require('./data').flattenPaths;
const { isPlainObj, getObjectType } = require('./data');
const { recurseReplace } = require('./data');
const { flattenPaths } = require('./data');

const DEFAULT_BUNDLE = {
authData: {},
Expand Down Expand Up @@ -43,11 +43,34 @@ const recurseReplaceBank = (obj, bank = {}) => {
if (typeof out !== 'string') {
return out;
}

Object.keys(bank).forEach(key => {
// Escape characters (ex. {{foo}} => \\{\\{foo\\}\\} )
const s = String(key).replace(/[-[\]/{}()\\*+?.^$|]/g, '\\$&');
const re = new RegExp(s, 'g');
out = out.replace(re, bank[key]);
const escapedKey = String(key).replace(/[-[\]/{}()\\*+?.^$|]/g, '\\$&');
const matchesKey = new RegExp(escapedKey, 'g');

if (!matchesKey.test(out)) {
return;
}

const valueParts = out.split(/({{.*?}})/).filter(Boolean);
const replacementValue = bank[key];

if (valueParts.length > 1) {
if (_.isArray(replacementValue) || _.isPlainObject(replacementValue)) {
throw new TypeError(
[
'Cannot interpolate objects or arrays into a string.',
`You've sent an ${getObjectType(replacementValue)},`,
`which will get coerced to ${replacementValue}`
].join(' ')
);
}

out = valueParts.join('').replace(matchesKey, replacementValue);
} else {
out = replacementValue;
}
});

return out;
Expand Down
16 changes: 14 additions & 2 deletions src/tools/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ const isPlainObj = o => {

const comparison = (obj, needle) => obj === needle;

const getObjectType = obj => {
if (_.isPlainObject(obj)) {
return 'Object';
}

if (_.isArray(obj)) {
return 'Array';
}

return _.capitalize(typeof obj);
};

// Returns a path for the deeply nested haystack where
// you could find the needle. If the needle is a plain
// object we try _.isEqual (which could be slow!).
Expand Down Expand Up @@ -130,8 +142,7 @@ const recurseExtract = (obj, matcher) => {
const _IGNORE = {};

// Flatten a nested object.
const flattenPaths = (data, sep) => {
sep = sep || '.';
const flattenPaths = (data, sep = '.') => {
const out = {};
const recurse = (obj, prop) => {
prop = prop || '';
Expand Down Expand Up @@ -179,6 +190,7 @@ module.exports = {
findMapDeep,
flattenPaths,
genId,
getObjectType,
isPlainObj,
jsonCopy,
memoizedFindMapDeep,
Expand Down
113 changes: 113 additions & 0 deletions test/create-request-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -534,4 +534,117 @@ describe('request client', () => {
});
});
});

describe('resolves body and header curlies', () => {
it('should keep valid data types', () => {
const event = {
bundle: {
inputData: {
number: 123,
bool: true,
float: 123.456,
arr: [1, 2, 3]
}
}
};
const bodyInput = createInput({}, event, testLogger);
const request = createAppRequestClient(bodyInput);
return request({
url: 'http://zapier-httpbin.herokuapp.com/post',
method: 'POST',
body: {
number: '{{bundle.inputData.number}}',
bool: '{{bundle.inputData.bool}}',
float: '{{bundle.inputData.float}}',
arr: '{{bundle.inputData.arr}}'
}
}).then(response => {
const { json } = response.json;

json.number.should.eql(123);
json.bool.should.eql(true);
json.float.should.eql(123.456);
json.arr.should.eql([1, 2, 3]);
});
});

it('should interpolate strings', () => {
const event = {
bundle: {
inputData: {
resourceId: 123
},
authData: {
access_token: 'Let me in'
}
}
};
const bodyInput = createInput({}, event, testLogger);
const request = createAppRequestClient(bodyInput);
return request({
url: 'http://zapier-httpbin.herokuapp.com/post',
method: 'POST',
body: {
message: 'We just got #{{bundle.inputData.resourceId}}'
},
headers: {
Authorization: 'Bearer {{bundle.authData.access_token}}'
}
}).then(response => {
const { json, headers } = response.json;

json.message.should.eql('We just got #123');
headers.Authorization.should.eql('Bearer Let me in');
});
});

it('should throw when interpolating a string with an array', () => {
const event = {
bundle: {
inputData: {
badData: [1, 2, 3]
}
}
};
const bodyInput = createInput({}, event, testLogger);
const request = createAppRequestClient(bodyInput);
return request({
url: 'http://zapier-httpbin.herokuapp.com/post',
method: 'POST',
body: {
message: 'No arrays, thank you: {{bundle.inputData.badData}}'
}
}).should.be.rejectedWith(
"Cannot interpolate objects or arrays into a string. You've sent an Array, which will get coerced to 1,2,3"
);
});

it('should send flatten objects', () => {
const event = {
bundle: {
inputData: {
address: {
street: '123 Zapier Way',
city: 'El Mundo'
}
}
}
};
const bodyInput = createInput({}, event, testLogger);
const request = createAppRequestClient(bodyInput);
return request({
url: 'http://zapier-httpbin.herokuapp.com/post',
method: 'POST',
body: {
streetAddress: '{{bundle.inputData.address.street}}',
city: '{{bundle.inputData.address.city}}'
}
}).then(response => {
const { json } = response.json;

json.streetAddress.should.eql('123 Zapier Way');
json.city.should.eql('El Mundo');
});
});
});
});

0 comments on commit d21cdee

Please sign in to comment.