janeway-jsonpath 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +23 -2
- data/bin/janeway +1 -2
- data/lib/janeway/ast/array_slice_selector.rb +70 -18
- data/lib/janeway/ast/binary_operator.rb +3 -0
- data/lib/janeway/ast/child_segment.rb +4 -6
- data/lib/janeway/ast/current_node.rb +2 -6
- data/lib/janeway/ast/descendant_segment.rb +3 -8
- data/lib/janeway/ast/filter_selector.rb +9 -3
- data/lib/janeway/ast/function.rb +21 -1
- data/lib/janeway/ast/identifier.rb +1 -1
- data/lib/janeway/ast/index_selector.rb +6 -7
- data/lib/janeway/ast/name_selector.rb +12 -10
- data/lib/janeway/ast/number.rb +1 -1
- data/lib/janeway/ast/query.rb +22 -6
- data/lib/janeway/ast/root_node.rb +2 -6
- data/lib/janeway/ast/selector.rb +4 -1
- data/lib/janeway/ast/wildcard_selector.rb +8 -12
- data/lib/janeway/error.rb +7 -1
- data/lib/janeway/functions/count.rb +8 -3
- data/lib/janeway/functions/length.rb +8 -2
- data/lib/janeway/functions/match.rb +4 -2
- data/lib/janeway/functions/search.rb +4 -2
- data/lib/janeway/functions/value.rb +3 -1
- data/lib/janeway/functions.rb +4 -3
- data/lib/janeway/interpreter.rb +102 -110
- data/lib/janeway/lexer.rb +35 -24
- data/lib/janeway/parser.rb +107 -96
- data/lib/janeway/version.rb +1 -1
- data/lib/janeway.rb +5 -5
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10f6ca7766f713b6272f971fd9f6b119895a59385dcebf0cfdb6e0b7d7b27de8
|
4
|
+
data.tar.gz: b33f60624ea2fc41740c888ce00c818c8a873ca56c3d8f85f49b00abee7ea466
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2cf900d4b424ba23d436a6a8d3714e274ad5b6198c3062ec73760d489634105effed9350c665c29673032f899a855ea7d946b85b583b83e3b0d0730ec1e2244f
|
7
|
+
data.tar.gz: 5b28155e3e56346e95f512688cde87310329626d8deb50af76f7ff2d59529f3bb8816b1c62d2bc402266feda338b29bd7197835543c96ea2930d572199cdb293
|
data/README.md
CHANGED
@@ -24,7 +24,7 @@ This project includes:
|
|
24
24
|
|
25
25
|
### Non-goals
|
26
26
|
|
27
|
-
* Changing behavior to follow [other implementations]
|
27
|
+
* Changing behavior to follow [other implementations](https://cburgmer.github.io/json-path-comparison/)
|
28
28
|
|
29
29
|
The JSONPath RFC was in draft status for a long time and has seen many changes.
|
30
30
|
There are many implementations based on older drafts, or which add features that were never in the RFC at all.
|
@@ -35,9 +35,30 @@ The RFC was finalized in 2024, and it has a rigorous [suite of compliance tests.
|
|
35
35
|
|
36
36
|
With these tools it is possible to have JSONPath implementations in many languages with identical behavior.
|
37
37
|
|
38
|
+
### Differences from joshbuddy/jsonpath
|
39
|
+
|
40
|
+
The only other serious ruby implementation of jsonpath is joshbuddy/jsonpath.
|
41
|
+
This implementation has been around for a long time.
|
42
|
+
I personally have used it in a software project for several years.
|
43
|
+
Here are differences I've found in porting this application from joshbuddy/jsonpath to janeway.
|
44
|
+
|
45
|
+
* joshbuddy/jsonpath allows unquoted strings in filter comparisons.
|
46
|
+
Examples:
|
47
|
+
$ jsonpath '$.store.book[?(@.category==reference)]' example.json
|
48
|
+
$ janeway '$.store.book[?(@.category=="reference")]' example.json
|
49
|
+
|
50
|
+
* joshbuddy/jsonpath allows root selector to be omitted
|
51
|
+
grid.first('gid').to_i
|
52
|
+
|
53
|
+
$ jsonpath 'store' example.json
|
54
|
+
$ janeway '$.store' example.json
|
55
|
+
|
56
|
+
*
|
57
|
+
$ jsonpath '$.nodes..services..id' spec/resources/grid-full.json
|
58
|
+
|
38
59
|
### Implementation
|
39
60
|
|
40
61
|
Functionality is based on [IETF RFC 9535, "JSONPath: Query Expressions for JSON"](https://www.rfc-editor.org/rfc/rfc9535.html#filter-selector)
|
41
62
|
The examples in the RFC have been implemented as unit tests.
|
42
63
|
|
43
|
-
For details not covered in the RFC, it does the most reasonable thing based on [what other JSONPath parsers do
|
64
|
+
For details not covered in the RFC, it does the most reasonable thing based on [what other JSONPath parsers do](https://cburgmer.github.io/json-path-comparison/). However, this is always secondary to following the RFC. Many of the popular behaviors there contradict the RFC, so it has to be one or the other.
|
data/bin/janeway
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'janeway'
|
3
4
|
require_relative 'selector'
|
4
5
|
|
5
6
|
module Janeway
|
@@ -25,12 +26,19 @@ module Janeway
|
|
25
26
|
# @param end_ [Integer, nil]
|
26
27
|
# @param step [Integer, nil]
|
27
28
|
def initialize(start = nil, end_ = nil, step = nil)
|
29
|
+
super(nil)
|
30
|
+
|
31
|
+
# Check arguments
|
28
32
|
[start, end_, step].each do |arg|
|
29
|
-
|
33
|
+
unless [NilClass, Integer].include?(arg.class)
|
34
|
+
raise Error, "Array slice selector index must be integer or nothing, got #{arg.inspect}"
|
35
|
+
end
|
36
|
+
next unless arg
|
30
37
|
|
31
|
-
|
38
|
+
# Check integer size limits
|
39
|
+
raise Error, "Array slice selector value too small: #{arg.inspect}" if arg < INTEGER_MIN
|
40
|
+
raise Error, "Array slice selector value too large: #{arg.inspect}" if arg > INTEGER_MAX
|
32
41
|
end
|
33
|
-
super(nil)
|
34
42
|
|
35
43
|
# Nil values are kept to indicate that the "default" values is to be used.
|
36
44
|
# The interpreter selects the actual values.
|
@@ -58,14 +66,8 @@ module Janeway
|
|
58
66
|
#
|
59
67
|
# @param input_size [Integer]
|
60
68
|
# @return [Integer]
|
61
|
-
def
|
62
|
-
|
63
|
-
@start.clamp(0, input_size)
|
64
|
-
elsif step.positive?
|
65
|
-
0
|
66
|
-
else # negative step
|
67
|
-
input_size - 1 # last index of input
|
68
|
-
end
|
69
|
+
def upper_index(input_size)
|
70
|
+
calculate_index_values(input_size).last
|
69
71
|
end
|
70
72
|
|
71
73
|
# Return the end index to use for stepping through the array, based on a specified array size
|
@@ -73,15 +75,65 @@ module Janeway
|
|
73
75
|
#
|
74
76
|
# @param input_size [Integer]
|
75
77
|
# @return [Integer]
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
78
|
+
def lower_index(input_size)
|
79
|
+
calculate_index_values(input_size).first
|
80
|
+
end
|
81
|
+
|
82
|
+
# Assign lower and upper bounds to instance variables, based on the input array size.
|
83
|
+
# @see https://www.rfc-editor.org/rfc/rfc9535.html#section-2.3.4.2.2-3
|
84
|
+
#
|
85
|
+
# @param input_size [Integer]
|
86
|
+
def calculate_index_values(input_size)
|
87
|
+
if step >= 0
|
88
|
+
start = @start || 0
|
89
|
+
end_ = @end || input_size
|
90
|
+
else
|
91
|
+
start = @start || (input_size - 1)
|
92
|
+
end_ = @end || ((-1 * input_size) - 1)
|
93
|
+
end
|
94
|
+
|
95
|
+
bounds(start, end_, step, input_size)
|
96
|
+
end
|
97
|
+
|
98
|
+
# NOTE: Conversion of start:end:step to array indexes is defined as pseudocode
|
99
|
+
# in the IETF spec.
|
100
|
+
# The methods #normalize and #bounds are ruby implementations of that code.
|
101
|
+
# Don't make changes here without referring to the original code in the spec.
|
102
|
+
# @see https://www.rfc-editor.org/rfc/rfc9535.html#section-2.3.4.2.2-6
|
103
|
+
|
104
|
+
# IETF: Slice expression parameters start and end are not directly usable
|
105
|
+
# as slice bounds and must first be normalized.
|
106
|
+
#
|
107
|
+
# @param index [Integer]
|
108
|
+
# @param len [Integer]
|
109
|
+
def normalize(index, len)
|
110
|
+
return index if index >= 0
|
111
|
+
|
112
|
+
len + index
|
113
|
+
end
|
114
|
+
|
115
|
+
# IETF: Slice expression parameters start and end are used to derive
|
116
|
+
# slice bounds lower and upper. The direction of the iteration, defined
|
117
|
+
# by the sign of step, determines which of the parameters is the lower
|
118
|
+
# bound and which is the upper bound:¶
|
119
|
+
# @see https://www.rfc-editor.org/rfc/rfc9535.html#section-2.3.4.2.2-9
|
120
|
+
# @param start [Integer] start index, normalized
|
121
|
+
# @param end_ [Integer] end index, normalized
|
122
|
+
# @param step [Integer] step value
|
123
|
+
# @param len [Integer] length of input array
|
124
|
+
def bounds(start, end_, step, len)
|
125
|
+
n_start = normalize(start, len)
|
126
|
+
n_end = normalize(end_, len)
|
127
|
+
|
128
|
+
if step >= 0
|
129
|
+
lower = n_start.clamp(0, len)
|
130
|
+
upper = n_end.clamp(0, len)
|
82
131
|
else
|
83
|
-
|
132
|
+
upper = n_start.clamp(-1, len - 1)
|
133
|
+
lower = n_end.clamp(-1, len - 1)
|
84
134
|
end
|
135
|
+
|
136
|
+
[lower, upper]
|
85
137
|
end
|
86
138
|
|
87
139
|
# ignores the brackets: argument, this always needs surrounding brackets
|
@@ -19,6 +19,9 @@ module Janeway
|
|
19
19
|
def left=(expr)
|
20
20
|
if comparison_operator? && !(expr.literal? || expr.singular_query?)
|
21
21
|
raise Error, "Expression #{expr} does not produce a singular value for #{operator_to_s} comparison"
|
22
|
+
elsif comparison_operator? && expr.is_a?(AST::Function) && !expr.literal?
|
23
|
+
msg = "Function #{expr} returns a non-comparable value which is not usable for #{operator_to_s} comparison"
|
24
|
+
raise Error, msg
|
22
25
|
end
|
23
26
|
|
24
27
|
# Compliance test suite requires error for this, but don't have go so far as to bar every literal
|
@@ -17,12 +17,10 @@ module Janeway
|
|
17
17
|
def_delegators :@value, :size, :first, :last, :each, :map, :empty?
|
18
18
|
|
19
19
|
# Subsequent expression that modifies the result of this selector list.
|
20
|
-
|
21
|
-
attr_accessor :child
|
20
|
+
attr_accessor :next
|
22
21
|
|
23
22
|
def initialize
|
24
23
|
super([]) # @value holds the expressions in the selector
|
25
|
-
@child = nil # @child is the next expression, which modifies the output of this one
|
26
24
|
end
|
27
25
|
|
28
26
|
# Add a selector to the list
|
@@ -33,15 +31,15 @@ module Janeway
|
|
33
31
|
end
|
34
32
|
|
35
33
|
def to_s(with_child: true)
|
36
|
-
str = @value.map { |selector| selector.to_s(brackets: false) }.join(', ')
|
37
|
-
with_child ? "[#{str}]#{@
|
34
|
+
str = @value.map { |selector| selector.to_s(brackets: false, dot_prefix: false) }.join(', ')
|
35
|
+
with_child ? "[#{str}]#{@next}" : "[#{str}]"
|
38
36
|
end
|
39
37
|
|
40
38
|
# @param level [Integer]
|
41
39
|
# @return [Array]
|
42
40
|
def tree(level)
|
43
41
|
msg = format('[%s]', @value.map(&:to_s).join(', '))
|
44
|
-
[indented(level, msg), @
|
42
|
+
[indented(level, msg), @next&.tree(level + 1)]
|
45
43
|
end
|
46
44
|
end
|
47
45
|
end
|
@@ -30,11 +30,7 @@ module Janeway
|
|
30
30
|
# Construct accepts an optional Selector which will be applied to the "current" node
|
31
31
|
class CurrentNode < Janeway::AST::Expression
|
32
32
|
def to_s
|
33
|
-
|
34
|
-
"@.#{@value.to_s(brackets: false)}"
|
35
|
-
else
|
36
|
-
"@#{@value}"
|
37
|
-
end
|
33
|
+
"@#{@value}"
|
38
34
|
end
|
39
35
|
|
40
36
|
# True if this is the root of a singular-query.
|
@@ -48,7 +44,7 @@ module Janeway
|
|
48
44
|
selector = @value
|
49
45
|
loop do
|
50
46
|
selector_types << selector.class
|
51
|
-
selector = selector.
|
47
|
+
selector = selector.next
|
52
48
|
break unless selector
|
53
49
|
end
|
54
50
|
allowed = [AST::IndexSelector, AST::NameSelector]
|
@@ -16,16 +16,11 @@ module Janeway
|
|
16
16
|
# $..[*, *] All values, twice non-deterministic order
|
17
17
|
# $..[0, 1] Multiple segments
|
18
18
|
class DescendantSegment < Janeway::AST::Selector
|
19
|
-
|
20
|
-
|
21
|
-
def initialize(selector)
|
22
|
-
super
|
23
|
-
|
24
|
-
@child = nil
|
25
|
-
end
|
19
|
+
# Subsequent expression that modifies the result of this selector list.
|
20
|
+
attr_accessor :next
|
26
21
|
|
27
22
|
def to_s
|
28
|
-
"..#{@
|
23
|
+
"..#{@next&.to_s(dot_prefix: false)}"
|
29
24
|
end
|
30
25
|
|
31
26
|
# @return [AST::Selector]
|
@@ -47,14 +47,20 @@ module Janeway
|
|
47
47
|
#
|
48
48
|
# @example: $.store[@.price < 10]
|
49
49
|
class FilterSelector < Janeway::AST::Selector
|
50
|
-
|
51
|
-
|
50
|
+
# @param brackets [Boolean] include brackets around selector
|
51
|
+
# ** ignores keyword arguments that don't apply to this selector
|
52
|
+
def to_s(brackets: true, **)
|
53
|
+
brackets ? "[?#{value}]#{@next}" : "?#{value}#{@next}"
|
52
54
|
end
|
53
55
|
|
54
56
|
# @param level [Integer]
|
55
57
|
# @return [Array]
|
56
58
|
def tree(level)
|
57
|
-
|
59
|
+
if @next
|
60
|
+
[indented(level, to_s), indented(level + 1, @next&.tree)]
|
61
|
+
else
|
62
|
+
[indented(level, to_s)]
|
63
|
+
end
|
58
64
|
end
|
59
65
|
end
|
60
66
|
end
|
data/lib/janeway/ast/function.rb
CHANGED
@@ -8,7 +8,6 @@ module Janeway
|
|
8
8
|
|
9
9
|
attr_reader :parameters, :body
|
10
10
|
|
11
|
-
# FIXME: provide function body too as a proc?
|
12
11
|
def initialize(name, parameters, &body)
|
13
12
|
raise ArgumentError, "expect string, got #{name.inspect}" unless name.is_a?(String)
|
14
13
|
|
@@ -35,6 +34,27 @@ module Janeway
|
|
35
34
|
def singular_query?
|
36
35
|
true
|
37
36
|
end
|
37
|
+
|
38
|
+
# True if the function's return value is a literal
|
39
|
+
def literal?
|
40
|
+
case name
|
41
|
+
when 'length', 'count', 'value' then true
|
42
|
+
when 'search', 'match' then false
|
43
|
+
else
|
44
|
+
raise "Unknown jsonpath function #{name}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# True if the function's return value is a boolean
|
49
|
+
# @return [Boolean]
|
50
|
+
def logical?
|
51
|
+
case name
|
52
|
+
when 'length', 'count', 'value' then false
|
53
|
+
when 'search', 'match' then true
|
54
|
+
else
|
55
|
+
raise "Unknown jsonpath function #{name}"
|
56
|
+
end
|
57
|
+
end
|
38
58
|
end
|
39
59
|
end
|
40
60
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'janeway'
|
3
4
|
require_relative 'selector'
|
4
5
|
|
5
6
|
module Janeway
|
@@ -7,19 +8,17 @@ module Janeway
|
|
7
8
|
# An index selector, e.g. 3, selects an indexed child of an array
|
8
9
|
# @example: $.store.book[2].title
|
9
10
|
class IndexSelector < Janeway::AST::Selector
|
10
|
-
# These are the limits of what javascript's Number type can represent
|
11
|
-
MIN = -9_007_199_254_740_991
|
12
|
-
MAX = 9_007_199_254_740_991
|
13
|
-
|
14
11
|
def initialize(index)
|
15
12
|
raise Error, "Invalid value for index selector: #{index.inspect}" unless index.is_a?(Integer)
|
16
|
-
raise Error, "Index selector value too small: #{index.inspect}" if index <
|
17
|
-
raise Error, "Index selector value too large: #{index.inspect}" if index >
|
13
|
+
raise Error, "Index selector value too small: #{index.inspect}" if index < INTEGER_MIN
|
14
|
+
raise Error, "Index selector value too large: #{index.inspect}" if index > INTEGER_MAX
|
18
15
|
|
19
16
|
super
|
20
17
|
end
|
21
18
|
|
22
|
-
|
19
|
+
# @param brackets [Boolean] include brackets around selector
|
20
|
+
# ** ignores keyword arguments that don't apply to this selector
|
21
|
+
def to_s(brackets: true, **)
|
23
22
|
brackets ? "[#{@value}]" : @value.to_s
|
24
23
|
end
|
25
24
|
end
|
@@ -14,22 +14,24 @@ module Janeway
|
|
14
14
|
|
15
15
|
def initialize(value)
|
16
16
|
super
|
17
|
-
# FIXME: implement name matching requirements here
|
18
17
|
raise "Invalid name: #{value.inspect}:#{value.class}" unless value.is_a?(String)
|
19
18
|
end
|
20
19
|
|
21
|
-
|
20
|
+
# @param bracket [Boolean] request for bracket syntax
|
21
|
+
# @param bracket [Boolean] include . prefix, if shorthand notation is used
|
22
|
+
def to_s(brackets: false, dot_prefix: true)
|
22
23
|
# Add quotes and surrounding brackets if the name includes chars that require quoting.
|
23
24
|
# These chars are not allowed in dotted notation, only bracket notation.
|
24
25
|
special_chars = [' ', '.']
|
25
26
|
brackets ||= special_chars.any? { |char| @value.include?(char) }
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
if brackets
|
28
|
+
name_str = quote(@value)
|
29
|
+
"[#{name_str}]#{@next}"
|
30
|
+
elsif dot_prefix
|
31
|
+
".#{@value}#{@next}"
|
32
|
+
else # omit dot prefix after a descendant segment
|
33
|
+
"#{@value}#{@next}"
|
34
|
+
end
|
33
35
|
end
|
34
36
|
|
35
37
|
# put surrounding quotes on a string
|
@@ -45,7 +47,7 @@ module Janeway
|
|
45
47
|
# @param level [Integer]
|
46
48
|
# @return [Array]
|
47
49
|
def tree(level)
|
48
|
-
[indented(level, "NameSelector:\"#{@value}\""), @
|
50
|
+
[indented(level, "NameSelector:\"#{@value}\""), @next&.tree(level + 1)]
|
49
51
|
end
|
50
52
|
end
|
51
53
|
end
|
data/lib/janeway/ast/number.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Janeway
|
4
4
|
module AST
|
5
|
-
# Represent a number. From the
|
5
|
+
# Represent a number. From the ABNF grammar:
|
6
6
|
# number = (int / "-0") [ frac ] [ exp ] ; decimal number
|
7
7
|
# frac = "." 1*DIGIT ; decimal fraction
|
8
8
|
# exp = "e" [ "-" / "+" ] 1*DIGIT ; decimal exponent
|
data/lib/janeway/ast/query.rb
CHANGED
@@ -2,18 +2,34 @@
|
|
2
2
|
|
3
3
|
module Janeway
|
4
4
|
module AST
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
5
|
+
# AST::Query holds the complete abstract syntax tree created by parsing the query.
|
6
|
+
#
|
7
|
+
# This can be frozen and passed to multiple threads or ractors for simultaneous use.
|
8
|
+
# No instance members are modified during the interpretation stage.
|
8
9
|
class Query
|
9
|
-
|
10
|
+
# @return [AST::RootNode]
|
11
|
+
attr_reader :root
|
12
|
+
|
13
|
+
# The original jsonpath query, for use in error messages
|
14
|
+
# @return [String]
|
15
|
+
attr_reader :jsonpath
|
16
|
+
|
17
|
+
# @param root_node [AST::Root]
|
18
|
+
# @param jsonpath [String]
|
19
|
+
def initialize(root_node, jsonpath)
|
20
|
+
raise ArgumentError, "expect root identifier, got #{root_node.inspect}" unless root_node.is_a?(RootNode)
|
21
|
+
raise ArgumentError, "expect query string, got #{jsonpath.inspect}" unless jsonpath.is_a?(String)
|
22
|
+
|
23
|
+
@root = root_node
|
24
|
+
@jsonpath = jsonpath
|
25
|
+
end
|
10
26
|
|
11
27
|
# Use this Query to search the input, and return the results.
|
12
28
|
#
|
13
29
|
# @param input [Object] ruby object to be searched
|
14
30
|
# @return [Array] all matched objects
|
15
|
-
def find_all(
|
16
|
-
Janeway::Interpreter.new(
|
31
|
+
def find_all(input)
|
32
|
+
Janeway::Interpreter.new(self).interpret(input)
|
17
33
|
end
|
18
34
|
|
19
35
|
def to_s
|
@@ -15,11 +15,7 @@ module Janeway
|
|
15
15
|
#
|
16
16
|
class RootNode < Janeway::AST::Expression
|
17
17
|
def to_s
|
18
|
-
|
19
|
-
"$.#{@value}"
|
20
|
-
else
|
21
|
-
"$#{@value}"
|
22
|
-
end
|
18
|
+
"$#{@value}"
|
23
19
|
end
|
24
20
|
|
25
21
|
# True if this is the root of a singular-query.
|
@@ -33,7 +29,7 @@ module Janeway
|
|
33
29
|
selector = @value
|
34
30
|
loop do
|
35
31
|
selector_types << selector.class
|
36
|
-
selector = selector.
|
32
|
+
selector = selector.next
|
37
33
|
break unless selector
|
38
34
|
end
|
39
35
|
allowed = [AST::IndexSelector, AST::NameSelector]
|
data/lib/janeway/ast/selector.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'expression'
|
4
|
+
|
3
5
|
# https://github.com/ietf-wg-jsonpath/draft-ietf-jsonpath-base/blob/main/draft-ietf-jsonpath-base.md#selectors
|
4
6
|
# Selectors:
|
5
7
|
#
|
@@ -22,7 +24,8 @@ module Janeway
|
|
22
24
|
module AST
|
23
25
|
# Represent a selector, which is an expression that filters nodes from a list based on a predicate.
|
24
26
|
class Selector < Janeway::AST::Expression
|
25
|
-
|
27
|
+
# Subsequent expression that modifies the result of this selector list.
|
28
|
+
attr_accessor :next
|
26
29
|
|
27
30
|
def ==(other)
|
28
31
|
value == other&.value
|
@@ -17,29 +17,25 @@ module Janeway
|
|
17
17
|
# It has only one possible value: '*'
|
18
18
|
# @example: $.store.book[*]
|
19
19
|
class WildcardSelector < Janeway::AST::Selector
|
20
|
-
attr_accessor :child
|
21
|
-
|
22
20
|
def initialize
|
23
21
|
super
|
24
|
-
@
|
22
|
+
@next = nil
|
25
23
|
end
|
26
24
|
|
27
|
-
def to_s(brackets: true)
|
28
|
-
if
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
"*.#{@child}"
|
33
|
-
end
|
25
|
+
def to_s(brackets: false, dot_prefix: true)
|
26
|
+
if brackets
|
27
|
+
"[*]#{@next&.to_s(brackets: true)}"
|
28
|
+
elsif dot_prefix
|
29
|
+
".*#{@next}"
|
34
30
|
else
|
35
|
-
"*#{@
|
31
|
+
"*#{@next}"
|
36
32
|
end
|
37
33
|
end
|
38
34
|
|
39
35
|
# @param level [Integer]
|
40
36
|
# @return [Array]
|
41
37
|
def tree(level)
|
42
|
-
[indented(level, '*'), @
|
38
|
+
[indented(level, '*'), @next.tree(level + 1)]
|
43
39
|
end
|
44
40
|
end
|
45
41
|
end
|
data/lib/janeway/error.rb
CHANGED
@@ -15,9 +15,15 @@ module Janeway
|
|
15
15
|
# @param query [String] entire query string
|
16
16
|
# @param location [Location] location of error
|
17
17
|
def initialize(message, query = nil, location = nil)
|
18
|
-
super(message)
|
18
|
+
super("Jsonpath query #{query} - #{message}")
|
19
19
|
@query = query
|
20
20
|
@location = location
|
21
21
|
end
|
22
|
+
|
23
|
+
def detailed_message
|
24
|
+
msg = "Error: #{message}\nQuery: #{query}\n"
|
25
|
+
msg += (' ' * location.col) + '^' if @location
|
26
|
+
msg
|
27
|
+
end
|
22
28
|
end
|
23
29
|
end
|
@@ -24,14 +24,19 @@ module Janeway
|
|
24
24
|
consume # (
|
25
25
|
|
26
26
|
# Read parameter
|
27
|
-
|
28
|
-
|
27
|
+
arg = parse_function_parameter
|
28
|
+
parameters = [arg]
|
29
|
+
raise Error, "Invalid parameter - count() expects node list, got #{arg.value.inspect}" if arg.literal?
|
30
|
+
unless current.type == :group_end
|
31
|
+
raise Error, 'Too many parameters for count() function call'
|
32
|
+
end
|
29
33
|
|
34
|
+
# Define function body
|
30
35
|
AST::Function.new('count', parameters) do |node_list|
|
31
36
|
if node_list.is_a?(Array)
|
32
37
|
node_list.size
|
33
38
|
else
|
34
|
-
1
|
39
|
+
1 # The count of a non-empty singulare noselist such as count(@) is always 1
|
35
40
|
end
|
36
41
|
end
|
37
42
|
end
|
@@ -13,8 +13,14 @@ module Janeway
|
|
13
13
|
consume # (
|
14
14
|
|
15
15
|
# Read parameter
|
16
|
-
|
17
|
-
|
16
|
+
arg = parse_function_parameter
|
17
|
+
parameters = [arg]
|
18
|
+
unless arg.singular_query? || arg.literal?
|
19
|
+
raise Error, "Invalid parameter - length() expects literal value or singular query, got #{arg.value.inspect}"
|
20
|
+
end
|
21
|
+
unless current.type == :group_end
|
22
|
+
raise Error, 'Too many parameters for length() function call'
|
23
|
+
end
|
18
24
|
|
19
25
|
# Meaning of return value depends on the JSON type:
|
20
26
|
# * string - number of Unicode scalar values in the string.
|
@@ -46,7 +46,7 @@ module Janeway
|
|
46
46
|
if current.type == :union
|
47
47
|
consume # ,
|
48
48
|
else
|
49
|
-
raise
|
49
|
+
raise Error, 'Not enough parameters for match() function call'
|
50
50
|
end
|
51
51
|
|
52
52
|
# Read second parameter (the regexp)
|
@@ -54,7 +54,9 @@ module Janeway
|
|
54
54
|
# Otherwise it is an expression that takes the regexp from the input document,
|
55
55
|
# and the iregexp will not be available until interpretation.
|
56
56
|
parameters << parse_function_parameter
|
57
|
-
|
57
|
+
unless current.type == :group_end
|
58
|
+
raise Error, 'Too many parameters for match() function call'
|
59
|
+
end
|
58
60
|
|
59
61
|
AST::Function.new('match', parameters) do |str, str_iregexp|
|
60
62
|
if str.is_a?(String) && str_iregexp.is_a?(String)
|
@@ -40,7 +40,7 @@ module Janeway
|
|
40
40
|
if current.type == :union
|
41
41
|
consume # ,
|
42
42
|
else
|
43
|
-
raise
|
43
|
+
raise Error, 'Insufficient parameters for search() function call'
|
44
44
|
end
|
45
45
|
|
46
46
|
# Read second parameter (the regexp)
|
@@ -48,7 +48,9 @@ module Janeway
|
|
48
48
|
# Otherwise it is an expression that takes the regexp from the input document,
|
49
49
|
# and the iregexp will not be available until interpretation.
|
50
50
|
parameters << parse_function_parameter
|
51
|
-
|
51
|
+
unless current.type == :group_end
|
52
|
+
raise Error, 'Too many parameters for match() function call'
|
53
|
+
end
|
52
54
|
|
53
55
|
AST::Function.new('search', parameters) do |str, str_iregexp|
|
54
56
|
if str.is_a?(String) && str_iregexp.is_a?(String)
|