graphql 2.5.11 → 2.6.10
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/lib/generators/graphql/detailed_trace_generator.rb +77 -0
- data/lib/generators/graphql/field_extractor.rb +17 -1
- data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
- data/lib/generators/graphql/templates/schema.erb +2 -1
- data/lib/graphql/analysis/query_complexity.rb +31 -15
- data/lib/graphql/analysis.rb +20 -13
- data/lib/graphql/backtrace/table.rb +10 -1
- data/lib/graphql/current.rb +7 -1
- data/lib/graphql/dashboard/application_controller.rb +45 -0
- data/lib/graphql/dashboard/landings_controller.rb +9 -0
- data/lib/graphql/dashboard/statics_controller.rb +31 -0
- data/lib/graphql/dashboard/subscriptions.rb +2 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
- data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
- data/lib/graphql/dashboard.rb +14 -73
- data/lib/graphql/dataloader/async_dataloader.rb +336 -60
- data/lib/graphql/dataloader/null_dataloader.rb +48 -10
- data/lib/graphql/dataloader/source.rb +32 -19
- data/lib/graphql/dataloader.rb +107 -39
- data/lib/graphql/date_encoding_error.rb +1 -1
- data/lib/graphql/execution/directive_checks.rb +2 -0
- data/lib/graphql/execution/field_resolve_step.rb +804 -0
- data/lib/graphql/execution/finalize.rb +229 -0
- data/lib/graphql/execution/input_values.rb +333 -0
- data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
- data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
- data/lib/graphql/execution/interpreter/resolve.rb +7 -13
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
- data/lib/graphql/execution/interpreter/runtime.rb +60 -40
- data/lib/graphql/execution/interpreter.rb +17 -23
- data/lib/graphql/execution/lazy.rb +1 -1
- data/lib/graphql/execution/load_argument_step.rb +102 -0
- data/lib/graphql/execution/multiplex.rb +1 -1
- data/lib/graphql/execution/next.rb +98 -0
- data/lib/graphql/execution/prepare_object_step.rb +151 -0
- data/lib/graphql/execution/resolve_type_step.rb +27 -0
- data/lib/graphql/execution/runner.rb +457 -0
- data/lib/graphql/execution/selections_step.rb +95 -0
- data/lib/graphql/execution.rb +15 -4
- data/lib/graphql/execution_error.rb +17 -12
- data/lib/graphql/float_decoding_error.rb +13 -0
- data/lib/graphql/float_encoding_error.rb +28 -0
- data/lib/graphql/introspection/directive_type.rb +7 -3
- data/lib/graphql/introspection/dynamic_fields.rb +5 -1
- data/lib/graphql/introspection/entry_points.rb +11 -3
- data/lib/graphql/introspection/enum_value_type.rb +5 -5
- data/lib/graphql/introspection/field_type.rb +13 -5
- data/lib/graphql/introspection/input_value_type.rb +21 -13
- data/lib/graphql/introspection/schema_type.rb +6 -2
- data/lib/graphql/introspection/type_type.rb +64 -28
- data/lib/graphql/invalid_null_error.rb +11 -5
- data/lib/graphql/language/block_string.rb +6 -11
- data/lib/graphql/language/cache.rb +84 -14
- data/lib/graphql/language/document_from_schema_definition.rb +2 -1
- data/lib/graphql/language/lexer.rb +27 -17
- data/lib/graphql/language/nodes.rb +2 -1
- data/lib/graphql/language/parser.rb +1 -1
- data/lib/graphql/language.rb +32 -17
- data/lib/graphql/pagination/connection.rb +2 -0
- data/lib/graphql/pagination/connections.rb +30 -0
- data/lib/graphql/pagination/relation_connection.rb +10 -1
- data/lib/graphql/query/context.rb +10 -3
- data/lib/graphql/query/null_context.rb +9 -3
- data/lib/graphql/query/partial.rb +18 -3
- data/lib/graphql/query/variable_validation_error.rb +16 -1
- data/lib/graphql/query/variables.rb +1 -1
- data/lib/graphql/query.rb +12 -3
- data/lib/graphql/railtie.rb +2 -1
- data/lib/graphql/runtime_error.rb +6 -0
- data/lib/graphql/schema/argument.rb +13 -1
- data/lib/graphql/schema/build_from_definition.rb +28 -1
- data/lib/graphql/schema/directive/feature.rb +4 -0
- data/lib/graphql/schema/directive/transform.rb +20 -0
- data/lib/graphql/schema/directive.rb +48 -13
- data/lib/graphql/schema/field/connection_extension.rb +4 -37
- data/lib/graphql/schema/field/scope_extension.rb +18 -13
- data/lib/graphql/schema/field.rb +79 -48
- data/lib/graphql/schema/field_extension.rb +11 -8
- data/lib/graphql/schema/has_single_input_argument.rb +24 -13
- data/lib/graphql/schema/input_object.rb +6 -2
- data/lib/graphql/schema/interface.rb +26 -0
- data/lib/graphql/schema/introspection_system.rb +6 -21
- data/lib/graphql/schema/list.rb +5 -1
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
- data/lib/graphql/schema/member/has_arguments.rb +43 -14
- data/lib/graphql/schema/member/has_authorization.rb +35 -0
- data/lib/graphql/schema/member/has_dataloader.rb +37 -0
- data/lib/graphql/schema/member/has_fields.rb +86 -5
- data/lib/graphql/schema/member.rb +5 -0
- data/lib/graphql/schema/non_null.rb +1 -1
- data/lib/graphql/schema/object.rb +1 -0
- data/lib/graphql/schema/printer.rb +1 -1
- data/lib/graphql/schema/ractor_shareable.rb +1 -0
- data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
- data/lib/graphql/schema/resolver.rb +76 -1
- data/lib/graphql/schema/subscription.rb +53 -10
- data/lib/graphql/schema/timeout.rb +2 -2
- data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
- data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
- data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
- data/lib/graphql/schema/validator/format_validator.rb +3 -3
- data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
- data/lib/graphql/schema/validator/length_validator.rb +6 -6
- data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
- data/lib/graphql/schema/validator/required_validator.rb +38 -5
- data/lib/graphql/schema/validator.rb +9 -0
- data/lib/graphql/schema/visibility/profile.rb +74 -53
- data/lib/graphql/schema/visibility/visit.rb +1 -1
- data/lib/graphql/schema/visibility.rb +32 -24
- data/lib/graphql/schema/wrapper.rb +11 -1
- data/lib/graphql/schema.rb +93 -27
- data/lib/graphql/static_validation/base_visitor.rb +90 -66
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +351 -248
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
- data/lib/graphql/static_validation/validation_context.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
- data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +40 -10
- data/lib/graphql/subscriptions/serialize.rb +10 -4
- data/lib/graphql/subscriptions.rb +36 -1
- data/lib/graphql/testing/helpers.rb +12 -9
- data/lib/graphql/testing/mock_action_cable.rb +111 -0
- data/lib/graphql/testing.rb +1 -0
- data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
- data/lib/graphql/tracing/detailed_trace.rb +70 -7
- data/lib/graphql/tracing/perfetto_trace.rb +213 -81
- data/lib/graphql/tracing/sentry_trace.rb +3 -1
- data/lib/graphql/tracing/trace.rb +6 -0
- data/lib/graphql/types/float.rb +18 -4
- data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
- data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
- data/lib/graphql/types/relay/has_node_field.rb +13 -8
- data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
- data/lib/graphql/types/relay/node_behaviors.rb +13 -2
- data/lib/graphql/unauthorized_error.rb +9 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +8 -2
- metadata +23 -59
|
@@ -21,17 +21,12 @@ module GraphQL
|
|
|
21
21
|
next
|
|
22
22
|
end
|
|
23
23
|
line_length = line.size
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
4
|
|
28
|
-
elsif line.match?(/\A[^ ]/)
|
|
29
|
-
0
|
|
30
|
-
else
|
|
31
|
-
line[/\A */].size
|
|
24
|
+
leading = 0
|
|
25
|
+
while leading < line_length && line.getbyte(leading) == 32
|
|
26
|
+
leading += 1
|
|
32
27
|
end
|
|
33
|
-
if
|
|
34
|
-
common_indent =
|
|
28
|
+
if leading < line_length && (common_indent.nil? || leading < common_indent)
|
|
29
|
+
common_indent = leading
|
|
35
30
|
end
|
|
36
31
|
end
|
|
37
32
|
|
|
@@ -41,7 +36,7 @@ module GraphQL
|
|
|
41
36
|
if idx == 0
|
|
42
37
|
next
|
|
43
38
|
else
|
|
44
|
-
line
|
|
39
|
+
line[0, common_indent] = ""
|
|
45
40
|
end
|
|
46
41
|
end
|
|
47
42
|
end
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
require 'graphql/version'
|
|
4
4
|
require 'digest/sha2'
|
|
5
|
+
require 'openssl'
|
|
6
|
+
require 'securerandom'
|
|
7
|
+
require 'tempfile'
|
|
5
8
|
|
|
6
9
|
module GraphQL
|
|
7
10
|
module Language
|
|
@@ -9,7 +12,11 @@ module GraphQL
|
|
|
9
12
|
#
|
|
10
13
|
# With Rails, parser caching may enabled by setting `config.graphql.parser_cache = true` in your Rails application.
|
|
11
14
|
#
|
|
12
|
-
# The cache may be manually built by assigning `GraphQL::Language::Parser.cache = GraphQL::Language::Cache.new("some_dir")`.
|
|
15
|
+
# The cache may be manually built by assigning `GraphQL::Language::Parser.cache = GraphQL::Language::Cache.new(Pathname.new("some_dir"), secret: ENV.fetch("GRAPHQL_CACHE_SECRET"))`.
|
|
16
|
+
# The `secret` should be a stable value stored outside of the cache directory.
|
|
17
|
+
# When it isn't provided, a process-local secret is generated and cache entries
|
|
18
|
+
# are rebuilt after the process restarts. Pass `secret: nil` to disable cache
|
|
19
|
+
# signing. This should only be used when the cache directory is trusted.
|
|
13
20
|
# This will create a directory (`tmp/cache/graphql` by default) that stores a cache of parsed files.
|
|
14
21
|
#
|
|
15
22
|
# Much like [bootsnap](https://github.com/Shopify/bootsnap), the parser cache needs to be cleaned up manually.
|
|
@@ -18,32 +25,95 @@ module GraphQL
|
|
|
18
25
|
#
|
|
19
26
|
# @see GraphQL::Railtie for simple Rails integration
|
|
20
27
|
class Cache
|
|
21
|
-
|
|
28
|
+
# @param path [Pathname] The directory where cache entries are stored.
|
|
29
|
+
# @param secret [String, nil] A stable secret for verifying cache entries. When omitted,
|
|
30
|
+
# a process-local secret is generated. Pass `nil` to disable cache signing.
|
|
31
|
+
def initialize(path, secret: SecureRandom.random_bytes(32))
|
|
22
32
|
@path = path
|
|
33
|
+
@secret = secret
|
|
23
34
|
end
|
|
24
35
|
|
|
25
36
|
DIGEST = Digest::SHA256.new << GraphQL::VERSION
|
|
37
|
+
HMAC_SIZE = OpenSSL::Digest::SHA256.new.digest_length
|
|
38
|
+
InvalidCache = Class.new(StandardError)
|
|
39
|
+
private_constant :InvalidCache
|
|
26
40
|
|
|
27
41
|
def fetch(filename)
|
|
28
|
-
|
|
42
|
+
cache_key = cache_key_for(filename)
|
|
43
|
+
return yield unless cache_key
|
|
44
|
+
|
|
45
|
+
cache_path = @path.join(cache_key)
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
return load_cache(cache_path, cache_key) if cache_path.file?
|
|
49
|
+
rescue InvalidCache, SystemCallError
|
|
50
|
+
# Rebuild caches created by older versions or with an invalid signature.
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
payload = yield
|
|
29
54
|
begin
|
|
30
|
-
|
|
55
|
+
write_cache(cache_path, cache_key, payload)
|
|
31
56
|
rescue SystemCallError
|
|
32
|
-
return
|
|
57
|
+
# Parser caching is best-effort; return the parsed payload if the cache cannot be written.
|
|
33
58
|
end
|
|
34
|
-
|
|
59
|
+
payload
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def cache_key_for(filename)
|
|
65
|
+
content_digest = Digest::SHA256.file(filename).hexdigest
|
|
66
|
+
(DIGEST.dup << filename << content_digest).to_s
|
|
67
|
+
rescue SystemCallError
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def load_cache(cache_path, cache_key)
|
|
72
|
+
cache_data = cache_path.binread
|
|
73
|
+
return Marshal.load(cache_data) unless @secret
|
|
74
|
+
|
|
75
|
+
signature = cache_data.byteslice(0, HMAC_SIZE)
|
|
76
|
+
payload = cache_data.byteslice(HMAC_SIZE..-1)
|
|
77
|
+
raise InvalidCache unless signature && payload
|
|
35
78
|
|
|
36
|
-
|
|
37
|
-
|
|
79
|
+
expected_signature = signature_for(cache_key, payload)
|
|
80
|
+
unless secure_compare(signature, expected_signature)
|
|
81
|
+
raise InvalidCache
|
|
82
|
+
end
|
|
83
|
+
Marshal.load(payload)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def write_cache(cache_path, cache_key, payload)
|
|
87
|
+
@path.mkpath
|
|
88
|
+
serialized_payload = Marshal.dump(payload)
|
|
89
|
+
cache_data = if @secret
|
|
90
|
+
signature_for(cache_key, serialized_payload) + serialized_payload
|
|
38
91
|
else
|
|
39
|
-
|
|
40
|
-
|
|
92
|
+
serialized_payload
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
Tempfile.create(['graphql-cache-', '.tmp'], @path.to_s) do |tempfile|
|
|
96
|
+
tempfile.binmode
|
|
97
|
+
tempfile.write(cache_data)
|
|
98
|
+
tempfile.flush
|
|
99
|
+
tempfile.fsync
|
|
100
|
+
tempfile.close
|
|
101
|
+
File.rename(tempfile.path, cache_path.to_s)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def signature_for(cache_key, payload)
|
|
106
|
+
OpenSSL::HMAC.digest('SHA256', @secret, cache_key + payload)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def secure_compare(left, right)
|
|
110
|
+
return false unless left.bytesize == right.bytesize
|
|
41
111
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
payload
|
|
112
|
+
result = 0
|
|
113
|
+
left.bytes.each_with_index do |byte, index|
|
|
114
|
+
result |= byte ^ right.getbyte(index)
|
|
46
115
|
end
|
|
116
|
+
result.zero?
|
|
47
117
|
end
|
|
48
118
|
end
|
|
49
119
|
end
|
|
@@ -52,8 +52,9 @@ module GraphQL
|
|
|
52
52
|
|
|
53
53
|
def build_object_type_node(object_type)
|
|
54
54
|
ints = @types.interfaces(object_type)
|
|
55
|
+
|
|
55
56
|
if !ints.empty?
|
|
56
|
-
ints.sort_by
|
|
57
|
+
ints = ints.sort_by(&:graphql_name)
|
|
57
58
|
ints.map! { |iface| build_type_name_node(iface) }
|
|
58
59
|
end
|
|
59
60
|
|
|
@@ -9,6 +9,9 @@ module GraphQL
|
|
|
9
9
|
end
|
|
10
10
|
@string = graphql_str
|
|
11
11
|
@filename = filename
|
|
12
|
+
if !@string.valid_encoding?
|
|
13
|
+
raise_parse_error("Parse error on bad Unicode escape sequence", nil, nil)
|
|
14
|
+
end
|
|
12
15
|
@scanner = StringScanner.new(graphql_str)
|
|
13
16
|
@pos = nil
|
|
14
17
|
@max_tokens = max_tokens || Float::INFINITY
|
|
@@ -28,7 +31,15 @@ module GraphQL
|
|
|
28
31
|
attr_reader :pos, :tokens_count
|
|
29
32
|
|
|
30
33
|
def advance
|
|
31
|
-
|
|
34
|
+
loop do
|
|
35
|
+
@scanner.skip(IGNORE_REGEXP)
|
|
36
|
+
if @scanner.skip(COMMENT_REGEXP)
|
|
37
|
+
@tokens_count += 1
|
|
38
|
+
next
|
|
39
|
+
end
|
|
40
|
+
break
|
|
41
|
+
end
|
|
42
|
+
|
|
32
43
|
if @scanner.eos?
|
|
33
44
|
@finished = true
|
|
34
45
|
return false
|
|
@@ -102,10 +113,6 @@ module GraphQL
|
|
|
102
113
|
@scanner.pos += 1
|
|
103
114
|
:UNKNOWN_CHAR
|
|
104
115
|
end
|
|
105
|
-
rescue ArgumentError => err
|
|
106
|
-
if err.message == "invalid byte sequence in UTF-8"
|
|
107
|
-
raise_parse_error("Parse error on bad Unicode escape sequence", nil, nil)
|
|
108
|
-
end
|
|
109
116
|
end
|
|
110
117
|
|
|
111
118
|
def token_value
|
|
@@ -122,7 +129,7 @@ module GraphQL
|
|
|
122
129
|
elsif token_name == :STRING
|
|
123
130
|
string_value
|
|
124
131
|
elsif @scanner.matched_size.nil?
|
|
125
|
-
@scanner.
|
|
132
|
+
@string.byteslice(@scanner.pos - 1, 1)
|
|
126
133
|
else
|
|
127
134
|
token_value
|
|
128
135
|
end
|
|
@@ -139,7 +146,7 @@ module GraphQL
|
|
|
139
146
|
"\\r" => "\r",
|
|
140
147
|
"\\t" => "\t",
|
|
141
148
|
}
|
|
142
|
-
UTF_8 = /\\u(?:([\
|
|
149
|
+
UTF_8 = /\\u(?:([\da-f]{4})|\{([\da-f]+)\})(?:\\u([\da-f]{4}))?/i
|
|
143
150
|
VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o
|
|
144
151
|
ESCAPED = /(?:#{ESCAPES}|#{UTF_8})/o
|
|
145
152
|
|
|
@@ -155,7 +162,11 @@ module GraphQL
|
|
|
155
162
|
if !str.valid_encoding? || !str.match?(VALID_STRING)
|
|
156
163
|
raise_parse_error("Bad unicode escape in #{str.inspect}")
|
|
157
164
|
else
|
|
158
|
-
|
|
165
|
+
begin
|
|
166
|
+
Lexer.replace_escaped_characters_in_place(str)
|
|
167
|
+
rescue RangeError
|
|
168
|
+
raise_parse_error("Bad unicode escape in #{str.inspect}")
|
|
169
|
+
end
|
|
159
170
|
|
|
160
171
|
if !str.valid_encoding?
|
|
161
172
|
raise_parse_error("Bad unicode escape in #{str.inspect}")
|
|
@@ -167,23 +178,22 @@ module GraphQL
|
|
|
167
178
|
end
|
|
168
179
|
|
|
169
180
|
def line_number
|
|
170
|
-
@scanner.string
|
|
181
|
+
@scanner.string.byteslice(0, @pos).b.count("\n".b) + 1
|
|
171
182
|
end
|
|
172
183
|
|
|
173
184
|
def column_number
|
|
174
|
-
@scanner.string
|
|
185
|
+
line_prefix = @scanner.string.byteslice(0, @pos)
|
|
186
|
+
line_prefix = line_prefix.b unless line_prefix.valid_encoding?
|
|
187
|
+
newline_index = line_prefix.rindex("\n")
|
|
188
|
+
newline_index ? line_prefix.length - newline_index : line_prefix.length + 1
|
|
175
189
|
end
|
|
176
190
|
|
|
177
191
|
def raise_parse_error(message, line = line_number, col = column_number)
|
|
178
192
|
raise GraphQL::ParseError.new(message, line, col, @string, filename: @filename)
|
|
179
193
|
end
|
|
180
194
|
|
|
181
|
-
IGNORE_REGEXP =
|
|
182
|
-
|
|
183
|
-
[, \c\r\n\t]+ |
|
|
184
|
-
\#.*$
|
|
185
|
-
)*
|
|
186
|
-
}x
|
|
195
|
+
IGNORE_REGEXP = /[, \c\r\n\t]+/
|
|
196
|
+
COMMENT_REGEXP = /\#[^\n]*/
|
|
187
197
|
IDENTIFIER_REGEXP = /[_A-Za-z][_0-9A-Za-z]*/
|
|
188
198
|
INT_REGEXP = /-?(?:[0]|[1-9][0-9]*)/
|
|
189
199
|
FLOAT_DECIMAL_REGEXP = /[.][0-9]+/
|
|
@@ -282,7 +292,7 @@ module GraphQL
|
|
|
282
292
|
QUOTE = '"'
|
|
283
293
|
UNICODE_DIGIT = /[0-9A-Za-z]/
|
|
284
294
|
FOUR_DIGIT_UNICODE = /#{UNICODE_DIGIT}{4}/
|
|
285
|
-
N_DIGIT_UNICODE = %r{#{Punctuation::LCURLY}#{UNICODE_DIGIT}{
|
|
295
|
+
N_DIGIT_UNICODE = %r{#{Punctuation::LCURLY}#{UNICODE_DIGIT}+#{Punctuation::RCURLY}}x
|
|
286
296
|
UNICODE_ESCAPE = %r{\\u(?:#{FOUR_DIGIT_UNICODE}|#{N_DIGIT_UNICODE})}
|
|
287
297
|
STRING_ESCAPE = %r{[\\][\\/bfnrt]}
|
|
288
298
|
BLOCK_QUOTE = '"""'
|
|
@@ -304,7 +304,8 @@ module GraphQL
|
|
|
304
304
|
children_method_names.map { |m| "#{m}: NO_CHILDREN" } +
|
|
305
305
|
DEFAULT_INITIALIZE_OPTIONS
|
|
306
306
|
|
|
307
|
-
|
|
307
|
+
# Intern descriptions so identical strings across SDL documents share one frozen object.
|
|
308
|
+
assignments = scalar_method_names.map { |m| m == :description ? "@#{m} = #{m} && -#{m}" : "@#{m} = #{m}" } +
|
|
308
309
|
children_method_names.map { |m| "@#{m} = #{m}.freeze" }
|
|
309
310
|
|
|
310
311
|
if name.end_with?("Definition") && name != "FragmentDefinition"
|
|
@@ -806,7 +806,7 @@ module GraphQL
|
|
|
806
806
|
|
|
807
807
|
def expect_token(expected_token_name)
|
|
808
808
|
unless @token_name == expected_token_name
|
|
809
|
-
raise_parse_error("Expected #{expected_token_name},
|
|
809
|
+
raise_parse_error("Expected #{expected_token_name}, #{@token_name == false ? "not end of file" : "actual: #{@token_name} (#{debug_token_value.inspect})"}")
|
|
810
810
|
end
|
|
811
811
|
advance_token
|
|
812
812
|
end
|
data/lib/graphql/language.rb
CHANGED
|
@@ -31,11 +31,11 @@ module GraphQL
|
|
|
31
31
|
|
|
32
32
|
"[#{serialized_array}]"
|
|
33
33
|
else
|
|
34
|
-
JSON.generate(value
|
|
34
|
+
JSON.generate(value)
|
|
35
35
|
end
|
|
36
36
|
rescue JSON::GeneratorError
|
|
37
|
-
if Float
|
|
38
|
-
|
|
37
|
+
if value.is_a?(Float) && !value.finite?
|
|
38
|
+
value.to_s
|
|
39
39
|
else
|
|
40
40
|
raise
|
|
41
41
|
end
|
|
@@ -47,13 +47,19 @@ module GraphQL
|
|
|
47
47
|
def self.escape_single_quoted_newlines(query_str)
|
|
48
48
|
scanner = StringScanner.new(query_str)
|
|
49
49
|
inside_single_quoted_string = false
|
|
50
|
+
inside_triple_quoted_string = false
|
|
50
51
|
new_query_str = nil
|
|
51
52
|
while !scanner.eos?
|
|
52
|
-
if scanner.skip(
|
|
53
|
+
if scanner.skip('"""')
|
|
54
|
+
inside_triple_quoted_string = !inside_triple_quoted_string
|
|
55
|
+
new_query_str && (new_query_str << scanner.matched)
|
|
56
|
+
elsif scanner.skip(/(?:\\"|[^"\n\r])+/m)
|
|
53
57
|
new_query_str && (new_query_str << scanner.matched)
|
|
54
58
|
elsif scanner.skip('"')
|
|
55
59
|
new_query_str && (new_query_str << '"')
|
|
56
|
-
|
|
60
|
+
if !inside_triple_quoted_string
|
|
61
|
+
inside_single_quoted_string = !inside_single_quoted_string
|
|
62
|
+
end
|
|
57
63
|
elsif scanner.skip("\n")
|
|
58
64
|
if inside_single_quoted_string
|
|
59
65
|
new_query_str ||= query_str[0, scanner.pos - 1]
|
|
@@ -77,21 +83,30 @@ module GraphQL
|
|
|
77
83
|
new_query_str || query_str
|
|
78
84
|
end
|
|
79
85
|
|
|
86
|
+
LEADING_REGEX = Regexp.union(" ", *Lexer::Punctuation.constants.map { |const| Lexer::Punctuation.const_get(const) })
|
|
87
|
+
|
|
88
|
+
# Optimized pattern using:
|
|
89
|
+
# - Possessive quantifiers (*+, ++) to prevent backtracking in number patterns
|
|
90
|
+
# - Atomic group (?>...) for IGNORE to prevent backtracking
|
|
91
|
+
# - Single unified number pattern instead of three alternatives
|
|
92
|
+
EFFICIENT_NUMBER_REGEXP = /-?(?:0|[1-9][0-9]*+)(?:\.[0-9]++)?(?:[eE][+-]?[0-9]++)?/
|
|
93
|
+
EFFICIENT_IGNORE_REGEXP = /(?>[, \r\n\t]+|\#[^\n]*$)*/
|
|
94
|
+
|
|
95
|
+
MAYBE_INVALID_NUMBER = /\d[_a-zA-Z]/
|
|
96
|
+
|
|
80
97
|
INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP = %r{
|
|
81
|
-
(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
)}x
|
|
98
|
+
(?<leading>#{LEADING_REGEX})
|
|
99
|
+
(?<num>#{EFFICIENT_NUMBER_REGEXP})
|
|
100
|
+
(?<name>#{Lexer::IDENTIFIER_REGEXP})
|
|
101
|
+
#{EFFICIENT_IGNORE_REGEXP}
|
|
102
|
+
:
|
|
103
|
+
}x
|
|
88
104
|
|
|
89
105
|
def self.add_space_between_numbers_and_names(query_str)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
end
|
|
106
|
+
# Fast check for digit followed by identifier char. If this doesn't match, skip the more expensive regexp entirely.
|
|
107
|
+
return query_str unless query_str.match?(MAYBE_INVALID_NUMBER)
|
|
108
|
+
return query_str unless query_str.match?(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP)
|
|
109
|
+
query_str.gsub(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP, "\\k<leading>\\k<num> \\k<name>:")
|
|
95
110
|
end
|
|
96
111
|
end
|
|
97
112
|
end
|
|
@@ -83,6 +83,36 @@ module GraphQL
|
|
|
83
83
|
end
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
+
def populate_connection(field, object, value, original_arguments, context)
|
|
87
|
+
if value.is_a? GraphQL::ExecutionError
|
|
88
|
+
raise value
|
|
89
|
+
elsif value.nil?
|
|
90
|
+
nil
|
|
91
|
+
elsif value.is_a?(GraphQL::Pagination::Connection)
|
|
92
|
+
# update the connection with some things that may not have been provided
|
|
93
|
+
value.context ||= context
|
|
94
|
+
value.parent ||= object
|
|
95
|
+
value.first_value ||= original_arguments[:first]
|
|
96
|
+
value.after_value ||= original_arguments[:after]
|
|
97
|
+
value.last_value ||= original_arguments[:last]
|
|
98
|
+
value.before_value ||= original_arguments[:before]
|
|
99
|
+
value.arguments ||= original_arguments # rubocop:disable Development/ContextIsPassedCop -- unrelated .arguments method
|
|
100
|
+
value.field ||= field
|
|
101
|
+
if field.has_max_page_size? && !value.has_max_page_size_override?
|
|
102
|
+
value.max_page_size = field.max_page_size
|
|
103
|
+
end
|
|
104
|
+
if field.has_default_page_size? && !value.has_default_page_size_override?
|
|
105
|
+
value.default_page_size = field.default_page_size
|
|
106
|
+
end
|
|
107
|
+
if (custom_t = context.schema.connections.edge_class_for_field(field))
|
|
108
|
+
value.edge_class = custom_t
|
|
109
|
+
end
|
|
110
|
+
value
|
|
111
|
+
else
|
|
112
|
+
context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
|
|
113
|
+
context.schema.connections.wrap(field, object, value, original_arguments, context)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
86
116
|
# use an override if there is one
|
|
87
117
|
# @api private
|
|
88
118
|
def edge_class_for_field(field)
|
|
@@ -221,7 +221,16 @@ module GraphQL
|
|
|
221
221
|
# returns an array of nodes
|
|
222
222
|
def load_nodes
|
|
223
223
|
# Return an array so we can consistently use `.index(node)` on it
|
|
224
|
-
@nodes
|
|
224
|
+
return @nodes if @nodes
|
|
225
|
+
if (@context[:dataloader].is_a?(GraphQL::Dataloader::AsyncDataloader))
|
|
226
|
+
# `AsyncDataloader` may resolve sibling fields (eg, `edges` and `pageInfo`)
|
|
227
|
+
# in separate Fibers, so several callers can get here before `@nodes` is set.
|
|
228
|
+
(@load_lock ||= Mutex.new).synchronize do
|
|
229
|
+
@nodes ||= limited_nodes.to_a
|
|
230
|
+
end
|
|
231
|
+
else
|
|
232
|
+
@nodes = limited_nodes.to_a
|
|
233
|
+
end
|
|
225
234
|
end
|
|
226
235
|
end
|
|
227
236
|
end
|
|
@@ -29,6 +29,7 @@ module GraphQL
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
extend Forwardable
|
|
32
|
+
include Schema::Member::HasDataloader
|
|
32
33
|
|
|
33
34
|
# @return [Array<GraphQL::ExecutionError>] errors returned during execution
|
|
34
35
|
attr_reader :errors
|
|
@@ -111,20 +112,26 @@ module GraphQL
|
|
|
111
112
|
# Return this value to tell the runtime
|
|
112
113
|
# to exclude this field from the response altogether
|
|
113
114
|
def skip
|
|
114
|
-
GraphQL::Execution::
|
|
115
|
+
GraphQL::Execution::Skip.new
|
|
115
116
|
end
|
|
116
117
|
|
|
117
118
|
# Add error at query-level.
|
|
118
119
|
# @param error [GraphQL::ExecutionError] an execution error
|
|
119
120
|
# @return [void]
|
|
120
121
|
def add_error(error)
|
|
121
|
-
if !error.is_a?(
|
|
122
|
-
raise TypeError, "expected error to be a
|
|
122
|
+
if !error.is_a?(GraphQL::RuntimeError)
|
|
123
|
+
raise TypeError, "expected error to be a GraphQL::RuntimeError, but was #{error.class}"
|
|
123
124
|
end
|
|
124
125
|
errors << error
|
|
125
126
|
nil
|
|
126
127
|
end
|
|
127
128
|
|
|
129
|
+
# @param value [Object] Any object to be inserted directly into the final response
|
|
130
|
+
# @return [GraphQL::Execution::Interpreter::RawValue] Return this from the field
|
|
131
|
+
def raw_value(value)
|
|
132
|
+
GraphQL::Execution::Interpreter::RawValue.new(value)
|
|
133
|
+
end
|
|
134
|
+
|
|
128
135
|
# @example Print the GraphQL backtrace during field resolution
|
|
129
136
|
# puts ctx.backtrace
|
|
130
137
|
#
|
|
@@ -4,7 +4,13 @@ module GraphQL
|
|
|
4
4
|
class Query
|
|
5
5
|
# This object can be `ctx` in places where there is no query
|
|
6
6
|
class NullContext < Context
|
|
7
|
-
|
|
7
|
+
def self.instance
|
|
8
|
+
@instance ||= self.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.instance=(new_inst)
|
|
12
|
+
@instance = new_inst
|
|
13
|
+
end
|
|
8
14
|
|
|
9
15
|
class NullQuery
|
|
10
16
|
def after_lazy(value)
|
|
@@ -20,10 +26,10 @@ module GraphQL
|
|
|
20
26
|
attr_reader :schema, :query, :warden, :dataloader
|
|
21
27
|
def_delegators GraphQL::EmptyObjects::EMPTY_HASH, :[], :fetch, :dig, :key?, :to_h
|
|
22
28
|
|
|
23
|
-
def initialize
|
|
29
|
+
def initialize(schema: NullSchema)
|
|
24
30
|
@query = NullQuery.new
|
|
25
31
|
@dataloader = GraphQL::Dataloader::NullDataloader.new
|
|
26
|
-
@schema =
|
|
32
|
+
@schema = schema
|
|
27
33
|
@warden = Schema::Warden::NullWarden.new(context: self, schema: @schema)
|
|
28
34
|
@types = @warden.visibility_profile
|
|
29
35
|
freeze
|
|
@@ -32,6 +32,7 @@ module GraphQL
|
|
|
32
32
|
@multiplex = nil
|
|
33
33
|
@result_values = nil
|
|
34
34
|
@result = nil
|
|
35
|
+
@finalizers = @top_level_finalizers = nil
|
|
35
36
|
|
|
36
37
|
if fragment_node
|
|
37
38
|
@ast_nodes = [fragment_node]
|
|
@@ -51,6 +52,10 @@ module GraphQL
|
|
|
51
52
|
@leaf
|
|
52
53
|
end
|
|
53
54
|
|
|
55
|
+
def root_value
|
|
56
|
+
object
|
|
57
|
+
end
|
|
58
|
+
|
|
54
59
|
attr_reader :context, :query, :ast_nodes, :root_type, :object, :field_definition, :path, :schema
|
|
55
60
|
|
|
56
61
|
attr_accessor :multiplex, :result_values
|
|
@@ -90,10 +95,22 @@ module GraphQL
|
|
|
90
95
|
@query.fragments
|
|
91
96
|
end
|
|
92
97
|
|
|
98
|
+
def validate
|
|
99
|
+
@query.validate
|
|
100
|
+
end
|
|
101
|
+
|
|
93
102
|
def valid?
|
|
94
103
|
@query.valid?
|
|
95
104
|
end
|
|
96
105
|
|
|
106
|
+
def query?
|
|
107
|
+
true
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def run_partials(...)
|
|
111
|
+
@query.run_partials(...)
|
|
112
|
+
end
|
|
113
|
+
|
|
97
114
|
def analyzers
|
|
98
115
|
EmptyObjects::EMPTY_ARRAY
|
|
99
116
|
end
|
|
@@ -107,7 +124,7 @@ module GraphQL
|
|
|
107
124
|
end
|
|
108
125
|
|
|
109
126
|
def selected_operation
|
|
110
|
-
ast_nodes.
|
|
127
|
+
Language::Nodes::OperationDefinition.new(selections: ast_nodes.flat_map(&:selections))
|
|
111
128
|
end
|
|
112
129
|
|
|
113
130
|
def static_errors
|
|
@@ -123,7 +140,6 @@ module GraphQL
|
|
|
123
140
|
def set_type_info_from_path
|
|
124
141
|
selections = [@query.selected_operation]
|
|
125
142
|
type = @query.root_type
|
|
126
|
-
parent_type = nil
|
|
127
143
|
field_defn = nil
|
|
128
144
|
|
|
129
145
|
@path.each do |name_in_doc|
|
|
@@ -162,7 +178,6 @@ module GraphQL
|
|
|
162
178
|
end
|
|
163
179
|
field_name = next_selections.first.name
|
|
164
180
|
field_defn = @schema.get_field(type, field_name, @query.context) || raise("Invariant: no field called #{field_name} on #{type.graphql_name}")
|
|
165
|
-
parent_type = type
|
|
166
181
|
type = field_defn.type
|
|
167
182
|
if type.non_null?
|
|
168
183
|
type = type.of_type
|
|
@@ -22,7 +22,7 @@ module GraphQL
|
|
|
22
22
|
# It is possible there are other extension items in this error, so handle
|
|
23
23
|
# a one level deep merge explicitly. However beyond that only show the
|
|
24
24
|
# latest value and problems.
|
|
25
|
-
super.merge({ "extensions" => { "value" =>
|
|
25
|
+
super.merge({ "extensions" => { "value" => value_for_extensions, "problems" => validation_result.problems }}) do |key, oldValue, newValue|
|
|
26
26
|
if oldValue.respond_to?(:merge)
|
|
27
27
|
oldValue.merge(newValue)
|
|
28
28
|
else
|
|
@@ -33,6 +33,21 @@ module GraphQL
|
|
|
33
33
|
|
|
34
34
|
private
|
|
35
35
|
|
|
36
|
+
def value_for_extensions(value = @value)
|
|
37
|
+
case value
|
|
38
|
+
when Array
|
|
39
|
+
value.map { |item| value_for_extensions(item) }
|
|
40
|
+
when Hash
|
|
41
|
+
value.each_with_object({}) do |(key, item), result|
|
|
42
|
+
result[key] = value_for_extensions(item)
|
|
43
|
+
end
|
|
44
|
+
when Float
|
|
45
|
+
value.finite? ? value : value.to_s
|
|
46
|
+
else
|
|
47
|
+
value
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
36
51
|
def problem_fields
|
|
37
52
|
@problem_fields ||= @validation_result
|
|
38
53
|
.problems
|
|
@@ -19,7 +19,7 @@ module GraphQL
|
|
|
19
19
|
@storage = ast_variables.each_with_object({}) do |ast_variable, memo|
|
|
20
20
|
if schema.validate_max_errors && schema.validate_max_errors <= @errors.count
|
|
21
21
|
add_max_errors_reached_message
|
|
22
|
-
break
|
|
22
|
+
break memo
|
|
23
23
|
end
|
|
24
24
|
# Find the right value for this variable:
|
|
25
25
|
# - First, use the value provided at runtime
|
data/lib/graphql/query.rb
CHANGED
|
@@ -45,8 +45,8 @@ module GraphQL
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
# @api private
|
|
48
|
-
def handle_or_reraise(err)
|
|
49
|
-
@schema.handle_or_reraise(context, err)
|
|
48
|
+
def handle_or_reraise(err, **kwargs)
|
|
49
|
+
@schema.handle_or_reraise(context, err, **kwargs)
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
@@ -159,6 +159,7 @@ module GraphQL
|
|
|
159
159
|
@root_value = root_value
|
|
160
160
|
@fragments = nil
|
|
161
161
|
@operations = nil
|
|
162
|
+
@finalizers = @top_level_finalizers = nil
|
|
162
163
|
@validate = validate
|
|
163
164
|
self.static_validator = static_validator if static_validator
|
|
164
165
|
context_tracers = (context ? context.fetch(:tracers, []) : [])
|
|
@@ -262,6 +263,10 @@ module GraphQL
|
|
|
262
263
|
with_prepared_ast { @operations }
|
|
263
264
|
end
|
|
264
265
|
|
|
266
|
+
def path
|
|
267
|
+
EmptyObjects::EMPTY_ARRAY
|
|
268
|
+
end
|
|
269
|
+
|
|
265
270
|
# Run subtree partials of this query and return their results.
|
|
266
271
|
# Each partial is identified with a `path:` and `object:`
|
|
267
272
|
# where the path references a field in the AST and the object will be treated
|
|
@@ -271,7 +276,11 @@ module GraphQL
|
|
|
271
276
|
# @return [Array<GraphQL::Query::Result>]
|
|
272
277
|
def run_partials(partials_hashes)
|
|
273
278
|
partials = partials_hashes.map { |partial_options| Partial.new(query: self, **partial_options) }
|
|
274
|
-
|
|
279
|
+
if context[:__graphql_execute_next]
|
|
280
|
+
Execution::Next.run_all(@schema, partials, context: @context)
|
|
281
|
+
else
|
|
282
|
+
Execution::Interpreter.run_all(@schema, partials, context: @context)
|
|
283
|
+
end
|
|
275
284
|
end
|
|
276
285
|
|
|
277
286
|
# Get the result for this query, executing it once
|