hydrate.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. /**
  2. * Module dependencies
  3. */
  4. var _ = require('@sailshq/lodash');
  5. /**
  6. * Eval stringified functions at the top-level or within value (use `typeSchema` to know
  7. * where to expect functions-- the "lamda" type)
  8. *
  9. * @param {*} value
  10. * @param {*} typeSchema
  11. * @return {*}
  12. */
  13. module.exports = function hydrate (value, typeSchema) {
  14. if (_.isUndefined(typeSchema)) {
  15. throw new Error('rttc.hydrate() requires a 2nd argument (`typeSchema`).');
  16. }
  17. // Deserialize any lamda functions that exist in the provided input value,
  18. // including at the top level.
  19. //
  20. // If this is a lamda type, or something which MIGHT contain a lamda type
  21. // (i.e. nested array or dictionary type schema), we must recursively iterate over the
  22. // type schema looking for lamda types, and when we find them, parse input values as
  23. // stringified functions, converting them to hydrated JavaScript functions.
  24. //
  25. // But otherwise, we just go ahead and bail.
  26. if (typeSchema !== 'lamda' &&
  27. (!_.isObject(typeSchema) || _.isEqual(typeSchema, []) || _.isEqual(typeSchema, {}))) {
  28. return value;
  29. }
  30. return (function parseLamdaInputValues(val, keysSoFar){
  31. var typeHere = keysSoFar.length > 0 ? _.get(typeSchema, keysSoFar.join('.')) : typeSchema;
  32. // If this is supposed to be an array or dictionary, recursively traverse the
  33. // next leg of the type schema
  34. //
  35. // (note that we don't need to worry about circular refs because we've already
  36. // ensured JSON serializability above)
  37. if (_.isArray(typeHere)) {
  38. // if the actual value does not have an array here as expected,
  39. // just stop looking for lamdas this direction (there obviously aren't any,
  40. // and it's not the job of this function to catch any validation issues)
  41. if (!_.isArray(val)) {
  42. return val;
  43. }
  44. // Special case for array generic (`[]`)
  45. if (typeHere.length === 0) {
  46. return val;
  47. }
  48. // Since a type schema array will only have one item, we must iterate over
  49. // the actual value:
  50. return _.reduce(val, function (memo, unused, index){
  51. memo.push(parseLamdaInputValues(val[index], keysSoFar.concat('0') ));
  52. return memo;
  53. }, []);
  54. }
  55. else if (_.isObject(typeHere)){
  56. // if the actual value does not have a dictionary here as expected,
  57. // just stop looking for lamdas this direction (there obviously aren't any,
  58. // and it's not the job of this function to catch any validation issues)
  59. if (!_.isObject(val)) {
  60. return val;
  61. }
  62. // Special case for dictionary generic (`{}`)
  63. if (_.keys(typeHere).length === 0) {
  64. return val;
  65. }
  66. return _.reduce(typeHere, function (memo, unused, subKey){
  67. // If the key from the type schema contains `.`, then fail with an error.
  68. if ((''+subKey).match(/\./)) {
  69. throw new Error('Keys containing dots (`.`) are not currently supported in the type schema for `rttc.hydrate`.');
  70. }
  71. memo[subKey] = parseLamdaInputValues(val[subKey], keysSoFar.concat(subKey));
  72. return memo;
  73. }, {});
  74. }
  75. // If this is supposed to be a lamda, and the actual value is a string,
  76. // parse a function out of it. If anything goes wrong, just pass the value
  77. // through as-is.
  78. else if (typeHere === 'lamda' && _.isString(val)) {
  79. try {
  80. var fn;
  81. // If the lamda string begins with "function", then we'll assume it's a
  82. // complete, stringified function.
  83. if (val.match(/^\s*function/)){
  84. eval('fn='+val);
  85. }
  86. // If the lamda string doesn't begin with "function", then we'll assume it
  87. // is a function body, and build a machine `fn` out of it (assumes standard
  88. // `fn` function signature)
  89. else {
  90. eval('fn=function(inputs, exits, env){'+val+'}');
  91. }
  92. return fn;
  93. }
  94. catch (e){
  95. // Could not parse usable lamda function from provided string-
  96. // so just pass the value through as-is.
  97. return val;
  98. }
  99. }
  100. // Otherwise, just return what we've got
  101. return val;
  102. })(value, []);
  103. };