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 25, 2019
1 parent 4400bee commit 53472e2
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 8 deletions.
39 changes: 33 additions & 6 deletions src/tools/cleaner.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
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 {
flattenPaths,
getObjectType,
isPlainObj,
recurseReplace
} = require('./data');

const DEFAULT_BUNDLE = {
authData: {},
Expand Down Expand Up @@ -43,11 +46,35 @@ 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 = key.replace(/[-[\]/{}()\\*+?.^$|]/g, '\\$&');
const matchesKey = new RegExp(escapedKey, 'g');

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

const matchesCurlies = /({{.*?}})/;
const valueParts = out.split(matchesCurlies).filter(Boolean);
const replacementValue = bank[key];
const isPartOfString = !matchesCurlies.test(out) || valueParts.length > 1;
const shouldThrowTypeError =
isPartOfString &&
(Array.isArray(replacementValue) || _.isPlainObject(replacementValue));

if (shouldThrowTypeError) {
throw new TypeError(
`Cannot reliably interpolate objects or arrays into a string. We received an ${getObjectType(
replacementValue
)}:\n"${replacementValue}"`
);
}

out = isPartOfString
? valueParts.join('').replace(matchesKey, replacementValue)
: 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 (Array.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 reliably interpolate objects or arrays into a string. We received an Array:\n"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 53472e2

Please sign in to comment.