graphql 1.10.3 → 1.10.4
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/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
|