parse.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. var assert = require('assert');
  2. var jp = require('../');
  3. var util = require('util');
  4. suite('parse', function() {
  5. test('should parse root-only', function() {
  6. var path = jp.parse('$');
  7. assert.deepEqual(path, [ { expression: { type: 'root', value: '$' } } ]);
  8. });
  9. test('parse path for store', function() {
  10. var path = jp.parse('$.store');
  11. assert.deepEqual(path, [
  12. { expression: { type: 'root', value: '$' } },
  13. { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'store' } }
  14. ])
  15. });
  16. test('parse path for the authors of all books in the store', function() {
  17. var path = jp.parse('$.store.book[*].author');
  18. assert.deepEqual(path, [
  19. { expression: { type: 'root', value: '$' } },
  20. { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'store' } },
  21. { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'book' } },
  22. { operation: 'subscript', scope: 'child', expression: { type: 'wildcard', value: '*' } },
  23. { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'author' } }
  24. ])
  25. });
  26. test('parse path for all authors', function() {
  27. var path = jp.parse('$..author');
  28. assert.deepEqual(path, [
  29. { expression: { type: 'root', value: '$' } },
  30. { operation: 'member', scope: 'descendant', expression: { type: 'identifier', value: 'author' } }
  31. ])
  32. });
  33. test('parse path for all authors via subscript descendant string literal', function() {
  34. var path = jp.parse("$..['author']");
  35. assert.deepEqual(path, [
  36. { expression: { type: 'root', value: '$' } },
  37. { operation: 'subscript', scope: 'descendant', expression: { type: 'string_literal', value: 'author' } }
  38. ])
  39. });
  40. test('parse path for all things in store', function() {
  41. var path = jp.parse('$.store.*');
  42. assert.deepEqual(path, [
  43. { expression: { type: 'root', value: '$' } },
  44. { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'store' } },
  45. { operation: 'member', scope: 'child', expression: { type: 'wildcard', value: '*' } }
  46. ])
  47. });
  48. test('parse path for price of everything in the store', function() {
  49. var path = jp.parse('$.store..price');
  50. assert.deepEqual(path, [
  51. { expression: { type: 'root', value: '$' } },
  52. { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'store' } },
  53. { operation: 'member', scope: 'descendant', expression: { type: 'identifier', value: 'price' } }
  54. ])
  55. });
  56. test('parse path for the last book in order via expression', function() {
  57. var path = jp.parse('$..book[(@.length-1)]');
  58. assert.deepEqual(path, [
  59. { expression: { type: 'root', value: '$' } },
  60. { operation: 'member', scope: 'descendant', expression: { type: 'identifier', value: 'book' } },
  61. { operation: 'subscript', scope: 'child', expression: { type: 'script_expression', value: '(@.length-1)' } }
  62. ])
  63. });
  64. test('parse path for the first two books via union', function() {
  65. var path = jp.parse('$..book[0,1]');
  66. assert.deepEqual(path, [
  67. { expression: { type: 'root', value: '$' } },
  68. { operation: 'member', scope: 'descendant', expression: { type: 'identifier', value: 'book' } },
  69. { operation: 'subscript', scope: 'child', expression: { type: 'union', value: [ { expression: { type: 'numeric_literal', value: '0' } }, { expression: { type: 'numeric_literal', value: '1' } } ] } }
  70. ])
  71. });
  72. test('parse path for the first two books via slice', function() {
  73. var path = jp.parse('$..book[0:2]');
  74. assert.deepEqual(path, [
  75. { expression: { type: 'root', value: '$' } },
  76. { operation: 'member', scope: 'descendant', expression: { type: 'identifier', value: 'book' } },
  77. { operation: 'subscript', scope: 'child', expression: { type: 'slice', value: '0:2' } }
  78. ])
  79. });
  80. test('parse path to filter all books with isbn number', function() {
  81. var path = jp.parse('$..book[?(@.isbn)]');
  82. assert.deepEqual(path, [
  83. { expression: { type: 'root', value: '$' } },
  84. { operation: 'member', scope: 'descendant', expression: { type: 'identifier', value: 'book' } },
  85. { operation: 'subscript', scope: 'child', expression: { type: 'filter_expression', value: '?(@.isbn)' } }
  86. ])
  87. });
  88. test('parse path to filter all books with a price less than 10', function() {
  89. var path = jp.parse('$..book[?(@.price<10)]');
  90. assert.deepEqual(path, [
  91. { expression: { type: 'root', value: '$' } },
  92. { operation: 'member', scope: 'descendant', expression: { type: 'identifier', value: 'book' } },
  93. { operation: 'subscript', scope: 'child', expression: { type: 'filter_expression', value: '?(@.price<10)' } }
  94. ])
  95. });
  96. test('parse path to match all elements', function() {
  97. var path = jp.parse('$..*');
  98. assert.deepEqual(path, [
  99. { expression: { type: 'root', value: '$' } },
  100. { operation: 'member', scope: 'descendant', expression: { type: 'wildcard', value: '*' } }
  101. ])
  102. });
  103. test('parse path with leading member', function() {
  104. var path = jp.parse('store');
  105. assert.deepEqual(path, [
  106. { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'store' } }
  107. ])
  108. });
  109. test('parse path with leading member and followers', function() {
  110. var path = jp.parse('Request.prototype.end');
  111. assert.deepEqual(path, [
  112. { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'Request' } },
  113. { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'prototype' } },
  114. { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'end' } }
  115. ])
  116. });
  117. test('parser ast is reinitialized after parse() throws', function() {
  118. assert.throws(function() { var path = jp.parse('store.book...') })
  119. var path = jp.parse('$..price');
  120. assert.deepEqual(path, [
  121. { "expression": { "type": "root", "value": "$" } },
  122. { "expression": { "type": "identifier", "value": "price" }, "operation": "member", "scope": "descendant"}
  123. ])
  124. });
  125. });
  126. suite('parse-negative', function() {
  127. test('parse path with leading member component throws', function() {
  128. assert.throws(function(e) { var path = jp.parse('.store') }, /Expecting 'DOLLAR'/)
  129. });
  130. test('parse path with leading descendant member throws', function() {
  131. assert.throws(function() { var path = jp.parse('..store') }, /Expecting 'DOLLAR'/)
  132. });
  133. test('leading script throws', function() {
  134. assert.throws(function() { var path = jp.parse('()') }, /Unrecognized text/)
  135. });
  136. test('first time friendly error', function() {
  137. assert.throws(function() { (new jp.JSONPath).parse('$...') }, /Expecting 'STAR'/)
  138. });
  139. });