jmespath-ruby 0.1.0 → 0.1.2
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/CHANGELOG.md +34 -1
- data/CONTRIBUTING.md +26 -3
- data/README.md +117 -19
- data/SECURITY.md +3 -2
- data/docs/guides/comparison.md +189 -0
- data/docs/guides/errors.md +190 -2
- data/docs/guides/functions.md +69 -0
- data/docs/guides/performance.md +3 -0
- data/lib/jmespath/cli/failure_diagnostic.rb +110 -0
- data/lib/jmespath/cli.rb +7 -2
- data/lib/jmespath/diagnostic.rb +60 -0
- data/lib/jmespath/errors.rb +118 -6
- data/lib/jmespath/evaluation/scalars.rb +2 -1
- data/lib/jmespath/functions/coercions.rb +26 -10
- data/lib/jmespath/functions/collections.rb +49 -25
- data/lib/jmespath/functions/conversion.rb +3 -3
- data/lib/jmespath/functions/function_call.rb +8 -3
- data/lib/jmespath/functions/references.rb +38 -21
- data/lib/jmespath/functions/scalar.rb +37 -20
- data/lib/jmespath/functions/signature.rb +19 -5
- data/lib/jmespath/functions/suggestions.rb +56 -0
- data/lib/jmespath/functions/validation_context.rb +49 -0
- data/lib/jmespath/functions.rb +72 -8
- data/lib/jmespath/plan.rb +13 -7
- data/lib/jmespath/version.rb +1 -1
- data/lib/jmespath.rb +2 -0
- data/sig/jmespath.rbs +38 -1
- metadata +6 -1
|
@@ -6,33 +6,49 @@ module JMESPath
|
|
|
6
6
|
module Coercions
|
|
7
7
|
extend self
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
NATIVE_TYPES = { String => :string, NilClass => :null, TrueClass => :boolean, FalseClass => :boolean,
|
|
10
|
+
Array => :array, Hash => :object, ExpressionReference => :expref }.freeze
|
|
11
|
+
|
|
12
|
+
def number(value, context: nil, position: nil, stage: :argument)
|
|
10
13
|
return value if value.is_a?(Integer)
|
|
11
14
|
return value if value.is_a?(Float) && value.finite?
|
|
12
15
|
|
|
13
|
-
raise
|
|
16
|
+
raise type_failure(value, "Expected a finite number", [:number], context, position, stage)
|
|
14
17
|
end
|
|
15
18
|
|
|
16
|
-
def string(value)
|
|
17
|
-
DataAccess.string(value) || raise(
|
|
19
|
+
def string(value, context: nil, position: nil)
|
|
20
|
+
DataAccess.string(value) || raise(type_failure(value, "Expected a string", [:string], context, position))
|
|
18
21
|
end
|
|
19
22
|
|
|
20
|
-
def array(value)
|
|
21
|
-
DataAccess.array(value) || raise(
|
|
23
|
+
def array(value, context: nil, position: nil)
|
|
24
|
+
DataAccess.array(value) || raise(type_failure(value, "Expected an array", [:array], context, position))
|
|
22
25
|
end
|
|
23
26
|
|
|
24
|
-
def reference(value)
|
|
27
|
+
def reference(value, context: nil, position: nil)
|
|
25
28
|
return value if value.is_a?(ExpressionReference)
|
|
26
29
|
|
|
27
|
-
raise
|
|
30
|
+
raise type_failure(value, "Expected an expression reference", [:expref], context, position)
|
|
28
31
|
end
|
|
29
32
|
|
|
30
|
-
def object(value)
|
|
31
|
-
DataAccess.object(value) || raise(
|
|
33
|
+
def object(value, context: nil, position: nil)
|
|
34
|
+
DataAccess.object(value) || raise(type_failure(value, "Expected an object", [:object], context, position))
|
|
32
35
|
end
|
|
33
36
|
|
|
34
37
|
private
|
|
35
38
|
|
|
39
|
+
def type_failure(value, message, types, context, position, stage = :argument) # rubocop:disable Metrics/ParameterLists
|
|
40
|
+
return Signature::TypeMismatch.new(message, origin: validation_origin) unless context
|
|
41
|
+
|
|
42
|
+
context.type_error(message, position: position, expected_types: types, actual_type: native_type(value),
|
|
43
|
+
origin: validation_origin, stage: stage)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def native_type(value)
|
|
47
|
+
return :number if Types.number?(value)
|
|
48
|
+
|
|
49
|
+
NATIVE_TYPES.find { |klass, _type| value.is_a?(klass) }&.last || :unknown
|
|
50
|
+
end
|
|
51
|
+
|
|
36
52
|
def validation_origin
|
|
37
53
|
nil
|
|
38
54
|
end
|
|
@@ -14,60 +14,84 @@ module JMESPath
|
|
|
14
14
|
"min_by" => [[:array], [:expref]], "max_by" => [[:array], [:expref]]
|
|
15
15
|
}.freeze
|
|
16
16
|
|
|
17
|
+
DESCRIPTIONS = {
|
|
18
|
+
"avg" => "Mean, or null for an empty array.",
|
|
19
|
+
"sum" => "Sum, including zero for an empty array.",
|
|
20
|
+
"min" => "Minimum, or null for an empty array.",
|
|
21
|
+
"max" => "Maximum, or null for an empty array.",
|
|
22
|
+
"sort" => "Stable sorted array of numbers or strings.",
|
|
23
|
+
"join" => "Join strings with a separator.",
|
|
24
|
+
"keys" => "Object keys as strings.",
|
|
25
|
+
"values" => "Object values.",
|
|
26
|
+
"merge" => "Merge objects, with later values winning.",
|
|
27
|
+
"map" => "Map an expression over an array, including null results.",
|
|
28
|
+
"sort_by" => "Stable array sorted by homogeneous expression results.",
|
|
29
|
+
"min_by" => "Input element with minimum expression result, or null for an empty array.",
|
|
30
|
+
"max_by" => "Input element with maximum expression result, or null for an empty array."
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
17
33
|
def self.register(registry)
|
|
18
34
|
SIGNATURES.reduce(registry) do |result, (name, signature)|
|
|
19
|
-
result.with_builtin(name, signature: signature, handler: name.to_sym,
|
|
35
|
+
result.with_builtin(name, signature: signature, handler: name.to_sym,
|
|
36
|
+
description: DESCRIPTIONS.fetch(name), variadic: name == "merge")
|
|
20
37
|
end
|
|
21
38
|
end
|
|
22
39
|
|
|
23
40
|
private
|
|
24
41
|
|
|
25
|
-
def sum(value)
|
|
26
|
-
|
|
42
|
+
def sum(value, context:)
|
|
43
|
+
element_context = context.array_element(1)
|
|
44
|
+
array(value, context: context, position: 1).sum { |item| number(item, context: element_context, position: 1) }
|
|
27
45
|
end
|
|
28
46
|
|
|
29
|
-
def avg(value)
|
|
30
|
-
values = array(value)
|
|
31
|
-
|
|
47
|
+
def avg(value, context:)
|
|
48
|
+
values = array(value, context: context, position: 1)
|
|
49
|
+
return if values.empty?
|
|
50
|
+
|
|
51
|
+
number(sum(value, context: context), context: context.array_element(1), position: 1).fdiv(values.length)
|
|
32
52
|
end
|
|
33
53
|
|
|
34
|
-
def min(value)
|
|
35
|
-
|
|
54
|
+
def min(value, context:)
|
|
55
|
+
element_context = context.array_element(1)
|
|
56
|
+
array(value, context: context, position: 1).min_by { |item| sort_key(item, context: element_context) }
|
|
36
57
|
end
|
|
37
58
|
|
|
38
|
-
def max(value)
|
|
39
|
-
|
|
59
|
+
def max(value, context:)
|
|
60
|
+
element_context = context.array_element(1)
|
|
61
|
+
array(value, context: context, position: 1).max_by { |item| sort_key(item, context: element_context) }
|
|
40
62
|
end
|
|
41
63
|
|
|
42
|
-
def sort(value)
|
|
43
|
-
|
|
44
|
-
|
|
64
|
+
def sort(value, context:)
|
|
65
|
+
element_context = context.array_element(1)
|
|
66
|
+
array(value, context: context, position: 1).each_with_index.sort_by do |item, index|
|
|
67
|
+
[sort_key(item, context: element_context), index]
|
|
45
68
|
end.map(&:first)
|
|
46
69
|
end
|
|
47
70
|
|
|
48
|
-
def join(separator, value)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
71
|
+
def join(separator, value, context:)
|
|
72
|
+
element_context = context.array_element(2)
|
|
73
|
+
array(value, context: context, position: 2).map do |item|
|
|
74
|
+
string(item, context: element_context, position: 2)
|
|
75
|
+
end.join(string(separator, context: context, position: 1))
|
|
52
76
|
end
|
|
53
77
|
|
|
54
|
-
def keys(value)
|
|
55
|
-
object(value).keys.map(&:to_s)
|
|
78
|
+
def keys(value, context:)
|
|
79
|
+
object(value, context: context, position: 1).keys.map(&:to_s)
|
|
56
80
|
end
|
|
57
81
|
|
|
58
|
-
def values(value)
|
|
59
|
-
object(value).values
|
|
82
|
+
def values(value, context:)
|
|
83
|
+
object(value, context: context, position: 1).values
|
|
60
84
|
end
|
|
61
85
|
|
|
62
|
-
def merge(*values)
|
|
86
|
+
def merge(*values, context:)
|
|
63
87
|
# @type var result: Hash[value, value]
|
|
64
88
|
result = {}
|
|
65
|
-
values.
|
|
89
|
+
values.each_with_index { |value, index| result.merge!(object(value, context: context, position: index + 1)) }
|
|
66
90
|
result
|
|
67
91
|
end
|
|
68
92
|
|
|
69
|
-
def sort_key(value)
|
|
70
|
-
DataAccess.string(value) || number(value)
|
|
93
|
+
def sort_key(value, context:, position: 1, stage: :argument)
|
|
94
|
+
DataAccess.string(value) || number(value, context: context, position: position, stage: stage)
|
|
71
95
|
end
|
|
72
96
|
end
|
|
73
97
|
|
|
@@ -8,17 +8,17 @@ module JMESPath
|
|
|
8
8
|
|
|
9
9
|
private
|
|
10
10
|
|
|
11
|
-
def to_array(value)
|
|
11
|
+
def to_array(value, **)
|
|
12
12
|
DataAccess.array(value) || [value]
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def to_string(value)
|
|
15
|
+
def to_string(value, **)
|
|
16
16
|
DataAccess.string(value) || JSON.generate(Types.normalize(value))
|
|
17
17
|
rescue JSON::NestingError
|
|
18
18
|
raise Errors::InvalidValueError, "Value is too deeply nested to serialize"
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def to_number(value)
|
|
21
|
+
def to_number(value, **)
|
|
22
22
|
return value if Types.number?(value)
|
|
23
23
|
|
|
24
24
|
text = DataAccess.string(value)&.strip
|
|
@@ -10,10 +10,15 @@ module JMESPath
|
|
|
10
10
|
|
|
11
11
|
private :number, :string, :array, :reference, :object
|
|
12
12
|
|
|
13
|
-
def call(definition, name, arguments, disable_visit_errors: false)
|
|
14
|
-
|
|
13
|
+
def call(definition, name, arguments, disable_visit_errors: false, context: nil)
|
|
14
|
+
context ||= ValidationContext.new(name, definition.signature)
|
|
15
|
+
definition.signature.validate!(arguments, name, origin: self, context: context)
|
|
15
16
|
implementation = definition.implementation
|
|
16
|
-
implementation.is_a?(Symbol)
|
|
17
|
+
if implementation.is_a?(Symbol)
|
|
18
|
+
send(implementation, *arguments, context: context)
|
|
19
|
+
else
|
|
20
|
+
implementation.call(*arguments)
|
|
21
|
+
end
|
|
17
22
|
rescue Signature::TypeMismatch, Signature::ArityMismatch => e
|
|
18
23
|
raise unless disable_visit_errors && e.origin.equal?(self)
|
|
19
24
|
|
|
@@ -8,51 +8,68 @@ module JMESPath
|
|
|
8
8
|
|
|
9
9
|
private
|
|
10
10
|
|
|
11
|
-
def map(expression, value)
|
|
12
|
-
reference = reference(expression)
|
|
13
|
-
array(value).map { |item| reference.call(item) }
|
|
11
|
+
def map(expression, value, context:)
|
|
12
|
+
reference = reference(expression, context: context, position: 1)
|
|
13
|
+
array(value, context: context, position: 2).map { |item| reference.call(item) }
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def sort_by(value, expression)
|
|
17
|
-
reference = reference(expression)
|
|
16
|
+
def sort_by(value, expression, context:)
|
|
17
|
+
reference = reference(expression, context: context, position: 2)
|
|
18
18
|
# @type var expected: Symbol?
|
|
19
19
|
expected = nil
|
|
20
|
+
# @type var result_context: ValidationContext?
|
|
21
|
+
result_context = nil
|
|
20
22
|
index = 0
|
|
21
|
-
array(value).sort_by do |item|
|
|
23
|
+
array(value, context: context, position: 1).sort_by do |item|
|
|
22
24
|
key = reference.call(item)
|
|
23
|
-
|
|
25
|
+
type = checked_type(key, expected, context: context)
|
|
26
|
+
expected = type
|
|
27
|
+
key_context = result_context || context.with_types([type])
|
|
28
|
+
result_context = key_context
|
|
24
29
|
index += 1
|
|
25
|
-
[sort_key(key), index]
|
|
30
|
+
[sort_key(key, context: key_context, position: 2, stage: :expression_result), index]
|
|
26
31
|
end
|
|
27
32
|
end
|
|
28
33
|
|
|
29
|
-
def min_by(value, expression)
|
|
30
|
-
keyed(value, expression).min_by(&:first)&.last
|
|
34
|
+
def min_by(value, expression, context:)
|
|
35
|
+
keyed(value, expression, context: context).min_by(&:first)&.last
|
|
31
36
|
end
|
|
32
37
|
|
|
33
|
-
def max_by(value, expression)
|
|
34
|
-
keyed(value, expression).max_by(&:first)&.last
|
|
38
|
+
def max_by(value, expression, context:)
|
|
39
|
+
keyed(value, expression, context: context).max_by(&:first)&.last
|
|
35
40
|
end
|
|
36
41
|
|
|
37
|
-
def keyed(value, expression)
|
|
38
|
-
reference = reference(expression)
|
|
42
|
+
def keyed(value, expression, context:)
|
|
43
|
+
reference = reference(expression, context: context, position: 2)
|
|
39
44
|
# @type var expected: Symbol?
|
|
40
45
|
expected = nil
|
|
41
|
-
|
|
46
|
+
# @type var result_context: ValidationContext?
|
|
47
|
+
result_context = nil
|
|
48
|
+
array(value, context: context, position: 1).map do |item|
|
|
42
49
|
key = reference.call(item)
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
type = checked_type(key, expected, context: context)
|
|
51
|
+
expected = type
|
|
52
|
+
key_context = result_context || context.with_types([type])
|
|
53
|
+
result_context = key_context
|
|
54
|
+
[sort_key(key, context: key_context, position: 2, stage: :expression_result), item]
|
|
45
55
|
end
|
|
46
56
|
end
|
|
47
57
|
|
|
48
|
-
def checked_type(value, expected)
|
|
58
|
+
def checked_type(value, expected, context:)
|
|
49
59
|
type = Types.name(value)
|
|
50
60
|
unless SORTABLE_TYPES.include?(type)
|
|
51
|
-
raise
|
|
61
|
+
raise context.type_error(
|
|
62
|
+
"Expression keys must be numbers or strings",
|
|
63
|
+
position: 2, expected_types: expected ? [expected] : SORTABLE_TYPES, actual_type: type,
|
|
64
|
+
origin: validation_origin, stage: :expression_result
|
|
65
|
+
)
|
|
52
66
|
end
|
|
53
67
|
if expected && expected != type
|
|
54
|
-
raise
|
|
55
|
-
|
|
68
|
+
raise context.type_error(
|
|
69
|
+
"Expression keys must have one type",
|
|
70
|
+
position: 2, expected_types: [expected], actual_type: type,
|
|
71
|
+
origin: validation_origin, stage: :expression_result
|
|
72
|
+
)
|
|
56
73
|
end
|
|
57
74
|
|
|
58
75
|
type
|
|
@@ -13,57 +13,74 @@ module JMESPath
|
|
|
13
13
|
"to_number" => [[:any]], "not_null" => [[:any]]
|
|
14
14
|
}.freeze
|
|
15
15
|
|
|
16
|
+
DESCRIPTIONS = {
|
|
17
|
+
"abs" => "Absolute value.",
|
|
18
|
+
"ceil" => "Integer rounded upward.",
|
|
19
|
+
"floor" => "Integer rounded downward.",
|
|
20
|
+
"contains" => "Test string or array membership.",
|
|
21
|
+
"ends_with" => "Test whether a string ends with a suffix.",
|
|
22
|
+
"starts_with" => "Test whether a string starts with a prefix.",
|
|
23
|
+
"length" => "Character or element count.",
|
|
24
|
+
"reverse" => "Reverse a string or array.",
|
|
25
|
+
"type" => "JMESPath type name.",
|
|
26
|
+
"to_array" => "Wrap a value in an array, preserving arrays.",
|
|
27
|
+
"to_string" => "String or compact JSON representation.",
|
|
28
|
+
"to_number" => "Number when convertible, otherwise null.",
|
|
29
|
+
"not_null" => "First non-null value."
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
16
32
|
def self.register(registry)
|
|
17
33
|
SIGNATURES.reduce(registry) do |result, (name, signature)|
|
|
18
34
|
handler = %w[contains ends_with starts_with].include?(name) ? "#{name}?" : name
|
|
19
|
-
result.with_builtin(name, signature: signature, handler: handler.to_sym,
|
|
35
|
+
result.with_builtin(name, signature: signature, handler: handler.to_sym,
|
|
36
|
+
description: DESCRIPTIONS.fetch(name), variadic: name == "not_null")
|
|
20
37
|
end
|
|
21
38
|
end
|
|
22
39
|
|
|
23
40
|
private
|
|
24
41
|
|
|
25
|
-
def abs(value)
|
|
26
|
-
number(value).abs
|
|
42
|
+
def abs(value, context:)
|
|
43
|
+
number(value, context: context, position: 1).abs
|
|
27
44
|
end
|
|
28
45
|
|
|
29
|
-
def ceil(value)
|
|
30
|
-
number(value).ceil
|
|
46
|
+
def ceil(value, context:)
|
|
47
|
+
number(value, context: context, position: 1).ceil
|
|
31
48
|
end
|
|
32
49
|
|
|
33
|
-
def floor(value)
|
|
34
|
-
number(value).floor
|
|
50
|
+
def floor(value, context:)
|
|
51
|
+
number(value, context: context, position: 1).floor
|
|
35
52
|
end
|
|
36
53
|
|
|
37
|
-
def contains?(subject, search)
|
|
54
|
+
def contains?(subject, search, context:)
|
|
38
55
|
text = DataAccess.string(subject)
|
|
39
|
-
return text.include?(string(search)) if text
|
|
56
|
+
return text.include?(string(search, context: context, position: 2)) if text
|
|
40
57
|
|
|
41
|
-
array(subject).any? { |item| Types.equal?(item, search) }
|
|
58
|
+
array(subject, context: context, position: 1).any? { |item| Types.equal?(item, search) }
|
|
42
59
|
end
|
|
43
60
|
|
|
44
|
-
def ends_with?(text, suffix)
|
|
45
|
-
string(text).end_with?(string(suffix))
|
|
61
|
+
def ends_with?(text, suffix, context:)
|
|
62
|
+
string(text, context: context, position: 1).end_with?(string(suffix, context: context, position: 2))
|
|
46
63
|
end
|
|
47
64
|
|
|
48
|
-
def starts_with?(text, prefix)
|
|
49
|
-
string(text).start_with?(string(prefix))
|
|
65
|
+
def starts_with?(text, prefix, context:)
|
|
66
|
+
string(text, context: context, position: 1).start_with?(string(prefix, context: context, position: 2))
|
|
50
67
|
end
|
|
51
68
|
|
|
52
|
-
def length(value)
|
|
53
|
-
collection = DataAccess.string(value) || DataAccess.array(value) || object(value)
|
|
69
|
+
def length(value, context:)
|
|
70
|
+
collection = DataAccess.string(value) || DataAccess.array(value) || object(value, context: context, position: 1)
|
|
54
71
|
collection.length
|
|
55
72
|
end
|
|
56
73
|
|
|
57
|
-
def reverse(value)
|
|
58
|
-
(DataAccess.string(value) || array(value)).reverse
|
|
74
|
+
def reverse(value, context:)
|
|
75
|
+
(DataAccess.string(value) || array(value, context: context, position: 1)).reverse
|
|
59
76
|
end
|
|
60
77
|
|
|
61
|
-
def type(value)
|
|
78
|
+
def type(value, **)
|
|
62
79
|
name = Types.name(value)
|
|
63
80
|
name.to_s unless name == :unknown
|
|
64
81
|
end
|
|
65
82
|
|
|
66
|
-
def not_null(*values)
|
|
83
|
+
def not_null(*values, **)
|
|
67
84
|
values.find { |value| !value.nil? }
|
|
68
85
|
end
|
|
69
86
|
end
|
|
@@ -10,9 +10,9 @@ module JMESPath
|
|
|
10
10
|
module FailureOrigin
|
|
11
11
|
attr_reader :origin
|
|
12
12
|
|
|
13
|
-
def initialize(message = nil, origin: nil)
|
|
13
|
+
def initialize(message = nil, origin: nil, **metadata)
|
|
14
14
|
@origin = origin
|
|
15
|
-
super(message)
|
|
15
|
+
super(message, **metadata)
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -26,6 +26,12 @@ module JMESPath
|
|
|
26
26
|
include FailureOrigin
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
attr_reader :arguments
|
|
30
|
+
|
|
31
|
+
def variadic?
|
|
32
|
+
@variadic
|
|
33
|
+
end
|
|
34
|
+
|
|
29
35
|
def initialize(arguments, variadic: false)
|
|
30
36
|
raise ArgumentError, "Invalid function signature" unless valid_definition?(arguments)
|
|
31
37
|
raise ArgumentError, "Variadic signatures require an argument" if variadic && arguments.empty?
|
|
@@ -35,21 +41,29 @@ module JMESPath
|
|
|
35
41
|
freeze
|
|
36
42
|
end
|
|
37
43
|
|
|
38
|
-
def validate!(values, name, origin: nil)
|
|
44
|
+
def validate!(values, name, origin: nil, context: nil)
|
|
45
|
+
context ||= ValidationContext.new(name, self)
|
|
39
46
|
unless valid_arity?(values.length)
|
|
40
|
-
raise
|
|
47
|
+
raise context.arity_error("Invalid number of arguments for #{name}", actual_arity: values.length,
|
|
48
|
+
origin: origin)
|
|
41
49
|
end
|
|
42
50
|
|
|
43
51
|
values.each_with_index do |value, index|
|
|
44
52
|
types = @arguments.fetch(index) { @arguments.fetch(-1) }
|
|
45
53
|
next if types.any? { |type| matches?(value, type) }
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
reject_type!(value, index, types, name, origin, context)
|
|
48
56
|
end
|
|
49
57
|
end
|
|
50
58
|
|
|
51
59
|
private
|
|
52
60
|
|
|
61
|
+
def reject_type!(value, index, types, name, origin, context) # rubocop:disable Metrics/ParameterLists
|
|
62
|
+
actual_type = Types.name(value)
|
|
63
|
+
raise context.type_error("Invalid argument #{index + 1} for #{name}: #{actual_type}",
|
|
64
|
+
position: index + 1, expected_types: types, actual_type: actual_type, origin: origin)
|
|
65
|
+
end
|
|
66
|
+
|
|
53
67
|
def valid_definition?(arguments)
|
|
54
68
|
arguments.is_a?(Array) && arguments.all? do |types|
|
|
55
69
|
types.is_a?(Array) && !types.empty? && (types - TYPES).empty?
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JMESPath
|
|
4
|
+
class Functions
|
|
5
|
+
# @api private
|
|
6
|
+
module Suggestions
|
|
7
|
+
class << self
|
|
8
|
+
def for(name, names)
|
|
9
|
+
names.filter_map { |candidate| ranked(name, candidate) }.sort.first(3).map(&:last)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def ranked(name, candidate)
|
|
15
|
+
threshold = [name.length, candidate.length].max <= 4 ? 1 : 2
|
|
16
|
+
return if (name.length - candidate.length).abs > threshold
|
|
17
|
+
|
|
18
|
+
score = distance(name, candidate)
|
|
19
|
+
[score, candidate] if score <= threshold
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def distance(left, right)
|
|
23
|
+
left = left.chars
|
|
24
|
+
right = right.chars
|
|
25
|
+
previous = (0..right.length).to_a
|
|
26
|
+
older = previous
|
|
27
|
+
left.each_index do |index|
|
|
28
|
+
current = row(left, right, index, previous, older)
|
|
29
|
+
older = previous
|
|
30
|
+
previous = current
|
|
31
|
+
end
|
|
32
|
+
previous.fetch(right.length)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def row(left, right, index, previous, older)
|
|
36
|
+
current = [index + 1]
|
|
37
|
+
right.each_index do |column|
|
|
38
|
+
score = [previous.fetch(column + 1) + 1, current.fetch(column) + 1,
|
|
39
|
+
substitution(left, right, index, column, previous)]
|
|
40
|
+
score << (older.fetch(column - 1) + 1) if swap?(left, right, index, column)
|
|
41
|
+
current << score.min
|
|
42
|
+
end
|
|
43
|
+
current
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def substitution(left, right, index, column, previous)
|
|
47
|
+
previous.fetch(column) + (left[index] == right[column] ? 0 : 1)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def swap?(left, right, index, column)
|
|
51
|
+
index.positive? && column.positive? && left[index] == right[column - 1] && left[index - 1] == right[column]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JMESPath
|
|
4
|
+
class Functions
|
|
5
|
+
# @api private
|
|
6
|
+
class ValidationContext
|
|
7
|
+
def initialize(name, signature, source_range: nil, expected_types: nil, actual_type: nil)
|
|
8
|
+
@name = name.dup.freeze
|
|
9
|
+
@signature = signature
|
|
10
|
+
@source_range = source_range&.dup&.freeze
|
|
11
|
+
@expected_types = expected_types&.dup&.freeze
|
|
12
|
+
@actual_type = actual_type
|
|
13
|
+
freeze
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def type_error(message, position:, expected_types:, actual_type:, origin:, stage: :argument) # rubocop:disable Metrics/ParameterLists
|
|
17
|
+
Signature::TypeMismatch.new(
|
|
18
|
+
message,
|
|
19
|
+
**metadata,
|
|
20
|
+
origin: origin, argument_position: position,
|
|
21
|
+
expected_types: @expected_types || expected_types,
|
|
22
|
+
actual_type: @actual_type || actual_type, validation_stage: stage
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def arity_error(message, actual_arity:, origin:)
|
|
27
|
+
Signature::ArityMismatch.new(
|
|
28
|
+
message, **metadata, origin: origin,
|
|
29
|
+
expected_arity: @signature.arguments.length, actual_arity: actual_arity
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def array_element(position)
|
|
34
|
+
with_types(@signature.arguments.fetch(position - 1), actual_type: :array)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def with_types(types, actual_type: nil)
|
|
38
|
+
self.class.new(@name, @signature, source_range: @source_range, expected_types: types, actual_type: actual_type)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def metadata
|
|
44
|
+
{ function_name: @name, signature: @signature.arguments, variadic: @signature.variadic?,
|
|
45
|
+
source_range: @source_range }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|