graphql 2.0.6 → 2.0.9
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.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/generators/graphql/relay.rb +3 -17
- data/lib/graphql/analysis/ast/field_usage.rb +3 -1
- data/lib/graphql/execution/interpreter/resolve.rb +7 -0
- data/lib/graphql/execution/interpreter/runtime.rb +23 -3
- data/lib/graphql/execution/lazy.rb +0 -1
- data/lib/graphql/language/parser.rb +338 -327
- data/lib/graphql/language/parser.y +12 -8
- data/lib/graphql/language/printer.rb +9 -5
- data/lib/graphql/pagination/connection.rb +31 -4
- data/lib/graphql/pagination/connections.rb +1 -0
- data/lib/graphql/schema/field/connection_extension.rb +4 -0
- data/lib/graphql/schema/field.rb +54 -14
- data/lib/graphql/schema/resolver.rb +21 -0
- data/lib/graphql/schema/warden.rb +6 -2
- data/lib/graphql/schema.rb +24 -8
- data/lib/graphql/subscriptions.rb +15 -3
- data/lib/graphql/tracing/data_dog_tracing.rb +16 -1
- data/lib/graphql/tracing/opentelemetry_tracing.rb +101 -0
- data/lib/graphql/tracing/platform_tracing.rb +8 -4
- data/lib/graphql/tracing.rb +1 -0
- data/lib/graphql/types/iso_8601_date_time.rb +3 -0
- data/lib/graphql/types/relay/base_connection.rb +16 -6
- data/lib/graphql/version.rb +1 -1
- metadata +3 -3
- data/lib/graphql/execution/lazy/resolve.rb +0 -91
@@ -321,13 +321,13 @@ rule
|
|
321
321
|
|
322
322
|
object_type_extension:
|
323
323
|
/* TODO - This first one shouldn't be necessary but parser is getting confused */
|
324
|
-
EXTEND TYPE name implements
|
325
|
-
| EXTEND TYPE name implements_opt directives_list_opt
|
324
|
+
EXTEND TYPE name implements field_definition_list_opt { result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: [], fields: val[4], position_source: val[0]) }
|
325
|
+
| EXTEND TYPE name implements_opt directives_list_opt field_definition_list_opt { result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: val[5], position_source: val[0]) }
|
326
326
|
| EXTEND TYPE name implements_opt directives_list { result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: [], position_source: val[0]) }
|
327
327
|
| EXTEND TYPE name implements { result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: [], fields: [], position_source: val[0]) }
|
328
328
|
|
329
329
|
interface_type_extension:
|
330
|
-
EXTEND INTERFACE name implements_opt directives_list_opt
|
330
|
+
EXTEND INTERFACE name implements_opt directives_list_opt field_definition_list_opt { result = make_node(:InterfaceTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: val[5], position_source: val[0]) }
|
331
331
|
| EXTEND INTERFACE name implements_opt directives_list { result = make_node(:InterfaceTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: [], position_source: val[0]) }
|
332
332
|
| EXTEND INTERFACE name implements { result = make_node(:InterfaceTypeExtension, name: val[2], interfaces: val[3], directives: [], fields: [], position_source: val[0]) }
|
333
333
|
|
@@ -355,8 +355,8 @@ rule
|
|
355
355
|
}
|
356
356
|
|
357
357
|
object_type_definition:
|
358
|
-
description_opt TYPE name implements_opt directives_list_opt
|
359
|
-
result = make_node(:ObjectTypeDefinition, name: val[2], interfaces: val[3], directives: val[4], fields: val[
|
358
|
+
description_opt TYPE name implements_opt directives_list_opt field_definition_list_opt {
|
359
|
+
result = make_node(:ObjectTypeDefinition, name: val[2], interfaces: val[3], directives: val[4], fields: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1])
|
360
360
|
}
|
361
361
|
|
362
362
|
implements_opt:
|
@@ -394,14 +394,18 @@ rule
|
|
394
394
|
result = make_node(:FieldDefinition, name: val[1], arguments: val[2], type: val[4], directives: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1])
|
395
395
|
}
|
396
396
|
|
397
|
-
|
397
|
+
field_definition_list_opt:
|
398
398
|
/* none */ { result = EMPTY_ARRAY }
|
399
|
+
| LCURLY field_definition_list RCURLY { result = val[1] }
|
400
|
+
|
401
|
+
field_definition_list:
|
402
|
+
/* none - this is not actually valid but graphql-ruby used to print this */ { result = EMPTY_ARRAY }
|
399
403
|
| field_definition { result = [val[0]] }
|
400
404
|
| field_definition_list field_definition { val[0] << val[1] }
|
401
405
|
|
402
406
|
interface_type_definition:
|
403
|
-
description_opt INTERFACE name implements_opt directives_list_opt
|
404
|
-
result = make_node(:InterfaceTypeDefinition, name: val[2], interfaces: val[3], directives: val[4], fields: val[
|
407
|
+
description_opt INTERFACE name implements_opt directives_list_opt field_definition_list_opt {
|
408
|
+
result = make_node(:InterfaceTypeDefinition, name: val[2], interfaces: val[3], directives: val[4], fields: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1])
|
405
409
|
}
|
406
410
|
|
407
411
|
union_members:
|
@@ -267,12 +267,16 @@ module GraphQL
|
|
267
267
|
end
|
268
268
|
|
269
269
|
def print_field_definitions(fields)
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
out
|
270
|
+
if fields.empty?
|
271
|
+
""
|
272
|
+
else
|
273
|
+
out = " {\n".dup
|
274
|
+
fields.each.with_index do |field, i|
|
275
|
+
out << print_description(field, indent: ' ', first_in_block: i == 0)
|
276
|
+
out << " #{print_field_definition(field)}\n"
|
277
|
+
end
|
278
|
+
out << "}"
|
274
279
|
end
|
275
|
-
out << "}"
|
276
280
|
end
|
277
281
|
|
278
282
|
def print_directives(directives)
|
@@ -56,8 +56,9 @@ module GraphQL
|
|
56
56
|
# @param last [Integer, nil] Limit parameter from the client, if provided
|
57
57
|
# @param before [String, nil] A cursor for pagination, if the client provided one.
|
58
58
|
# @param arguments [Hash] The arguments to the field that returned the collection wrapped by this connection
|
59
|
-
# @param max_page_size [Integer, nil] A configured value to cap the result size. Applied as `first` if neither first or last are given.
|
60
|
-
|
59
|
+
# @param max_page_size [Integer, nil] A configured value to cap the result size. Applied as `first` if neither first or last are given and no `default_page_size` is set.
|
60
|
+
# @param default_page_size [Integer, nil] A configured value to determine the result size when neither first or last are given.
|
61
|
+
def initialize(items, parent: nil, field: nil, context: nil, first: nil, after: nil, max_page_size: :not_given, default_page_size: :not_given, last: nil, before: nil, edge_class: nil, arguments: nil)
|
61
62
|
@items = items
|
62
63
|
@parent = parent
|
63
64
|
@context = context
|
@@ -76,6 +77,12 @@ module GraphQL
|
|
76
77
|
else
|
77
78
|
max_page_size
|
78
79
|
end
|
80
|
+
@has_default_page_size_override = default_page_size != :not_given
|
81
|
+
@default_page_size = if default_page_size == :not_given
|
82
|
+
nil
|
83
|
+
else
|
84
|
+
default_page_size
|
85
|
+
end
|
79
86
|
end
|
80
87
|
|
81
88
|
def max_page_size=(new_value)
|
@@ -95,16 +102,36 @@ module GraphQL
|
|
95
102
|
@has_max_page_size_override
|
96
103
|
end
|
97
104
|
|
105
|
+
def default_page_size=(new_value)
|
106
|
+
@has_default_page_size_override = true
|
107
|
+
@default_page_size = new_value
|
108
|
+
end
|
109
|
+
|
110
|
+
def default_page_size
|
111
|
+
if @has_default_page_size_override
|
112
|
+
@default_page_size
|
113
|
+
else
|
114
|
+
context.schema.default_page_size
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def has_default_page_size_override?
|
119
|
+
@has_default_page_size_override
|
120
|
+
end
|
121
|
+
|
98
122
|
attr_writer :first
|
99
123
|
# @return [Integer, nil]
|
100
124
|
# A clamped `first` value.
|
101
125
|
# (The underlying instance variable doesn't have limits on it.)
|
102
|
-
# If neither `first` nor `last` is given, but `
|
126
|
+
# If neither `first` nor `last` is given, but `default_page_size` is
|
127
|
+
# present, default_page_size is used for first. If `default_page_size`
|
128
|
+
# is greater than `max_page_size``, it'll be clamped down to
|
129
|
+
# `max_page_size`. If `default_page_size` is nil, use `max_page_size`.
|
103
130
|
def first
|
104
131
|
@first ||= begin
|
105
132
|
capped = limit_pagination_argument(@first_value, max_page_size)
|
106
133
|
if capped.nil? && last.nil?
|
107
|
-
capped = max_page_size
|
134
|
+
capped = limit_pagination_argument(default_page_size, max_page_size) || max_page_size
|
108
135
|
end
|
109
136
|
capped
|
110
137
|
end
|
@@ -70,6 +70,7 @@ module GraphQL
|
|
70
70
|
parent: parent,
|
71
71
|
field: field,
|
72
72
|
max_page_size: field.has_max_page_size? ? field.max_page_size : context.schema.default_max_page_size,
|
73
|
+
default_page_size: field.has_default_page_size? ? field.default_page_size : context.schema.default_page_size,
|
73
74
|
first: arguments[:first],
|
74
75
|
after: arguments[:after],
|
75
76
|
last: arguments[:last],
|
@@ -47,6 +47,9 @@ module GraphQL
|
|
47
47
|
if field.has_max_page_size? && !value.has_max_page_size_override?
|
48
48
|
value.max_page_size = field.max_page_size
|
49
49
|
end
|
50
|
+
if field.has_default_page_size? && !value.has_default_page_size_override?
|
51
|
+
value.default_page_size = field.default_page_size
|
52
|
+
end
|
50
53
|
if context.schema.new_connections? && (custom_t = context.schema.connections.edge_class_for_field(@field))
|
51
54
|
value.edge_class = custom_t
|
52
55
|
end
|
@@ -64,6 +67,7 @@ module GraphQL
|
|
64
67
|
original_arguments,
|
65
68
|
field: field,
|
66
69
|
max_page_size: field.max_page_size,
|
70
|
+
default_page_size: field.default_page_size,
|
67
71
|
parent: object,
|
68
72
|
context: context,
|
69
73
|
)
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -200,6 +200,7 @@ module GraphQL
|
|
200
200
|
# @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
|
201
201
|
# @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
|
202
202
|
# @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
|
203
|
+
# @param default_page_size [Integer, nil] For connections, the default number of items to return from this field, or `nil` to return unlimited results.
|
203
204
|
# @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
|
204
205
|
# @param resolver_class [Class] (Private) A {Schema::Resolver} which this field was derived from. Use `resolver:` to create a field with a resolver.
|
205
206
|
# @param arguments [{String=>GraphQL::Schema::Argument, Hash}] Arguments for this field (may be added in the block, also)
|
@@ -214,7 +215,8 @@ module GraphQL
|
|
214
215
|
# @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
|
215
216
|
# @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
|
216
217
|
# @param validates [Array<Hash>] Configurations for validating this field
|
217
|
-
|
218
|
+
# @fallback_value [Object] A fallback value if the method is not defined
|
219
|
+
def initialize(type: nil, name: nil, owner: nil, null: nil, description: :not_given, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: :not_given, default_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, 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, broadcastable: nil, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: :not_given, &definition_block)
|
218
220
|
if name.nil?
|
219
221
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
220
222
|
end
|
@@ -246,9 +248,12 @@ module GraphQL
|
|
246
248
|
end
|
247
249
|
end
|
248
250
|
|
249
|
-
method_name = method || name_s
|
251
|
+
method_name = method || hash_key || name_s
|
250
252
|
@dig_keys = dig
|
251
|
-
|
253
|
+
if hash_key
|
254
|
+
@hash_key = hash_key
|
255
|
+
@hash_key_str = hash_key.to_s
|
256
|
+
end
|
252
257
|
|
253
258
|
@method_str = -method_name.to_s
|
254
259
|
@method_sym = method_name.to_sym
|
@@ -265,6 +270,8 @@ module GraphQL
|
|
265
270
|
@connection = connection
|
266
271
|
@has_max_page_size = max_page_size != :not_given
|
267
272
|
@max_page_size = max_page_size == :not_given ? nil : max_page_size
|
273
|
+
@has_default_page_size = default_page_size != :not_given
|
274
|
+
@default_page_size = default_page_size == :not_given ? nil : default_page_size
|
268
275
|
@introspection = introspection
|
269
276
|
@extras = extras
|
270
277
|
if !broadcastable.nil?
|
@@ -277,6 +284,7 @@ module GraphQL
|
|
277
284
|
@relay_nodes_field = relay_nodes_field
|
278
285
|
@ast_node = ast_node
|
279
286
|
@method_conflict_warning = method_conflict_warning
|
287
|
+
@fallback_value = fallback_value
|
280
288
|
|
281
289
|
arguments.each do |name, arg|
|
282
290
|
case arg
|
@@ -459,11 +467,11 @@ module GraphQL
|
|
459
467
|
end
|
460
468
|
|
461
469
|
if max_possible_page_size.nil?
|
462
|
-
max_possible_page_size = max_page_size || query.schema.default_max_page_size
|
470
|
+
max_possible_page_size = default_page_size || query.schema.default_page_size || max_page_size || query.schema.default_max_page_size
|
463
471
|
end
|
464
472
|
|
465
473
|
if max_possible_page_size.nil?
|
466
|
-
raise GraphQL::Error, "Can't calculate complexity for #{path}, no `first:`, `last:`, `max_page_size` or `default_max_page_size`"
|
474
|
+
raise GraphQL::Error, "Can't calculate complexity for #{path}, no `first:`, `last:`, `default_page_size`, `max_page_size` or `default_max_page_size`"
|
467
475
|
else
|
468
476
|
metadata_complexity = 0
|
469
477
|
lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner)
|
@@ -491,7 +499,13 @@ module GraphQL
|
|
491
499
|
case defined_complexity
|
492
500
|
when Proc
|
493
501
|
arguments = query.arguments_for(nodes.first, self)
|
494
|
-
|
502
|
+
if arguments.is_a?(GraphQL::ExecutionError)
|
503
|
+
return child_complexity
|
504
|
+
elsif arguments.respond_to?(:keyword_arguments)
|
505
|
+
arguments = arguments.keyword_arguments
|
506
|
+
end
|
507
|
+
|
508
|
+
defined_complexity.call(query.context, arguments, child_complexity)
|
495
509
|
when Numeric
|
496
510
|
defined_complexity + child_complexity
|
497
511
|
else
|
@@ -534,6 +548,16 @@ module GraphQL
|
|
534
548
|
@max_page_size || (@resolver_class && @resolver_class.max_page_size)
|
535
549
|
end
|
536
550
|
|
551
|
+
# @return [Boolean] True if this field's {#default_page_size} should override the schema default.
|
552
|
+
def has_default_page_size?
|
553
|
+
@has_default_page_size || (@resolver_class && @resolver_class.has_default_page_size?)
|
554
|
+
end
|
555
|
+
|
556
|
+
# @return [Integer, nil] Applied to connections if {#has_default_page_size?}
|
557
|
+
def default_page_size
|
558
|
+
@default_page_size || (@resolver_class && @resolver_class.default_page_size)
|
559
|
+
end
|
560
|
+
|
537
561
|
class MissingReturnTypeError < GraphQL::Error; end
|
538
562
|
attr_writer :type
|
539
563
|
|
@@ -639,10 +663,8 @@ module GraphQL
|
|
639
663
|
|
640
664
|
inner_object = obj.object
|
641
665
|
|
642
|
-
if @hash_key
|
643
|
-
inner_object[@hash_key]
|
644
|
-
elsif @dig_keys
|
645
|
-
inner_object.dig(*@dig_keys)
|
666
|
+
if defined?(@hash_key)
|
667
|
+
inner_object[@hash_key] || inner_object[@hash_key_str] || (@fallback_value != :not_given ? @fallback_value : nil)
|
646
668
|
elsif obj.respond_to?(resolver_method)
|
647
669
|
method_to_call = resolver_method
|
648
670
|
method_receiver = obj
|
@@ -653,10 +675,26 @@ module GraphQL
|
|
653
675
|
obj.public_send(resolver_method)
|
654
676
|
end
|
655
677
|
elsif inner_object.is_a?(Hash)
|
656
|
-
if
|
678
|
+
if @dig_keys
|
679
|
+
inner_object.dig(*@dig_keys)
|
680
|
+
elsif defined?(@hash_key)
|
681
|
+
if inner_object.key?(@hash_key)
|
682
|
+
inner_object[@hash_key]
|
683
|
+
elsif inner_object.key?(@hash_key_str)
|
684
|
+
inner_object[@hash_key_str]
|
685
|
+
elsif @fallback_value != :not_given
|
686
|
+
@fallback_value
|
687
|
+
else
|
688
|
+
nil
|
689
|
+
end
|
690
|
+
elsif inner_object.key?(@method_sym)
|
657
691
|
inner_object[@method_sym]
|
658
|
-
|
692
|
+
elsif inner_object.key?(@method_str)
|
659
693
|
inner_object[@method_str]
|
694
|
+
elsif @fallback_value != :not_given
|
695
|
+
@fallback_value
|
696
|
+
else
|
697
|
+
nil
|
660
698
|
end
|
661
699
|
elsif inner_object.respond_to?(@method_sym)
|
662
700
|
method_to_call = @method_sym
|
@@ -666,6 +704,8 @@ module GraphQL
|
|
666
704
|
else
|
667
705
|
inner_object.public_send(@method_sym)
|
668
706
|
end
|
707
|
+
elsif @fallback_value != :not_given
|
708
|
+
@fallback_value
|
669
709
|
else
|
670
710
|
raise <<-ERR
|
671
711
|
Failed to implement #{@owner.graphql_name}.#{@name}, tried:
|
@@ -674,7 +714,7 @@ module GraphQL
|
|
674
714
|
- `#{inner_object.class}##{@method_sym}`, which did not exist
|
675
715
|
- Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{inner_object}`, but it wasn't a Hash
|
676
716
|
|
677
|
-
To implement this field, define one of the methods above (and check for typos)
|
717
|
+
To implement this field, define one of the methods above (and check for typos), or supply a `fallback_value`.
|
678
718
|
ERR
|
679
719
|
end
|
680
720
|
end
|
@@ -746,7 +786,7 @@ module GraphQL
|
|
746
786
|
|
747
787
|
if unsatisfied_ruby_kwargs.any? || unsatisfied_method_params.any?
|
748
788
|
raise FieldImplementationFailed.new, <<-ERR
|
749
|
-
Failed to call
|
789
|
+
Failed to call `#{method_name.inspect}` on #{receiver.inspect} because the Ruby method params were incompatible with the GraphQL arguments:
|
750
790
|
|
751
791
|
#{ unsatisfied_ruby_kwargs
|
752
792
|
.map { |key, value| "- `#{key}: #{value}` was given by GraphQL but not defined in the Ruby method. Add `#{key}:` to the method parameters." }
|
@@ -328,6 +328,27 @@ module GraphQL
|
|
328
328
|
(!!defined?(@max_page_size)) || (superclass.respond_to?(:has_max_page_size?) && superclass.has_max_page_size?)
|
329
329
|
end
|
330
330
|
|
331
|
+
# Get or set the `default_page_size:` which will be configured for fields using this resolver
|
332
|
+
# (`nil` means "unlimited default page size".)
|
333
|
+
# @param default_page_size [Integer, nil] Set a new value
|
334
|
+
# @return [Integer, nil] The `default_page_size` assigned to fields that use this resolver
|
335
|
+
def default_page_size(new_default_page_size = :not_given)
|
336
|
+
if new_default_page_size != :not_given
|
337
|
+
@default_page_size = new_default_page_size
|
338
|
+
elsif defined?(@default_page_size)
|
339
|
+
@default_page_size
|
340
|
+
elsif superclass.respond_to?(:default_page_size)
|
341
|
+
superclass.default_page_size
|
342
|
+
else
|
343
|
+
nil
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
# @return [Boolean] `true` if this resolver or a superclass has an assigned `default_page_size`
|
348
|
+
def has_default_page_size?
|
349
|
+
(!!defined?(@default_page_size)) || (superclass.respond_to?(:has_default_page_size?) && superclass.has_default_page_size?)
|
350
|
+
end
|
351
|
+
|
331
352
|
# A non-normalized type configuration, without `null` applied
|
332
353
|
def type_expr
|
333
354
|
@type_expr || (superclass.respond_to?(:type_expr) ? superclass.type_expr : nil)
|
@@ -55,7 +55,9 @@ module GraphQL
|
|
55
55
|
if visible_item.nil?
|
56
56
|
visible_item = item
|
57
57
|
else
|
58
|
-
raise
|
58
|
+
raise DuplicateNamesError.new(
|
59
|
+
duplicated_name: item.path, duplicated_definition_1: visible_item.inspect, duplicated_definition_2: item.inspect
|
60
|
+
)
|
59
61
|
end
|
60
62
|
end
|
61
63
|
end
|
@@ -362,7 +364,9 @@ module GraphQL
|
|
362
364
|
if @reachable_type_set.add?(type)
|
363
365
|
type_by_name = rt_hash[type.graphql_name] ||= type
|
364
366
|
if type_by_name != type
|
365
|
-
raise DuplicateNamesError
|
367
|
+
raise DuplicateNamesError.new(
|
368
|
+
duplicated_name: type.graphql_name, duplicated_definition_1: type.inspect, duplicated_definition_2: type_by_name.inspect
|
369
|
+
)
|
366
370
|
end
|
367
371
|
if type.kind.input_object?
|
368
372
|
# recurse into visible arguments
|
data/lib/graphql/schema.rb
CHANGED
@@ -73,14 +73,16 @@ module GraphQL
|
|
73
73
|
extend GraphQL::Schema::Member::HasAstNode
|
74
74
|
extend GraphQL::Schema::FindInheritedValue
|
75
75
|
|
76
|
-
class
|
77
|
-
|
78
|
-
|
76
|
+
class DuplicateNamesError < GraphQL::Error
|
77
|
+
attr_reader :duplicated_name
|
78
|
+
def initialize(duplicated_name:, duplicated_definition_1:, duplicated_definition_2:)
|
79
|
+
@duplicated_name = duplicated_name
|
80
|
+
super(
|
81
|
+
"Found two visible definitions for `#{duplicated_name}`: #{duplicated_definition_1}, #{duplicated_definition_2}"
|
82
|
+
)
|
79
83
|
end
|
80
84
|
end
|
81
85
|
|
82
|
-
class DuplicateNamesError < GraphQL::Error; end
|
83
|
-
|
84
86
|
class UnresolvedLateBoundTypeError < GraphQL::Error
|
85
87
|
attr_reader :type
|
86
88
|
def initialize(type:)
|
@@ -225,7 +227,9 @@ module GraphQL
|
|
225
227
|
if visible_t.nil?
|
226
228
|
visible_t = t
|
227
229
|
else
|
228
|
-
raise DuplicateNamesError
|
230
|
+
raise DuplicateNamesError.new(
|
231
|
+
duplicated_name: k, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
|
232
|
+
)
|
229
233
|
end
|
230
234
|
end
|
231
235
|
end
|
@@ -252,7 +256,9 @@ module GraphQL
|
|
252
256
|
if visible_t.nil?
|
253
257
|
visible_t = t
|
254
258
|
else
|
255
|
-
raise DuplicateNamesError
|
259
|
+
raise DuplicateNamesError.new(
|
260
|
+
duplicated_name: type_name, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
|
261
|
+
)
|
256
262
|
end
|
257
263
|
end
|
258
264
|
end
|
@@ -500,6 +506,14 @@ module GraphQL
|
|
500
506
|
end
|
501
507
|
end
|
502
508
|
|
509
|
+
def default_page_size(new_default_page_size = nil)
|
510
|
+
if new_default_page_size
|
511
|
+
@default_page_size = new_default_page_size
|
512
|
+
else
|
513
|
+
@default_page_size || find_inherited_value(:default_page_size)
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
503
517
|
def query_execution_strategy(new_query_execution_strategy = nil)
|
504
518
|
if new_query_execution_strategy
|
505
519
|
@query_execution_strategy = new_query_execution_strategy
|
@@ -977,7 +991,9 @@ module GraphQL
|
|
977
991
|
if !defined?(@subscription_extension_added) && subscription && self.subscriptions
|
978
992
|
@subscription_extension_added = true
|
979
993
|
subscription.all_field_definitions.each do |field|
|
980
|
-
field.
|
994
|
+
if !field.extensions.any? { |ext| ext.is_a?(Subscriptions::DefaultSubscriptionResolveExtension) }
|
995
|
+
field.extension(Subscriptions::DefaultSubscriptionResolveExtension)
|
996
|
+
end
|
981
997
|
end
|
982
998
|
end
|
983
999
|
end
|
@@ -39,12 +39,14 @@ module GraphQL
|
|
39
39
|
end
|
40
40
|
|
41
41
|
# @param schema [Class] the GraphQL schema this manager belongs to
|
42
|
-
|
42
|
+
# @param validate_update [Boolean] If false, then validation is skipped when executing updates
|
43
|
+
def initialize(schema:, validate_update: true, broadcast: false, default_broadcastable: false, **rest)
|
43
44
|
if broadcast
|
44
45
|
schema.query_analyzer(Subscriptions::BroadcastAnalyzer)
|
45
46
|
end
|
46
47
|
@default_broadcastable = default_broadcastable
|
47
48
|
@schema = schema
|
49
|
+
@validate_update = validate_update
|
48
50
|
end
|
49
51
|
|
50
52
|
# @return [Boolean] Used when fields don't have `broadcastable:` explicitly set
|
@@ -117,14 +119,16 @@ module GraphQL
|
|
117
119
|
variables = query_data.fetch(:variables)
|
118
120
|
context = query_data.fetch(:context)
|
119
121
|
operation_name = query_data.fetch(:operation_name)
|
120
|
-
|
122
|
+
execute_options = {
|
121
123
|
query: query_string,
|
122
124
|
context: context,
|
123
125
|
subscription_topic: event.topic,
|
124
126
|
operation_name: operation_name,
|
125
127
|
variables: variables,
|
126
128
|
root_value: object,
|
127
|
-
|
129
|
+
}
|
130
|
+
execute_options[:validate] = validate_update?(**execute_options)
|
131
|
+
result = @schema.execute(**execute_options)
|
128
132
|
subscriptions_context = result.context.namespace(:subscriptions)
|
129
133
|
if subscriptions_context[:no_update]
|
130
134
|
result = nil
|
@@ -142,6 +146,14 @@ module GraphQL
|
|
142
146
|
result
|
143
147
|
end
|
144
148
|
|
149
|
+
# Define this method to customize whether to validate
|
150
|
+
# this subscription when executing an update.
|
151
|
+
#
|
152
|
+
# @return [Boolean] defaults to `true`, or false if `validate: false` is provided.
|
153
|
+
def validate_update?(query:, context:, root_value:, subscription_topic:, operation_name:, variables:)
|
154
|
+
@validate_update
|
155
|
+
end
|
156
|
+
|
145
157
|
# Run the update query for this subscription and deliver it
|
146
158
|
# @see {#execute_update}
|
147
159
|
# @see {#deliver}
|
@@ -17,15 +17,21 @@ module GraphQL
|
|
17
17
|
def platform_trace(platform_key, key, data)
|
18
18
|
tracer.trace(platform_key, service: service_name) do |span|
|
19
19
|
span.span_type = 'custom'
|
20
|
+
if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
|
21
|
+
span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
|
22
|
+
span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, key)
|
23
|
+
end
|
20
24
|
|
21
25
|
if key == 'execute_multiplex'
|
22
26
|
operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ')
|
23
|
-
|
27
|
+
|
28
|
+
resource = if operations.empty?
|
24
29
|
first_query = data[:multiplex].queries.first
|
25
30
|
fallback_transaction_name(first_query && first_query.context)
|
26
31
|
else
|
27
32
|
operations
|
28
33
|
end
|
34
|
+
span.resource = resource if resource
|
29
35
|
|
30
36
|
# For top span of query, set the analytics sample rate tag, if available.
|
31
37
|
if analytics_enabled?
|
@@ -39,6 +45,8 @@ module GraphQL
|
|
39
45
|
span.set_tag(:query_string, data[:query].query_string)
|
40
46
|
end
|
41
47
|
|
48
|
+
prepare_span(key, data, span)
|
49
|
+
|
42
50
|
yield
|
43
51
|
end
|
44
52
|
end
|
@@ -47,6 +55,13 @@ module GraphQL
|
|
47
55
|
options.fetch(:service, 'ruby-graphql')
|
48
56
|
end
|
49
57
|
|
58
|
+
# Implement this method in a subclass to apply custom tags to datadog spans
|
59
|
+
# @param key [String] The event being traced
|
60
|
+
# @param data [Hash] The runtime data for this event (@see GraphQL::Tracing for keys for each event)
|
61
|
+
# @param span [Datadog::Tracing::SpanOperation] The datadog span for this event
|
62
|
+
def prepare_span(key, data, span)
|
63
|
+
end
|
64
|
+
|
50
65
|
def tracer
|
51
66
|
default_tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer
|
52
67
|
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Tracing
|
5
|
+
class OpenTelemetryTracing < PlatformTracing
|
6
|
+
self.platform_keys = {
|
7
|
+
'lex' => 'graphql.lex',
|
8
|
+
'parse' => 'graphql.parse',
|
9
|
+
'validate' => 'graphql.validate',
|
10
|
+
'analyze_query' => 'graphql.analyze_query',
|
11
|
+
'analyze_multiplex' => 'graphql.analyze_multiplex',
|
12
|
+
'execute_query' => 'graphql.execute_query',
|
13
|
+
'execute_query_lazy' => 'graphql.execute_query_lazy',
|
14
|
+
'execute_multiplex' => 'graphql.execute_multiplex'
|
15
|
+
}
|
16
|
+
|
17
|
+
def platform_trace(platform_key, key, data)
|
18
|
+
return yield if platform_key.nil?
|
19
|
+
|
20
|
+
tracer.in_span(platform_key, attributes: attributes_for(key, data)) do |span, _context|
|
21
|
+
yield.tap do |response|
|
22
|
+
errors = response[:errors]&.compact&.map { |e| e.to_h }&.to_json if key == 'validate'
|
23
|
+
unless errors.nil?
|
24
|
+
span.add_event(
|
25
|
+
'graphql.validation.error',
|
26
|
+
attributes: {
|
27
|
+
'message' => errors
|
28
|
+
}
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def platform_field_key(type, field)
|
36
|
+
"#{type.graphql_name}.#{field.graphql_name}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def platform_authorized_key(type)
|
40
|
+
"#{type.graphql_name}.authorized"
|
41
|
+
end
|
42
|
+
|
43
|
+
def platform_resolve_type_key(type)
|
44
|
+
"#{type.graphql_name}.resolve_type"
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def tracer
|
50
|
+
OpenTelemetry::Instrumentation::GraphQL::Instrumentation.instance.tracer
|
51
|
+
end
|
52
|
+
|
53
|
+
def config
|
54
|
+
OpenTelemetry::Instrumentation::GraphQL::Instrumentation.instance.config
|
55
|
+
end
|
56
|
+
|
57
|
+
def platform_key_enabled?(ctx, key)
|
58
|
+
return false unless config[key]
|
59
|
+
|
60
|
+
ns = ctx.namespace(:opentelemetry)
|
61
|
+
return true if ns.empty? # restores original behavior so that keys are returned if tracing is not set in context.
|
62
|
+
return false unless ns.key?(key) && ns[key]
|
63
|
+
|
64
|
+
return true
|
65
|
+
end
|
66
|
+
|
67
|
+
def attributes_for(key, data)
|
68
|
+
attributes = {}
|
69
|
+
case key
|
70
|
+
when 'execute_query'
|
71
|
+
attributes['selected_operation_name'] = data[:query].selected_operation_name if data[:query].selected_operation_name
|
72
|
+
attributes['selected_operation_type'] = data[:query].selected_operation.operation_type
|
73
|
+
attributes['query_string'] = data[:query].query_string
|
74
|
+
end
|
75
|
+
attributes
|
76
|
+
end
|
77
|
+
|
78
|
+
def cached_platform_key(ctx, key, trace_phase)
|
79
|
+
cache = ctx.namespace(self.class)[:platform_key_cache] ||= {}
|
80
|
+
|
81
|
+
cache.fetch(key) do
|
82
|
+
cache[key] = if trace_phase == :field
|
83
|
+
return unless platform_key_enabled?(ctx, :enable_platform_field)
|
84
|
+
|
85
|
+
yield
|
86
|
+
elsif trace_phase == :authorized
|
87
|
+
return unless platform_key_enabled?(ctx, :enable_platform_authorized)
|
88
|
+
|
89
|
+
yield
|
90
|
+
elsif trace_phase == :resolve_type
|
91
|
+
return unless platform_key_enabled?(ctx, :enable_platform_resolve_type)
|
92
|
+
|
93
|
+
yield
|
94
|
+
else
|
95
|
+
raise "Unknown trace phase"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|