graphql 1.10.3 → 1.10.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql/execution/interpreter.rb +1 -0
- data/lib/graphql/execution/interpreter/arguments_cache.rb +66 -0
- data/lib/graphql/execution/interpreter/runtime.rb +2 -42
- data/lib/graphql/language.rb +1 -0
- data/lib/graphql/language/nodes.rb +2 -0
- data/lib/graphql/language/printer.rb +1 -1
- data/lib/graphql/language/sanitized_printer.rb +189 -0
- data/lib/graphql/query.rb +22 -6
- data/lib/graphql/schema.rb +36 -18
- data/lib/graphql/schema/argument.rb +1 -1
- data/lib/graphql/schema/build_from_definition.rb +6 -14
- data/lib/graphql/schema/field.rb +4 -3
- data/lib/graphql/tracing.rb +1 -0
- data/lib/graphql/tracing/appoptics_tracing.rb +163 -0
- data/lib/graphql/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: abb84b7f4f94ae78b7d23d8726590780465d0018a3b225fb9b0cefbd557e6c1b
|
4
|
+
data.tar.gz: 78222841f7996f8449081d272169e6bd7e120f5937a08ea04a33c04217ff21dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d935f76873acb5bf0f15f9fa7c6d74a1a8ac5268d4885216698b206c66cda923b5cc269f90cf708ac296bf5d2973f9e45fb52558b5223cc3c345ae2650933e0c
|
7
|
+
data.tar.gz: 601450b86d9d6e9f9445060c24ba0dc74ce88ab2d69b2551b5e8836b4f02d35a0794e75e32c6db60eaefffc22878c87b07162c77a65b8ea1b5e241d6262881ca
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Execution
|
5
|
+
class Interpreter
|
6
|
+
class ArgumentsCache
|
7
|
+
def initialize(query)
|
8
|
+
@query = query
|
9
|
+
@storage = Hash.new do |h, ast_node|
|
10
|
+
h[ast_node] = Hash.new do |h2, arg_owner|
|
11
|
+
h2[arg_owner] = Hash.new do |h3, parent_object|
|
12
|
+
# First, normalize all AST or Ruby values to a plain Ruby hash
|
13
|
+
args_hash = prepare_args_hash(ast_node)
|
14
|
+
# Then call into the schema to coerce those incoming values
|
15
|
+
arg_owner.coerce_arguments(parent_object, args_hash, query.context)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch(ast_node, argument_owner, parent_object)
|
22
|
+
@storage[ast_node][argument_owner][parent_object]
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
NO_VALUE_GIVEN = Object.new
|
28
|
+
|
29
|
+
def prepare_args_hash(ast_arg_or_hash_or_value)
|
30
|
+
case ast_arg_or_hash_or_value
|
31
|
+
when Hash
|
32
|
+
args_hash = {}
|
33
|
+
ast_arg_or_hash_or_value.each do |k, v|
|
34
|
+
args_hash[k] = prepare_args_hash(v)
|
35
|
+
end
|
36
|
+
args_hash
|
37
|
+
when Array
|
38
|
+
ast_arg_or_hash_or_value.map { |v| prepare_args_hash(v) }
|
39
|
+
when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive
|
40
|
+
args_hash = {}
|
41
|
+
ast_arg_or_hash_or_value.arguments.each do |arg|
|
42
|
+
v = prepare_args_hash(arg.value)
|
43
|
+
if v != NO_VALUE_GIVEN
|
44
|
+
args_hash[arg.name] = v
|
45
|
+
end
|
46
|
+
end
|
47
|
+
args_hash
|
48
|
+
when GraphQL::Language::Nodes::VariableIdentifier
|
49
|
+
if @query.variables.key?(ast_arg_or_hash_or_value.name)
|
50
|
+
variable_value = @query.variables[ast_arg_or_hash_or_value.name]
|
51
|
+
prepare_args_hash(variable_value)
|
52
|
+
else
|
53
|
+
NO_VALUE_GIVEN
|
54
|
+
end
|
55
|
+
when GraphQL::Language::Nodes::Enum
|
56
|
+
ast_arg_or_hash_or_value.name
|
57
|
+
when GraphQL::Language::Nodes::NullValue
|
58
|
+
nil
|
59
|
+
else
|
60
|
+
ast_arg_or_hash_or_value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -436,48 +436,8 @@ module GraphQL
|
|
436
436
|
end
|
437
437
|
end
|
438
438
|
|
439
|
-
|
440
|
-
|
441
|
-
def prepare_args_hash(ast_arg_or_hash_or_value)
|
442
|
-
case ast_arg_or_hash_or_value
|
443
|
-
when Hash
|
444
|
-
args_hash = {}
|
445
|
-
ast_arg_or_hash_or_value.each do |k, v|
|
446
|
-
args_hash[k] = prepare_args_hash(v)
|
447
|
-
end
|
448
|
-
args_hash
|
449
|
-
when Array
|
450
|
-
ast_arg_or_hash_or_value.map { |v| prepare_args_hash(v) }
|
451
|
-
when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive
|
452
|
-
args_hash = {}
|
453
|
-
ast_arg_or_hash_or_value.arguments.each do |arg|
|
454
|
-
v = prepare_args_hash(arg.value)
|
455
|
-
if v != NO_VALUE_GIVEN
|
456
|
-
args_hash[arg.name] = v
|
457
|
-
end
|
458
|
-
end
|
459
|
-
args_hash
|
460
|
-
when GraphQL::Language::Nodes::VariableIdentifier
|
461
|
-
if query.variables.key?(ast_arg_or_hash_or_value.name)
|
462
|
-
variable_value = query.variables[ast_arg_or_hash_or_value.name]
|
463
|
-
prepare_args_hash(variable_value)
|
464
|
-
else
|
465
|
-
NO_VALUE_GIVEN
|
466
|
-
end
|
467
|
-
when GraphQL::Language::Nodes::Enum
|
468
|
-
ast_arg_or_hash_or_value.name
|
469
|
-
when GraphQL::Language::Nodes::NullValue
|
470
|
-
nil
|
471
|
-
else
|
472
|
-
ast_arg_or_hash_or_value
|
473
|
-
end
|
474
|
-
end
|
475
|
-
|
476
|
-
def arguments(graphql_object, arg_owner, ast_node_or_hash)
|
477
|
-
# First, normalize all AST or Ruby values to a plain Ruby hash
|
478
|
-
args_hash = prepare_args_hash(ast_node_or_hash)
|
479
|
-
# Then call into the schema to coerce those incoming values
|
480
|
-
arg_owner.coerce_arguments(graphql_object, args_hash, context)
|
439
|
+
def arguments(graphql_object, arg_owner, ast_node)
|
440
|
+
query.arguments_for(ast_node, arg_owner, parent_object: graphql_object)
|
481
441
|
end
|
482
442
|
|
483
443
|
def write_invalid_null_in_response(path, invalid_null_error)
|
data/lib/graphql/language.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "graphql/language/block_string"
|
3
3
|
require "graphql/language/printer"
|
4
|
+
require "graphql/language/sanitized_printer"
|
4
5
|
require "graphql/language/document_from_schema_definition"
|
5
6
|
require "graphql/language/generation"
|
6
7
|
require "graphql/language/lexer"
|
@@ -93,7 +93,7 @@ module GraphQL
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def print_input_object(input_object)
|
96
|
-
"{#{input_object.arguments.map { |a|
|
96
|
+
"{#{input_object.arguments.map { |a| print_argument(a) }.join(", ")}}"
|
97
97
|
end
|
98
98
|
|
99
99
|
def print_list_type(list_type)
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
module Language
|
4
|
+
# A custom printer used to print sanitized queries. It inlines provided variables
|
5
|
+
# within the query for facilitate logging and analysis of queries.
|
6
|
+
#
|
7
|
+
# The printer returns `nil` if the query is invalid.
|
8
|
+
#
|
9
|
+
# Since the GraphQL Ruby AST for a GraphQL query doesnt contain any reference
|
10
|
+
# on the type of fields or arguments, we have to track the current object, field
|
11
|
+
# and input type while printing the query.
|
12
|
+
#
|
13
|
+
# @example Printing a scrubbed string
|
14
|
+
# printer = QueryPrinter.new(query)
|
15
|
+
# puts printer.sanitized_query_string
|
16
|
+
#
|
17
|
+
# @see {Query#sanitized_query_string}
|
18
|
+
class SanitizedPrinter < GraphQL::Language::Printer
|
19
|
+
|
20
|
+
REDACTED = "\"<REDACTED>\""
|
21
|
+
|
22
|
+
def initialize(query)
|
23
|
+
@query = query
|
24
|
+
@current_type = nil
|
25
|
+
@current_field = nil
|
26
|
+
@current_input_type = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [String, nil] A scrubbed query string, if the query was valid.
|
30
|
+
def sanitized_query_string
|
31
|
+
if query.valid?
|
32
|
+
print(query.document)
|
33
|
+
else
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def print_node(node, indent: "")
|
39
|
+
if node.is_a?(String)
|
40
|
+
type = @current_input_type.unwrap
|
41
|
+
# Replace any strings that aren't IDs or Enum values with REDACTED
|
42
|
+
if type.kind.enum? || type.graphql_name == "ID"
|
43
|
+
super
|
44
|
+
else
|
45
|
+
REDACTED
|
46
|
+
end
|
47
|
+
elsif node.is_a?(Array)
|
48
|
+
old_input_type = @current_input_type
|
49
|
+
if @current_input_type && @current_input_type.list?
|
50
|
+
@current_input_type = @current_input_type.of_type
|
51
|
+
@current_input_type = @current_input_type.of_type if @current_input_type.non_null?
|
52
|
+
end
|
53
|
+
|
54
|
+
res = super
|
55
|
+
@current_input_type = old_input_type
|
56
|
+
res
|
57
|
+
else
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def print_argument(argument)
|
63
|
+
arg_owner = @current_input_type || @current_directive || @current_field
|
64
|
+
arg_def = arg_owner.arguments[argument.name]
|
65
|
+
|
66
|
+
old_input_type = @current_input_type
|
67
|
+
@current_input_type = arg_def.type.non_null? ? arg_def.type.of_type : arg_def.type
|
68
|
+
res = super
|
69
|
+
@current_input_type = old_input_type
|
70
|
+
res
|
71
|
+
end
|
72
|
+
|
73
|
+
def print_list_type(list_type)
|
74
|
+
old_input_type = @current_input_type
|
75
|
+
@current_input_type = old_input_type.of_type
|
76
|
+
res = super
|
77
|
+
@current_input_type = old_input_type
|
78
|
+
res
|
79
|
+
end
|
80
|
+
|
81
|
+
def print_variable_identifier(variable_id)
|
82
|
+
variable_value = query.variables[variable_id.name]
|
83
|
+
print_node(value_to_ast(variable_value, @current_input_type))
|
84
|
+
end
|
85
|
+
|
86
|
+
def print_field(field, indent: "")
|
87
|
+
@current_field = query.schema.get_field(@current_type, field.name)
|
88
|
+
old_type = @current_type
|
89
|
+
@current_type = @current_field.type.unwrap
|
90
|
+
res = super
|
91
|
+
@current_type = old_type
|
92
|
+
res
|
93
|
+
end
|
94
|
+
|
95
|
+
def print_inline_fragment(inline_fragment, indent: "")
|
96
|
+
old_type = @current_type
|
97
|
+
|
98
|
+
if inline_fragment.type
|
99
|
+
@current_type = query.schema.types[inline_fragment.type.name]
|
100
|
+
end
|
101
|
+
|
102
|
+
res = super
|
103
|
+
|
104
|
+
@current_type = old_type
|
105
|
+
|
106
|
+
res
|
107
|
+
end
|
108
|
+
|
109
|
+
def print_fragment_definition(fragment_def, indent: "")
|
110
|
+
old_type = @current_type
|
111
|
+
@current_type = query.schema.types[fragment_def.type.name]
|
112
|
+
|
113
|
+
res = super
|
114
|
+
|
115
|
+
@current_type = old_type
|
116
|
+
|
117
|
+
res
|
118
|
+
end
|
119
|
+
|
120
|
+
def print_directive(directive)
|
121
|
+
@current_directive = query.schema.directives[directive.name]
|
122
|
+
|
123
|
+
res = super
|
124
|
+
|
125
|
+
@current_directive = nil
|
126
|
+
res
|
127
|
+
end
|
128
|
+
|
129
|
+
# Print the operation definition but do not include the variable
|
130
|
+
# definitions since we will inline them within the query
|
131
|
+
def print_operation_definition(operation_definition, indent: "")
|
132
|
+
old_type = @current_type
|
133
|
+
@current_type = query.schema.public_send(operation_definition.operation_type)
|
134
|
+
|
135
|
+
out = "#{indent}#{operation_definition.operation_type}".dup
|
136
|
+
out << " #{operation_definition.name}" if operation_definition.name
|
137
|
+
out << print_directives(operation_definition.directives)
|
138
|
+
out << print_selections(operation_definition.selections, indent: indent)
|
139
|
+
|
140
|
+
@current_type = old_type
|
141
|
+
out
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def value_to_ast(value, type)
|
147
|
+
type = type.of_type if type.non_null?
|
148
|
+
|
149
|
+
if value.nil?
|
150
|
+
return GraphQL::Language::Nodes::NullValue.new(name: "null")
|
151
|
+
end
|
152
|
+
|
153
|
+
case type.kind.name
|
154
|
+
when "INPUT_OBJECT"
|
155
|
+
value = if value.respond_to?(:to_unsafe_h)
|
156
|
+
# for ActionController::Parameters
|
157
|
+
value.to_unsafe_h
|
158
|
+
else
|
159
|
+
value.to_h
|
160
|
+
end
|
161
|
+
|
162
|
+
arguments = value.map do |key, val|
|
163
|
+
sub_type = type.arguments[key.to_s].type
|
164
|
+
|
165
|
+
GraphQL::Language::Nodes::Argument.new(
|
166
|
+
name: key.to_s,
|
167
|
+
value: value_to_ast(val, sub_type)
|
168
|
+
)
|
169
|
+
end
|
170
|
+
GraphQL::Language::Nodes::InputObject.new(
|
171
|
+
arguments: arguments
|
172
|
+
)
|
173
|
+
when "LIST"
|
174
|
+
if value.respond_to?(:each)
|
175
|
+
value.each { |v| value_to_ast(v, type.of_type) }
|
176
|
+
else
|
177
|
+
[value].each { |v| value_to_ast(v, type.of_type) }
|
178
|
+
end
|
179
|
+
when "ENUM"
|
180
|
+
GraphQL::Language::Nodes::Enum.new(name: value)
|
181
|
+
else
|
182
|
+
value
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
attr_reader :query
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
data/lib/graphql/query.rb
CHANGED
@@ -124,8 +124,6 @@ module GraphQL
|
|
124
124
|
end
|
125
125
|
end
|
126
126
|
|
127
|
-
@arguments_cache = ArgumentsCache.build(self)
|
128
|
-
|
129
127
|
# Trying to execute a document
|
130
128
|
# with no operations returns an empty hash
|
131
129
|
@ast_variables = []
|
@@ -243,10 +241,28 @@ module GraphQL
|
|
243
241
|
end
|
244
242
|
|
245
243
|
# Node-level cache for calculating arguments. Used during execution and query analysis.
|
246
|
-
# @
|
247
|
-
# @
|
248
|
-
|
249
|
-
|
244
|
+
# @param ast_node [GraphQL::Language::Nodes::AbstractNode]
|
245
|
+
# @param definition [GraphQL::Schema::Field]
|
246
|
+
# @param parent_object [GraphQL::Schema::Object]
|
247
|
+
# @return Hash{Symbol => Object}
|
248
|
+
def arguments_for(ast_node, definition, parent_object: nil)
|
249
|
+
if interpreter?
|
250
|
+
@arguments_cache ||= Execution::Interpreter::ArgumentsCache.new(self)
|
251
|
+
@arguments_cache.fetch(ast_node, definition, parent_object)
|
252
|
+
else
|
253
|
+
@arguments_cache ||= ArgumentsCache.build(self)
|
254
|
+
@arguments_cache[ast_node][definition]
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# A version of the given query string, with:
|
259
|
+
# - Variables inlined to the query
|
260
|
+
# - Strings replaced with `<REDACTED>`
|
261
|
+
# @return [String, nil] Returns nil if the query is invalid.
|
262
|
+
def sanitized_query_string
|
263
|
+
with_prepared_ast {
|
264
|
+
GraphQL::Language::SanitizedPrinter.new(self).sanitized_query_string
|
265
|
+
}
|
250
266
|
end
|
251
267
|
|
252
268
|
def validation_pipeline
|
data/lib/graphql/schema.rb
CHANGED
@@ -82,6 +82,12 @@ module GraphQL
|
|
82
82
|
include GraphQL::Define::InstanceDefinable
|
83
83
|
extend GraphQL::Schema::FindInheritedValue
|
84
84
|
|
85
|
+
class DuplicateTypeNamesError < GraphQL::Error
|
86
|
+
def initialize(type_name:, first_definition:, second_definition:, path:)
|
87
|
+
super("Multiple definitions for `#{type_name}`. Previously found #{first_definition.inspect} (#{first_definition.class}), then found #{second_definition.inspect} (#{second_definition.class}) at #{path.join(".")}")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
85
91
|
class UnresolvedLateBoundTypeError < GraphQL::Error
|
86
92
|
attr_reader :type
|
87
93
|
def initialize(type:)
|
@@ -715,14 +721,20 @@ module GraphQL
|
|
715
721
|
# @param using [Hash] Plugins to attach to the created schema with `use(key, value)`
|
716
722
|
# @param interpreter [Boolean] If false, the legacy {Execution::Execute} runtime will be used
|
717
723
|
# @return [Class] the schema described by `document`
|
718
|
-
def self.from_definition(definition_or_path, default_resolve:
|
724
|
+
def self.from_definition(definition_or_path, default_resolve: nil, interpreter: true, parser: BuildFromDefinition::DefaultParser, using: {})
|
719
725
|
# If the file ends in `.graphql`, treat it like a filepath
|
720
726
|
definition = if definition_or_path.end_with?(".graphql")
|
721
727
|
File.read(definition_or_path)
|
722
728
|
else
|
723
729
|
definition_or_path
|
724
730
|
end
|
725
|
-
GraphQL::Schema::BuildFromDefinition.from_definition(
|
731
|
+
GraphQL::Schema::BuildFromDefinition.from_definition(
|
732
|
+
definition,
|
733
|
+
default_resolve: default_resolve,
|
734
|
+
parser: parser,
|
735
|
+
using: using,
|
736
|
+
interpreter: interpreter,
|
737
|
+
)
|
726
738
|
end
|
727
739
|
|
728
740
|
# Error that is raised when [#Schema#from_definition] is passed an invalid schema definition string.
|
@@ -1686,7 +1698,7 @@ module GraphQL
|
|
1686
1698
|
end
|
1687
1699
|
late_types = []
|
1688
1700
|
new_types = Array(t)
|
1689
|
-
new_types.each { |t| add_type(t, owner: nil, late_types: late_types) }
|
1701
|
+
new_types.each { |t| add_type(t, owner: nil, late_types: late_types, path: [t.graphql_name]) }
|
1690
1702
|
missed_late_types = 0
|
1691
1703
|
while (late_type_vals = late_types.shift)
|
1692
1704
|
type_owner, lt = late_type_vals
|
@@ -1695,13 +1707,13 @@ module GraphQL
|
|
1695
1707
|
# Reset the counter, since we might succeed next go-round
|
1696
1708
|
missed_late_types = 0
|
1697
1709
|
update_type_owner(type_owner, type)
|
1698
|
-
add_type(type, owner: type_owner, late_types: late_types)
|
1710
|
+
add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name])
|
1699
1711
|
elsif lt.is_a?(LateBoundType)
|
1700
1712
|
if (type = get_type(lt.graphql_name))
|
1701
1713
|
# Reset the counter, since we might succeed next go-round
|
1702
1714
|
missed_late_types = 0
|
1703
1715
|
update_type_owner(type_owner, type)
|
1704
|
-
add_type(type, owner: type_owner, late_types: late_types)
|
1716
|
+
add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name])
|
1705
1717
|
else
|
1706
1718
|
missed_late_types += 1
|
1707
1719
|
# Add it back to the list, maybe we'll be able to resolve it later.
|
@@ -1776,7 +1788,7 @@ module GraphQL
|
|
1776
1788
|
end
|
1777
1789
|
end
|
1778
1790
|
|
1779
|
-
def add_type(type, owner:, late_types:)
|
1791
|
+
def add_type(type, owner:, late_types:, path:)
|
1780
1792
|
if type.respond_to?(:metadata) && type.metadata.is_a?(Hash)
|
1781
1793
|
type_class = type.metadata[:type_class]
|
1782
1794
|
if type_class.nil?
|
@@ -1796,46 +1808,52 @@ module GraphQL
|
|
1796
1808
|
|
1797
1809
|
if (prev_type = own_types[type.graphql_name])
|
1798
1810
|
if prev_type != type
|
1799
|
-
raise
|
1811
|
+
raise DuplicateTypeNamesError.new(
|
1812
|
+
type_name: type.graphql_name,
|
1813
|
+
first_definition: prev_type,
|
1814
|
+
second_definition: type,
|
1815
|
+
path: path,
|
1816
|
+
)
|
1800
1817
|
else
|
1801
1818
|
# This type was already added
|
1802
1819
|
end
|
1803
1820
|
elsif type.is_a?(Class) && type < GraphQL::Schema::Directive
|
1804
|
-
type.arguments.each do |
|
1821
|
+
type.arguments.each do |name, arg|
|
1805
1822
|
arg_type = arg.type.unwrap
|
1806
1823
|
references_to(arg_type, from: arg)
|
1807
|
-
add_type(arg_type, owner: arg, late_types: late_types)
|
1824
|
+
add_type(arg_type, owner: arg, late_types: late_types, path: path + [name])
|
1808
1825
|
end
|
1809
1826
|
else
|
1810
1827
|
own_types[type.graphql_name] = type
|
1811
1828
|
if type.kind.fields?
|
1812
|
-
type.fields.each do |
|
1829
|
+
type.fields.each do |name, field|
|
1813
1830
|
field_type = field.type.unwrap
|
1814
1831
|
references_to(field_type, from: field)
|
1815
|
-
|
1816
|
-
field
|
1832
|
+
field_path = path + [name]
|
1833
|
+
add_type(field_type, owner: field, late_types: late_types, path: field_path)
|
1834
|
+
field.arguments.each do |arg_name, arg|
|
1817
1835
|
arg_type = arg.type.unwrap
|
1818
1836
|
references_to(arg_type, from: arg)
|
1819
|
-
add_type(arg_type, owner: arg, late_types: late_types)
|
1837
|
+
add_type(arg_type, owner: arg, late_types: late_types, path: field_path + [arg_name])
|
1820
1838
|
end
|
1821
1839
|
end
|
1822
1840
|
end
|
1823
1841
|
if type.kind.input_object?
|
1824
|
-
type.arguments.each do |
|
1842
|
+
type.arguments.each do |arg_name, arg|
|
1825
1843
|
arg_type = arg.type.unwrap
|
1826
1844
|
references_to(arg_type, from: arg)
|
1827
|
-
add_type(arg_type, owner: arg, late_types: late_types)
|
1845
|
+
add_type(arg_type, owner: arg, late_types: late_types, path: path + [arg_name])
|
1828
1846
|
end
|
1829
1847
|
end
|
1830
1848
|
if type.kind.union?
|
1831
1849
|
own_possible_types[type.graphql_name] = type.possible_types
|
1832
1850
|
type.possible_types.each do |t|
|
1833
|
-
add_type(t, owner: type, late_types: late_types)
|
1851
|
+
add_type(t, owner: type, late_types: late_types, path: path + ["possible_types"])
|
1834
1852
|
end
|
1835
1853
|
end
|
1836
1854
|
if type.kind.interface?
|
1837
1855
|
type.orphan_types.each do |t|
|
1838
|
-
add_type(t, owner: type, late_types: late_types)
|
1856
|
+
add_type(t, owner: type, late_types: late_types, path: path + ["orphan_types"])
|
1839
1857
|
end
|
1840
1858
|
end
|
1841
1859
|
if type.kind.object?
|
@@ -1843,7 +1861,7 @@ module GraphQL
|
|
1843
1861
|
type.interfaces.each do |i|
|
1844
1862
|
implementers = own_possible_types[i.graphql_name] ||= []
|
1845
1863
|
implementers << type
|
1846
|
-
add_type(i, owner: type, late_types: late_types)
|
1864
|
+
add_type(i, owner: type, late_types: late_types, path: path + ["implements"])
|
1847
1865
|
end
|
1848
1866
|
end
|
1849
1867
|
end
|
@@ -55,7 +55,7 @@ module GraphQL
|
|
55
55
|
@owner = owner
|
56
56
|
@as = as
|
57
57
|
@loads = loads
|
58
|
-
@keyword = as || Schema::Member::BuildType.underscore(@name).to_sym
|
58
|
+
@keyword = as || (arg_name.is_a?(Symbol) ? arg_name : Schema::Member::BuildType.underscore(@name).to_sym)
|
59
59
|
@prepare = prepare
|
60
60
|
@ast_node = ast_node
|
61
61
|
@from_resolver = from_resolver
|
@@ -5,31 +5,22 @@ module GraphQL
|
|
5
5
|
class Schema
|
6
6
|
module BuildFromDefinition
|
7
7
|
class << self
|
8
|
-
|
8
|
+
# @see {Schema.from_definition}
|
9
|
+
def from_definition(definition_string, default_resolve:, using: {}, relay: false, interpreter: true, parser: DefaultParser)
|
9
10
|
document = parser.parse(definition_string)
|
10
|
-
|
11
|
+
default_resolve ||= {}
|
12
|
+
Builder.build(document, default_resolve: default_resolve, relay: relay, using: using, interpreter: interpreter)
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
14
16
|
# @api private
|
15
17
|
DefaultParser = GraphQL::Language::Parser
|
16
18
|
|
17
|
-
# @api private
|
18
|
-
module DefaultResolve
|
19
|
-
def self.call(type, field, obj, args, ctx)
|
20
|
-
if field.arguments.any?
|
21
|
-
obj.public_send(field.name, args, ctx)
|
22
|
-
else
|
23
|
-
obj.public_send(field.name)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
19
|
# @api private
|
29
20
|
module Builder
|
30
21
|
extend self
|
31
22
|
|
32
|
-
def build(document, default_resolve
|
23
|
+
def build(document, default_resolve:, using: {}, interpreter: true, relay:)
|
33
24
|
raise InvalidDocumentError.new('Must provide a document ast.') if !document || !document.is_a?(GraphQL::Language::Nodes::Document)
|
34
25
|
|
35
26
|
if default_resolve.is_a?(Hash)
|
@@ -316,6 +307,7 @@ module GraphQL
|
|
316
307
|
type: type_resolver.call(field_definition.type),
|
317
308
|
null: true,
|
318
309
|
connection: type_name.end_with?("Connection"),
|
310
|
+
connection_extension: nil,
|
319
311
|
deprecation_reason: build_deprecation_reason(field_definition.directives),
|
320
312
|
ast_node: field_definition,
|
321
313
|
method_conflict_warning: false,
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -172,6 +172,7 @@ module GraphQL
|
|
172
172
|
# @param hash_key [String, Symbol] The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`)
|
173
173
|
# @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`)
|
174
174
|
# @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
|
175
|
+
# @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
|
175
176
|
# @param max_page_size [Integer] For connections, the maximum number of items to return from this field
|
176
177
|
# @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
|
177
178
|
# @param resolve [<#call(obj, args, ctx)>] **deprecated** for compatibility with <1.8.0
|
@@ -187,7 +188,7 @@ module GraphQL
|
|
187
188
|
# @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field
|
188
189
|
# @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
|
189
190
|
# @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
|
190
|
-
def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: nil, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: [], extensions: EMPTY_ARRAY, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, arguments: EMPTY_HASH, &definition_block)
|
191
|
+
def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: nil, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: [], extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, arguments: EMPTY_HASH, &definition_block)
|
191
192
|
if name.nil?
|
192
193
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
193
194
|
end
|
@@ -275,8 +276,8 @@ module GraphQL
|
|
275
276
|
end
|
276
277
|
# The problem with putting this after the definition_block
|
277
278
|
# is that it would override arguments
|
278
|
-
if connection?
|
279
|
-
self.extension(
|
279
|
+
if connection? && connection_extension
|
280
|
+
self.extension(connection_extension)
|
280
281
|
end
|
281
282
|
|
282
283
|
if definition_block
|
data/lib/graphql/tracing.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "graphql/tracing/active_support_notifications_tracing"
|
3
3
|
require "graphql/tracing/platform_tracing"
|
4
|
+
require "graphql/tracing/appoptics_tracing"
|
4
5
|
require "graphql/tracing/appsignal_tracing"
|
5
6
|
require "graphql/tracing/data_dog_tracing"
|
6
7
|
require "graphql/tracing/new_relic_tracing"
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Tracing
|
5
|
+
|
6
|
+
# This class uses the AppopticsAPM SDK from the appoptics_apm gem to create
|
7
|
+
# traces for GraphQL.
|
8
|
+
#
|
9
|
+
# There are 4 configurations available. They can be set in the
|
10
|
+
# appoptics_apm config file or in code. Please see:
|
11
|
+
# {https://docs.appoptics.com/kb/apm_tracing/ruby/configure}
|
12
|
+
#
|
13
|
+
# AppOpticsAPM::Config[:graphql][:enabled] = true|false
|
14
|
+
# AppOpticsAPM::Config[:graphql][:transaction_name] = true|false
|
15
|
+
# AppOpticsAPM::Config[:graphql][:sanitize_query] = true|false
|
16
|
+
# AppOpticsAPM::Config[:graphql][:remove_comments] = true|false
|
17
|
+
class AppOpticsTracing < GraphQL::Tracing::PlatformTracing
|
18
|
+
# These GraphQL events will show up as 'graphql.prep' spans
|
19
|
+
PREP_KEYS = ['lex', 'parse', 'validate', 'analyze_query', 'analyze_multiplex'].freeze
|
20
|
+
# These GraphQL events will show up as 'graphql.execute' spans
|
21
|
+
EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze
|
22
|
+
|
23
|
+
# During auto-instrumentation this version of AppOpticsTracing is compared
|
24
|
+
# with the version provided in the appoptics_apm gem, so that the newer
|
25
|
+
# version of the class can be used
|
26
|
+
|
27
|
+
def self.version
|
28
|
+
Gem::Version.new('1.0.0')
|
29
|
+
end
|
30
|
+
|
31
|
+
self.platform_keys = {
|
32
|
+
'lex' => 'lex',
|
33
|
+
'parse' => 'parse',
|
34
|
+
'validate' => 'validate',
|
35
|
+
'analyze_query' => 'analyze_query',
|
36
|
+
'analyze_multiplex' => 'analyze_multiplex',
|
37
|
+
'execute_multiplex' => 'execute_multiplex',
|
38
|
+
'execute_query' => 'execute_query',
|
39
|
+
'execute_query_lazy' => 'execute_query_lazy'
|
40
|
+
}
|
41
|
+
|
42
|
+
def platform_trace(platform_key, _key, data)
|
43
|
+
return yield if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
|
44
|
+
|
45
|
+
layer = span_name(platform_key)
|
46
|
+
kvs = metadata(data, layer)
|
47
|
+
kvs[:Key] = platform_key if (PREP_KEYS + EXEC_KEYS).include?(platform_key)
|
48
|
+
|
49
|
+
transaction_name(kvs[:InboundQuery]) if kvs[:InboundQuery] && layer == 'graphql.execute'
|
50
|
+
|
51
|
+
::AppOpticsAPM::SDK.trace(layer, kvs) do
|
52
|
+
kvs.clear # we don't have to send them twice
|
53
|
+
yield
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def platform_field_key(type, field)
|
58
|
+
"graphql.#{type.name}.#{field.name}"
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def gql_config
|
64
|
+
::AppOpticsAPM::Config[:graphql] ||= {}
|
65
|
+
end
|
66
|
+
|
67
|
+
def transaction_name(query)
|
68
|
+
return if gql_config[:transaction_name] == false ||
|
69
|
+
::AppOpticsAPM::SDK.get_transaction_name
|
70
|
+
|
71
|
+
split_query = query.strip.split(/\W+/, 3)
|
72
|
+
split_query[0] = 'query' if split_query[0].empty?
|
73
|
+
name = "graphql.#{split_query[0..1].join('.')}"
|
74
|
+
|
75
|
+
::AppOpticsAPM::SDK.set_transaction_name(name)
|
76
|
+
end
|
77
|
+
|
78
|
+
def multiplex_transaction_name(names)
|
79
|
+
return if gql_config[:transaction_name] == false ||
|
80
|
+
::AppOpticsAPM::SDK.get_transaction_name
|
81
|
+
|
82
|
+
name = "graphql.multiplex.#{names.join('.')}"
|
83
|
+
name = "#{name[0..251]}..." if name.length > 254
|
84
|
+
|
85
|
+
::AppOpticsAPM::SDK.set_transaction_name(name)
|
86
|
+
end
|
87
|
+
|
88
|
+
def span_name(key)
|
89
|
+
return 'graphql.prep' if PREP_KEYS.include?(key)
|
90
|
+
return 'graphql.execute' if EXEC_KEYS.include?(key)
|
91
|
+
|
92
|
+
key[/^graphql\./] ? key : "graphql.#{key}"
|
93
|
+
end
|
94
|
+
|
95
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
96
|
+
def metadata(data, layer)
|
97
|
+
data.keys.map do |key|
|
98
|
+
case key
|
99
|
+
when :context
|
100
|
+
graphql_context(data[key], layer)
|
101
|
+
when :query
|
102
|
+
graphql_query(data[key])
|
103
|
+
when :query_string
|
104
|
+
graphql_query_string(data[key])
|
105
|
+
when :multiplex
|
106
|
+
graphql_multiplex(data[key])
|
107
|
+
else
|
108
|
+
[key, data[key]]
|
109
|
+
end
|
110
|
+
end.flatten.each_slice(2).to_h.merge(Spec: 'graphql')
|
111
|
+
end
|
112
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
113
|
+
|
114
|
+
def graphql_context(context, layer)
|
115
|
+
context.errors && context.errors.each do |err|
|
116
|
+
AppOpticsAPM::API.log_exception(layer, err)
|
117
|
+
end
|
118
|
+
|
119
|
+
[[:Path, context.path.join('.')]]
|
120
|
+
end
|
121
|
+
|
122
|
+
def graphql_query(query)
|
123
|
+
return [] unless query
|
124
|
+
|
125
|
+
query_string = query.query_string
|
126
|
+
query_string = remove_comments(query_string) if gql_config[:remove_comments] != false
|
127
|
+
query_string = sanitize(query_string) if gql_config[:sanitize_query] != false
|
128
|
+
|
129
|
+
[[:InboundQuery, query_string],
|
130
|
+
[:Operation, query.selected_operation_name]]
|
131
|
+
end
|
132
|
+
|
133
|
+
def graphql_query_string(query_string)
|
134
|
+
query_string = remove_comments(query_string) if gql_config[:remove_comments] != false
|
135
|
+
query_string = sanitize(query_string) if gql_config[:sanitize_query] != false
|
136
|
+
|
137
|
+
[:InboundQuery, query_string]
|
138
|
+
end
|
139
|
+
|
140
|
+
def graphql_multiplex(data)
|
141
|
+
names = data.queries.map(&:operations).map(&:keys).flatten.compact
|
142
|
+
multiplex_transaction_name(names) if names.size > 1
|
143
|
+
|
144
|
+
[:Operations, names.join(', ')]
|
145
|
+
end
|
146
|
+
|
147
|
+
def sanitize(query)
|
148
|
+
return unless query
|
149
|
+
|
150
|
+
# remove arguments
|
151
|
+
query.gsub(/"[^"]*"/, '"?"') # strings
|
152
|
+
.gsub(/-?[0-9]*\.?[0-9]+e?[0-9]*/, '?') # ints + floats
|
153
|
+
.gsub(/\[[^\]]*\]/, '[?]') # arrays
|
154
|
+
end
|
155
|
+
|
156
|
+
def remove_comments(query)
|
157
|
+
return unless query
|
158
|
+
|
159
|
+
query.gsub(/#[^\n\r]*/, '')
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
data/lib/graphql/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.10.
|
4
|
+
version: 1.10.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-03-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: benchmark-ips
|
@@ -422,6 +422,7 @@ files:
|
|
422
422
|
- lib/graphql/execution/flatten.rb
|
423
423
|
- lib/graphql/execution/instrumentation.rb
|
424
424
|
- lib/graphql/execution/interpreter.rb
|
425
|
+
- lib/graphql/execution/interpreter/arguments_cache.rb
|
425
426
|
- lib/graphql/execution/interpreter/execution_errors.rb
|
426
427
|
- lib/graphql/execution/interpreter/handles_raw_value.rb
|
427
428
|
- lib/graphql/execution/interpreter/hash_response.rb
|
@@ -477,6 +478,7 @@ files:
|
|
477
478
|
- lib/graphql/language/parser.rb
|
478
479
|
- lib/graphql/language/parser.y
|
479
480
|
- lib/graphql/language/printer.rb
|
481
|
+
- lib/graphql/language/sanitized_printer.rb
|
480
482
|
- lib/graphql/language/token.rb
|
481
483
|
- lib/graphql/language/visitor.rb
|
482
484
|
- lib/graphql/list_type.rb
|
@@ -682,6 +684,7 @@ files:
|
|
682
684
|
- lib/graphql/subscriptions/subscription_root.rb
|
683
685
|
- lib/graphql/tracing.rb
|
684
686
|
- lib/graphql/tracing/active_support_notifications_tracing.rb
|
687
|
+
- lib/graphql/tracing/appoptics_tracing.rb
|
685
688
|
- lib/graphql/tracing/appsignal_tracing.rb
|
686
689
|
- lib/graphql/tracing/data_dog_tracing.rb
|
687
690
|
- lib/graphql/tracing/new_relic_tracing.rb
|
@@ -743,7 +746,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
743
746
|
- !ruby/object:Gem::Version
|
744
747
|
version: '0'
|
745
748
|
requirements: []
|
746
|
-
rubygems_version: 3.
|
749
|
+
rubygems_version: 3.0.3
|
747
750
|
signing_key:
|
748
751
|
specification_version: 4
|
749
752
|
summary: A GraphQL language and runtime for Ruby
|