get-path-info.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. /**
  2. * Module dependencies
  3. */
  4. var _ = require('@sailshq/lodash');
  5. var TYPES = require('./helpers/types');
  6. var dehydrate = require('./dehydrate');
  7. /**
  8. * Given an exemplar schema and a keypath, return information
  9. * about the specified segment.
  10. *
  11. * If the path is inside of a generic, then the exemplar is '*',
  12. * and this path is optional. If the path is inside of a `ref`,
  13. * then the exemplar is '===', and this path is optional.
  14. *
  15. *
  16. * @param {*} schema - an exemplar
  17. * @param {String} path - a valid dot-deliminited path, where empty string ('') is the root (e.g. "" or "foo" or "foo.bar" or "foo.0.bar.0.baz")
  18. *
  19. * @returns {Dictionary}
  20. * @property {*} exemplar
  21. * @property {Boolean} optional
  22. *
  23. * @throws E_MALFORMED_PATH
  24. * If keypath is invalid for the provided schema
  25. *
  26. * @throws E_UNREACHABLE
  27. * if keypath is unreachable (meaning it is not allowed in the schema)
  28. */
  29. module.exports = function getPathInfo (schema, path) {
  30. // Dehydrate the schema to avoid circular recursion
  31. var dehydratedSchema = dehydrate(schema);
  32. // Derive an array of "hops" from the provided keypath.
  33. var hops = (path === '') ? [] : path.split('.');
  34. // These variables are used by the logic below.
  35. var currentExemplar = dehydratedSchema;
  36. // By default the path is not optional.
  37. var optional = false;
  38. _.each(hops, function dereferenceEach(hop){
  39. if (_.isArray(currentExemplar)) {
  40. if (!_.isFinite(+hop)) {
  41. // If the hop cannot be cast to a positive integer,
  42. // something funny is going on.
  43. throw (function (){
  44. var err = new Error('Malformed keypath: "'+path+'" (non-numeric hop `'+hop+'` is not a valid array index)');
  45. err.code = 'E_MALFORMED_PATH';
  46. return err;
  47. })();
  48. }
  49. // generic array
  50. // (same thing as `['*']`)
  51. if (_.isEqual(currentExemplar, [])) {
  52. optional = true;
  53. currentExemplar = TYPES.json.getExemplar();
  54. }
  55. // patterned array
  56. else {
  57. optional = false;
  58. if (_.isUndefined(currentExemplar[0])) {
  59. throw (function (){
  60. var err = new Error('Keypath: "'+path+'" is unreachable in schema');
  61. err.code = 'E_UNREACHABLE';
  62. return err;
  63. })();
  64. }
  65. else {
  66. currentExemplar = currentExemplar[0];
  67. }
  68. }
  69. }
  70. else if (_.isObject(currentExemplar)) {
  71. // generic dictionary
  72. if (_.isEqual(currentExemplar, {})) {
  73. optional = true;
  74. currentExemplar = TYPES.json.getExemplar();
  75. }
  76. // facted dictionary
  77. else {
  78. optional = false;
  79. if (_.isUndefined(currentExemplar[hop])) {
  80. throw (function (){
  81. var err = new Error('Keypath: "'+path+'" is unreachable in schema');
  82. err.code = 'E_UNREACHABLE';
  83. return err;
  84. })();
  85. }
  86. else {
  87. currentExemplar = currentExemplar[hop];
  88. }
  89. }
  90. }
  91. // generic json
  92. else if (TYPES.json.isExemplar(currentExemplar)) {
  93. optional = true;
  94. currentExemplar = TYPES.json.getExemplar();
  95. }
  96. // mutable reference
  97. else if (TYPES.ref.isExemplar(currentExemplar)) {
  98. optional = true;
  99. currentExemplar = TYPES.ref.getExemplar();
  100. }
  101. // lamda
  102. else if (TYPES.lamda.isExemplar(currentExemplar)) {
  103. throw (function (){
  104. var err = new Error('Keypath: "'+path+'" is unreachable in schema');
  105. err.code = 'E_UNREACHABLE';
  106. return err;
  107. })();
  108. }
  109. // primitive
  110. else {
  111. throw (function (){
  112. var err = new Error('Keypath: "'+path+'" is unreachable in schema');
  113. err.code = 'E_UNREACHABLE';
  114. return err;
  115. })();
  116. }
  117. });
  118. return {
  119. exemplar: currentExemplar,
  120. optional: optional
  121. };
  122. };