janeway-jsonpath 0.1.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 +7 -0
- data/LICENSE +7 -0
- data/README.md +43 -0
- data/bin/janeway +130 -0
- data/lib/janeway/ast/array_slice_selector.rb +104 -0
- data/lib/janeway/ast/binary_operator.rb +101 -0
- data/lib/janeway/ast/boolean.rb +24 -0
- data/lib/janeway/ast/child_segment.rb +48 -0
- data/lib/janeway/ast/current_node.rb +71 -0
- data/lib/janeway/ast/descendant_segment.rb +44 -0
- data/lib/janeway/ast/error.rb +10 -0
- data/lib/janeway/ast/expression.rb +55 -0
- data/lib/janeway/ast/filter_selector.rb +61 -0
- data/lib/janeway/ast/function.rb +40 -0
- data/lib/janeway/ast/helpers.rb +27 -0
- data/lib/janeway/ast/identifier.rb +35 -0
- data/lib/janeway/ast/index_selector.rb +27 -0
- data/lib/janeway/ast/name_selector.rb +52 -0
- data/lib/janeway/ast/null.rb +23 -0
- data/lib/janeway/ast/number.rb +21 -0
- data/lib/janeway/ast/query.rb +41 -0
- data/lib/janeway/ast/root_node.rb +50 -0
- data/lib/janeway/ast/selector.rb +32 -0
- data/lib/janeway/ast/string_type.rb +21 -0
- data/lib/janeway/ast/unary_operator.rb +26 -0
- data/lib/janeway/ast/wildcard_selector.rb +46 -0
- data/lib/janeway/error.rb +23 -0
- data/lib/janeway/functions/count.rb +39 -0
- data/lib/janeway/functions/length.rb +33 -0
- data/lib/janeway/functions/match.rb +69 -0
- data/lib/janeway/functions/search.rb +63 -0
- data/lib/janeway/functions/value.rb +47 -0
- data/lib/janeway/functions.rb +62 -0
- data/lib/janeway/interpreter.rb +644 -0
- data/lib/janeway/lexer.rb +514 -0
- data/lib/janeway/location.rb +3 -0
- data/lib/janeway/parser.rb +608 -0
- data/lib/janeway/token.rb +39 -0
- data/lib/janeway/version.rb +5 -0
- data/lib/janeway.rb +51 -0
- metadata +92 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'selector'
|
4
|
+
|
5
|
+
# https://datatracker.ietf.org/doc/rfc9535/
|
6
|
+
|
7
|
+
module Janeway
|
8
|
+
module AST
|
9
|
+
# Filter selectors are used to iterate over the elements or members of
|
10
|
+
# structured values, i.e., JSON arrays and objects. The structured
|
11
|
+
# values are identified in the nodelist offered by the child or
|
12
|
+
# descendant segment using the filter selector.
|
13
|
+
|
14
|
+
# For each iteration (element/member), a logical expression (the
|
15
|
+
# _filter expression_) is evaluated, which decides whether the node of
|
16
|
+
# the element/member is selected. (While a logical expression
|
17
|
+
# evaluates to what mathematically is a Boolean value, this
|
18
|
+
# specification uses the term _logical_ to maintain a distinction from
|
19
|
+
# the Boolean values that JSON can represent.)
|
20
|
+
|
21
|
+
# During the iteration process, the filter expression receives the node
|
22
|
+
# of each array element or object member value of the structured value
|
23
|
+
# being filtered; this element or member value is then known as the
|
24
|
+
# _current node_.
|
25
|
+
|
26
|
+
# The current node can be used as the start of one or more JSONPath
|
27
|
+
# queries in subexpressions of the filter expression, notated via the
|
28
|
+
# current-node-identifier @. Each JSONPath query can be used either for
|
29
|
+
# testing existence of a result of the query, for obtaining a specific
|
30
|
+
# JSON value resulting from that query that can then be used in a
|
31
|
+
# comparison, or as a _function argument_.
|
32
|
+
|
33
|
+
# Filter selectors may use function extensions, which are covered in
|
34
|
+
# Section 2.4. Within the logical expression for a filter selector,
|
35
|
+
# function expressions can be used to operate on nodelists and values.
|
36
|
+
# The set of available functions is extensible, with a number of
|
37
|
+
# functions predefined (see Section 2.4) and the ability to register
|
38
|
+
# further functions provided by the "Function Extensions" subregistry
|
39
|
+
# (Section 3.2). When a function is defined, it is given a unique
|
40
|
+
# name, and its return value and each of its parameters are given a
|
41
|
+
# _declared type_. The type system is limited in scope; its purpose is
|
42
|
+
# to express restrictions that, without functions, are implicit in the
|
43
|
+
# grammar of filter expressions. The type system also guides
|
44
|
+
# conversions (Section 2.4.2) that mimic the way different kinds of
|
45
|
+
# expressions are handled in the grammar when function expressions are
|
46
|
+
# not in use.
|
47
|
+
#
|
48
|
+
# @example: $.store[@.price < 10]
|
49
|
+
class FilterSelector < Janeway::AST::Selector
|
50
|
+
def to_s(brackets: true)
|
51
|
+
brackets ? "[?#{value}]" : "?#{value}"
|
52
|
+
end
|
53
|
+
|
54
|
+
# @param level [Integer]
|
55
|
+
# @return [Array]
|
56
|
+
def tree(level)
|
57
|
+
[indented(level, to_s)]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Janeway
|
4
|
+
module AST
|
5
|
+
# Represents a JSONPath built-in function.
|
6
|
+
class Function < Janeway::AST::Expression
|
7
|
+
alias name value
|
8
|
+
|
9
|
+
attr_reader :parameters, :body
|
10
|
+
|
11
|
+
# FIXME: provide function body too as a proc?
|
12
|
+
def initialize(name, parameters, &body)
|
13
|
+
raise ArgumentError, "expect string, got #{name.inspect}" unless name.is_a?(String)
|
14
|
+
|
15
|
+
super(name)
|
16
|
+
@parameters = parameters
|
17
|
+
@body = body
|
18
|
+
raise "expect body to be a Proc, got #{body.class}" unless body.is_a?(Proc)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"#{name}(#{@parameters.join(',')})"
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param level [Integer]
|
26
|
+
# @return [Array]
|
27
|
+
def tree(level)
|
28
|
+
[indented(level, to_s)]
|
29
|
+
end
|
30
|
+
|
31
|
+
# True if this is the root of a singular-query.
|
32
|
+
# @see https://www.rfc-editor.org/rfc/rfc9535.html#name-well-typedness-of-function-
|
33
|
+
#
|
34
|
+
# @return [Boolean]
|
35
|
+
def singular_query?
|
36
|
+
true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Janeway
|
4
|
+
module AST
|
5
|
+
module Helpers
|
6
|
+
# @param str [String] ascii string, CamelCase
|
7
|
+
# @return [String] lowercase, with underscores
|
8
|
+
def self.camelcase_to_underscore(str)
|
9
|
+
found_uppercase = false
|
10
|
+
chars = []
|
11
|
+
str.each_char do |char|
|
12
|
+
if char.ord.between?(65, 90) # ascii 'A'..'Z' inclusive
|
13
|
+
chars << '_'
|
14
|
+
chars << (char.ord + 32).chr
|
15
|
+
found_uppercase = true
|
16
|
+
else
|
17
|
+
chars << char
|
18
|
+
end
|
19
|
+
end
|
20
|
+
return str unless found_uppercase
|
21
|
+
|
22
|
+
chars.shift if chars.first == '_'
|
23
|
+
chars.join
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Janeway
|
4
|
+
module AST
|
5
|
+
class Identifier < Janeway::AST::Expression
|
6
|
+
alias name value
|
7
|
+
|
8
|
+
# TODO: This list is incomplete. Complete after some aspects of the parser become clearer.
|
9
|
+
EXPECTED_NEXT_TOKENS = %I[
|
10
|
+
\n
|
11
|
+
+
|
12
|
+
-
|
13
|
+
*
|
14
|
+
/
|
15
|
+
==
|
16
|
+
!=
|
17
|
+
>
|
18
|
+
<
|
19
|
+
>=
|
20
|
+
<=
|
21
|
+
&&
|
22
|
+
||
|
23
|
+
].freeze
|
24
|
+
|
25
|
+
# @return [String]
|
26
|
+
def to_s
|
27
|
+
@value
|
28
|
+
end
|
29
|
+
|
30
|
+
def expects?(next_token)
|
31
|
+
EXPECTED_NEXT_TOKENS.include?(next_token)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'selector'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
module AST
|
7
|
+
# An index selector, e.g. 3, selects an indexed child of an array
|
8
|
+
# @example: $.store.book[2].title
|
9
|
+
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
|
+
def initialize(index)
|
15
|
+
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 < MIN
|
17
|
+
raise Error, "Index selector value too large: #{index.inspect}" if index > MAX
|
18
|
+
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s(brackets: true)
|
23
|
+
brackets ? "[#{@value}]" : @value.to_s
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'selector'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
module AST
|
7
|
+
# A name selector, e.g. 'name', selects a named child of an object.
|
8
|
+
# @example
|
9
|
+
# $.store
|
10
|
+
# $[store]
|
11
|
+
# The dot or bracket part is not captured here, only the name
|
12
|
+
class NameSelector < Janeway::AST::Selector
|
13
|
+
alias name value
|
14
|
+
|
15
|
+
def initialize(value)
|
16
|
+
super
|
17
|
+
# FIXME: implement name matching requirements here
|
18
|
+
raise "Invalid name: #{value.inspect}:#{value.class}" unless value.is_a?(String)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s(brackets: false)
|
22
|
+
# Add quotes and surrounding brackets if the name includes chars that require quoting.
|
23
|
+
# These chars are not allowed in dotted notation, only bracket notation.
|
24
|
+
special_chars = [' ', '.']
|
25
|
+
brackets ||= special_chars.any? { |char| @value.include?(char) }
|
26
|
+
name_str =
|
27
|
+
if brackets
|
28
|
+
quote(@value)
|
29
|
+
else
|
30
|
+
@value
|
31
|
+
end
|
32
|
+
brackets ? "[#{name_str}]#{@child}" : "#{name_str}#{@child}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# put surrounding quotes on a string
|
36
|
+
# @return [String]
|
37
|
+
def quote(str)
|
38
|
+
if str.include?("'")
|
39
|
+
format('"%s"', str)
|
40
|
+
else
|
41
|
+
"'#{str}'"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param level [Integer]
|
46
|
+
# @return [Array]
|
47
|
+
def tree(level)
|
48
|
+
[indented(level, "NameSelector:\"#{@value}\""), @child.tree(level + 1)]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Janeway
|
4
|
+
module AST
|
5
|
+
class Null < Janeway::AST::Expression
|
6
|
+
def to_s
|
7
|
+
'null'
|
8
|
+
end
|
9
|
+
|
10
|
+
# @param level [Integer]
|
11
|
+
# @return [Array]
|
12
|
+
def tree(level = 0)
|
13
|
+
indented(level, 'null')
|
14
|
+
end
|
15
|
+
|
16
|
+
# Return true if this is a literal expression
|
17
|
+
# @return [Boolean]
|
18
|
+
def literal?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Janeway
|
4
|
+
module AST
|
5
|
+
# Represent a number. From the BNF grammer:
|
6
|
+
# number = (int / "-0") [ frac ] [ exp ] ; decimal number
|
7
|
+
# frac = "." 1*DIGIT ; decimal fraction
|
8
|
+
# exp = "e" [ "-" / "+" ] 1*DIGIT ; decimal exponent
|
9
|
+
class Number < Janeway::AST::Expression
|
10
|
+
def to_s
|
11
|
+
@value.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
# Return true if this is a literal expression
|
15
|
+
# @return [Boolean]
|
16
|
+
def literal?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Janeway
|
4
|
+
module AST
|
5
|
+
# Syntactically, a JSONPath query consists of a root identifier ($),
|
6
|
+
# which stands for a nodelist that contains the root node of the
|
7
|
+
# query argument, followed by a possibly empty sequence of segments.
|
8
|
+
class Query
|
9
|
+
attr_accessor :root
|
10
|
+
|
11
|
+
# Use this Query to search the input, and return the results.
|
12
|
+
#
|
13
|
+
# @param input [Object] ruby object to be searched
|
14
|
+
# @return [Array] all matched objects
|
15
|
+
def find_all(object)
|
16
|
+
Janeway::Interpreter.new(input).interpret(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
@root.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
# Queries are considered equal if their ASTs evaluate to the same JSONPath string.
|
24
|
+
#
|
25
|
+
# The string output is generated by the AST and should be considered a "normalized"
|
26
|
+
# form of the query. It may have different whitespace and parentheses than the original
|
27
|
+
# input but will be semantically equivalent.
|
28
|
+
def ==(other)
|
29
|
+
to_s == other.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
# Print AST in tree format
|
33
|
+
# Every AST class prints a 1-line representation of self, with children on separate lines
|
34
|
+
def tree
|
35
|
+
result = @root.tree(0)
|
36
|
+
|
37
|
+
result.flatten.join("\n")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Janeway
|
4
|
+
module AST
|
5
|
+
# Every JSONPath query (except those inside filter expressions; see Section
|
6
|
+
# 2.3.5) MUST begin with the root identifier $.
|
7
|
+
#
|
8
|
+
# The root identifier $ represents the root node of the query argument and produces
|
9
|
+
# a nodelist consisting of that root node.
|
10
|
+
#
|
11
|
+
# The root identifier may also be used in the filter selectors.
|
12
|
+
#
|
13
|
+
# @example:
|
14
|
+
# $(? $.key1 == $.key2 )
|
15
|
+
#
|
16
|
+
class RootNode < Janeway::AST::Expression
|
17
|
+
def to_s
|
18
|
+
if @value.is_a?(NameSelector) || @value.is_a?(WildcardSelector)
|
19
|
+
"$.#{@value}"
|
20
|
+
else
|
21
|
+
"$#{@value}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# True if this is the root of a singular-query.
|
26
|
+
# @see https://www.rfc-editor.org/rfc/rfc9535.html#name-well-typedness-of-function-
|
27
|
+
#
|
28
|
+
# @return [Boolean]
|
29
|
+
def singular_query?
|
30
|
+
return true unless @value # there are no following selectors
|
31
|
+
|
32
|
+
selector_types = []
|
33
|
+
selector = @value
|
34
|
+
loop do
|
35
|
+
selector_types << selector.class
|
36
|
+
selector = selector.child
|
37
|
+
break unless selector
|
38
|
+
end
|
39
|
+
allowed = [AST::IndexSelector, AST::NameSelector]
|
40
|
+
selector_types.uniq.all? { allowed.include?(_1) }
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param level [Integer]
|
44
|
+
# @return [Array]
|
45
|
+
def tree(level = 0)
|
46
|
+
[indented(level, '$'), @value.tree(level + 1)]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# https://github.com/ietf-wg-jsonpath/draft-ietf-jsonpath-base/blob/main/draft-ietf-jsonpath-base.md#selectors
|
4
|
+
# Selectors:
|
5
|
+
#
|
6
|
+
# A name selector, e.g. 'name', selects a named child of an object.
|
7
|
+
#
|
8
|
+
# An index selector, e.g. 3, selects an indexed child of an array.
|
9
|
+
#
|
10
|
+
# A wildcard * ({{wildcard-selector}}) in the expression [*] selects all
|
11
|
+
# children of a node and in the expression ..[*] selects all descendants of a
|
12
|
+
# node.
|
13
|
+
#
|
14
|
+
# An array slice start:end:step ({{slice}}) selects a series of elements from
|
15
|
+
# an array, giving a start position, an end position, and an optional step
|
16
|
+
# value that moves the position from the start to the end.
|
17
|
+
#
|
18
|
+
# Filter expressions ?<logical-expr> select certain children of an object or array, as in:
|
19
|
+
#
|
20
|
+
# $.store.book[?@.price < 10].title
|
21
|
+
module Janeway
|
22
|
+
module AST
|
23
|
+
# Represent a selector, which is an expression that filters nodes from a list based on a predicate.
|
24
|
+
class Selector < Janeway::AST::Expression
|
25
|
+
attr_accessor :child
|
26
|
+
|
27
|
+
def ==(other)
|
28
|
+
value == other&.value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Janeway
|
4
|
+
module AST
|
5
|
+
class StringType < Janeway::AST::Expression
|
6
|
+
def to_s
|
7
|
+
if @value.include?("'")
|
8
|
+
%("#{@value}"')
|
9
|
+
else
|
10
|
+
"'#{@value}'"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Return true if this is a literal expression
|
15
|
+
# @return [Boolean]
|
16
|
+
def literal?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Janeway
|
4
|
+
module AST
|
5
|
+
# Represent unary operators "!", "-"
|
6
|
+
class UnaryOperator < Janeway::AST::Expression
|
7
|
+
attr_accessor :operator, :operand
|
8
|
+
|
9
|
+
def initialize(operator, operand = nil)
|
10
|
+
super()
|
11
|
+
@operator = operator
|
12
|
+
@operand = operand
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"#{@operator} #{operand}"
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param level [Integer]
|
20
|
+
# @return [Array]
|
21
|
+
def tree(level)
|
22
|
+
indented(level, "#{operator}#{operand}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'selector'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
module AST
|
7
|
+
# A wildcard selector selects the nodes of all children of an object
|
8
|
+
# or array. The order in which the children of an object appear in
|
9
|
+
# the resultant nodelist is not stipulated, since JSON objects are
|
10
|
+
# unordered. Children of an array appear in array order in the
|
11
|
+
# resultant nodelist.
|
12
|
+
#
|
13
|
+
# Note that the children of an object are its member values, not its member names/keys.
|
14
|
+
#
|
15
|
+
# The wildcard selector selects nothing from a primitive JSON value (ie. a number, a string, true, false, or null).
|
16
|
+
#
|
17
|
+
# It has only one possible value: '*'
|
18
|
+
# @example: $.store.book[*]
|
19
|
+
class WildcardSelector < Janeway::AST::Selector
|
20
|
+
attr_accessor :child
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
super
|
24
|
+
@child = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s(brackets: true)
|
28
|
+
if @child.is_a?(NameSelector) || @child.is_a?(WildcardSelector)
|
29
|
+
if brackets
|
30
|
+
"[*]#{@child.to_s(brackets: true)}"
|
31
|
+
else
|
32
|
+
"*.#{@child}"
|
33
|
+
end
|
34
|
+
else
|
35
|
+
"*#{@child}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param level [Integer]
|
40
|
+
# @return [Array]
|
41
|
+
def tree(level)
|
42
|
+
[indented(level, '*'), @child.tree(level + 1)]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'location'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
# Base class for JSONPath query errors
|
7
|
+
class Error < StandardError
|
8
|
+
# @return [String]
|
9
|
+
attr_reader :query
|
10
|
+
|
11
|
+
# @return [Location, nil]
|
12
|
+
attr_reader :location
|
13
|
+
|
14
|
+
# @param message [String] error message
|
15
|
+
# @param query [String] entire query string
|
16
|
+
# @param location [Location] location of error
|
17
|
+
def initialize(message, query = nil, location = nil)
|
18
|
+
super(message)
|
19
|
+
@query = query
|
20
|
+
@location = location
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Janeway
|
4
|
+
# Mixin to provide JSONPath function handlers for Parser
|
5
|
+
module Functions
|
6
|
+
# The count() function extension provides a way to obtain the number of
|
7
|
+
# nodes in a nodelist and make that available for further processing in
|
8
|
+
# the filter expression:
|
9
|
+
#
|
10
|
+
# Its only argument is a nodelist. The result is a value (an unsigned
|
11
|
+
# integer) that gives the number of nodes in the nodelist.
|
12
|
+
#
|
13
|
+
# Notes:
|
14
|
+
# * There is no deduplication of the nodelist.
|
15
|
+
# * The number of nodes in the nodelist is counted independent of
|
16
|
+
# their values or any children they may have, e.g., the count of a
|
17
|
+
# non-empty singular nodelist such as count(@) is always 1.
|
18
|
+
#
|
19
|
+
# @example $[?count(@.*.author) >= 5]
|
20
|
+
def parse_function_count
|
21
|
+
consume # function
|
22
|
+
raise "expect group_start token, found #{current}" unless current.type == :group_start
|
23
|
+
|
24
|
+
consume # (
|
25
|
+
|
26
|
+
# Read parameter
|
27
|
+
parameters = [parse_function_parameter]
|
28
|
+
raise "expect group_end token, found #{current}" unless current.type == :group_end
|
29
|
+
|
30
|
+
AST::Function.new('count', parameters) do |node_list|
|
31
|
+
if node_list.is_a?(Array)
|
32
|
+
node_list.size
|
33
|
+
else
|
34
|
+
1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Janeway
|
4
|
+
module Functions
|
5
|
+
# The length() function extension provides a way to compute the length of a value
|
6
|
+
# and make that available for further processing in the filter expression:
|
7
|
+
#
|
8
|
+
# JSONPath return type: ValueType
|
9
|
+
def parse_function_length
|
10
|
+
consume # function
|
11
|
+
raise "expect group_start token, found #{current}" unless current.type == :group_start
|
12
|
+
|
13
|
+
consume # (
|
14
|
+
|
15
|
+
# Read parameter
|
16
|
+
parameters = [parse_function_parameter]
|
17
|
+
raise "expect group_end token, found #{current}" unless current.type == :group_end
|
18
|
+
|
19
|
+
# Meaning of return value depends on the JSON type:
|
20
|
+
# * string - number of Unicode scalar values in the string.
|
21
|
+
# * array - number of elements in the array.
|
22
|
+
# * object - number of members in the object.
|
23
|
+
# For any other argument value, the result is the special result Nothing.
|
24
|
+
AST::Function.new('length', parameters) do |value|
|
25
|
+
if [Array, Hash, String].include?(value.class)
|
26
|
+
value.size
|
27
|
+
else
|
28
|
+
:nothing
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Janeway
|
4
|
+
# Mixin to provide JSONPath function handlers for Parser
|
5
|
+
module Functions
|
6
|
+
# The match() function extension provides a way to check whether (the
|
7
|
+
# entirety of; see Section 2.4.7) a given string matches a given regular
|
8
|
+
# expression, which is in the form described in [RFC9485].
|
9
|
+
#
|
10
|
+
# @example $[?match(@.date, "1974-05-..")]
|
11
|
+
#
|
12
|
+
# Its arguments are instances of ValueType (possibly taken from a
|
13
|
+
# singular query, as for the first argument in the example above). If the
|
14
|
+
# first argument is not a string or the second argument is not a string
|
15
|
+
# conforming to [RFC9485], the result is LogicalFalse. Otherwise, the
|
16
|
+
# string that is the first argument is matched against the I-Regexp
|
17
|
+
# contained in the string that is the second argument; the result is
|
18
|
+
# LogicalTrue if the string matches the I-Regexp and is LogicalFalse
|
19
|
+
# otherwise.
|
20
|
+
#
|
21
|
+
#
|
22
|
+
# The regexp dialect is called "I-Regexp" and is defined in RFC9485.
|
23
|
+
#
|
24
|
+
# Fortunately a shortcut is availalble, that RFC contains instructions
|
25
|
+
# for converting an I-Regexp to ruby's regexp format.
|
26
|
+
# @see https://www.rfc-editor.org/rfc/rfc9485.html#name-pcre-re2-and-ruby-regexps
|
27
|
+
#
|
28
|
+
# The instructions are:
|
29
|
+
# * For any unescaped dots (.) outside character classes (first
|
30
|
+
# alternative of charClass production), replace the dot with [^\n\r].
|
31
|
+
# * Enclose the regexp in \A(?: and )\z.
|
32
|
+
#
|
33
|
+
# tl;dr: How is this different from the search function?
|
34
|
+
# "match" must match the entire string, "search" matches a substring.
|
35
|
+
def parse_function_match
|
36
|
+
consume # function
|
37
|
+
raise "expect group_start token, found #{current}" unless current.type == :group_start
|
38
|
+
|
39
|
+
consume # (
|
40
|
+
|
41
|
+
# Read first parameter
|
42
|
+
parameters = []
|
43
|
+
parameters << parse_function_parameter
|
44
|
+
|
45
|
+
# Consume comma
|
46
|
+
if current.type == :union
|
47
|
+
consume # ,
|
48
|
+
else
|
49
|
+
raise "expect comma token, found #{current}"
|
50
|
+
end
|
51
|
+
|
52
|
+
# Read second parameter (the regexp)
|
53
|
+
# This could be a string, in which case it is available now.
|
54
|
+
# Otherwise it is an expression that takes the regexp from the input document,
|
55
|
+
# and the iregexp will not be available until interpretation.
|
56
|
+
parameters << parse_function_parameter
|
57
|
+
raise "expect group_end token, found #{current}" unless current.type == :group_end
|
58
|
+
|
59
|
+
AST::Function.new('match', parameters) do |str, str_iregexp|
|
60
|
+
if str.is_a?(String) && str_iregexp.is_a?(String)
|
61
|
+
regexp = translate_iregex_to_ruby_regex(str_iregexp)
|
62
|
+
regexp.match?(str)
|
63
|
+
else
|
64
|
+
false # result defined by RFC9535
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|