graphql 1.13.0 → 1.13.24

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.
Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +3 -8
  3. data/lib/generators/graphql/enum_generator.rb +4 -10
  4. data/lib/generators/graphql/field_extractor.rb +31 -0
  5. data/lib/generators/graphql/input_generator.rb +50 -0
  6. data/lib/generators/graphql/install/mutation_root_generator.rb +34 -0
  7. data/lib/generators/graphql/install_generator.rb +10 -3
  8. data/lib/generators/graphql/interface_generator.rb +7 -7
  9. data/lib/generators/graphql/mutation_create_generator.rb +22 -0
  10. data/lib/generators/graphql/mutation_delete_generator.rb +22 -0
  11. data/lib/generators/graphql/mutation_generator.rb +5 -30
  12. data/lib/generators/graphql/mutation_update_generator.rb +22 -0
  13. data/lib/generators/graphql/object_generator.rb +8 -37
  14. data/lib/generators/graphql/orm_mutations_base.rb +40 -0
  15. data/lib/generators/graphql/scalar_generator.rb +4 -2
  16. data/lib/generators/graphql/templates/enum.erb +5 -1
  17. data/lib/generators/graphql/templates/input.erb +9 -0
  18. data/lib/generators/graphql/templates/interface.erb +4 -2
  19. data/lib/generators/graphql/templates/mutation.erb +1 -1
  20. data/lib/generators/graphql/templates/mutation_create.erb +20 -0
  21. data/lib/generators/graphql/templates/mutation_delete.erb +20 -0
  22. data/lib/generators/graphql/templates/mutation_update.erb +21 -0
  23. data/lib/generators/graphql/templates/object.erb +4 -2
  24. data/lib/generators/graphql/templates/scalar.erb +3 -1
  25. data/lib/generators/graphql/templates/union.erb +4 -2
  26. data/lib/generators/graphql/type_generator.rb +46 -9
  27. data/lib/generators/graphql/union_generator.rb +5 -5
  28. data/lib/graphql/analysis/ast/field_usage.rb +6 -2
  29. data/lib/graphql/analysis/ast/visitor.rb +2 -1
  30. data/lib/graphql/argument.rb +1 -1
  31. data/lib/graphql/base_type.rb +5 -3
  32. data/lib/graphql/boolean_type.rb +1 -1
  33. data/lib/graphql/dataloader/source.rb +2 -2
  34. data/lib/graphql/date_encoding_error.rb +16 -0
  35. data/lib/graphql/define/instance_definable.rb +15 -0
  36. data/lib/graphql/directive/deprecated_directive.rb +1 -1
  37. data/lib/graphql/directive/include_directive.rb +1 -1
  38. data/lib/graphql/directive/skip_directive.rb +1 -1
  39. data/lib/graphql/directive.rb +1 -1
  40. data/lib/graphql/enum_type.rb +2 -2
  41. data/lib/graphql/execution/interpreter/arguments_cache.rb +4 -2
  42. data/lib/graphql/execution/interpreter/runtime.rb +48 -28
  43. data/lib/graphql/execution/multiplex.rb +3 -0
  44. data/lib/graphql/field.rb +1 -1
  45. data/lib/graphql/float_type.rb +1 -1
  46. data/lib/graphql/id_type.rb +1 -1
  47. data/lib/graphql/input_object_type.rb +1 -1
  48. data/lib/graphql/int_type.rb +1 -1
  49. data/lib/graphql/interface_type.rb +1 -1
  50. data/lib/graphql/introspection/directive_location_enum.rb +2 -2
  51. data/lib/graphql/introspection/directive_type.rb +4 -2
  52. data/lib/graphql/introspection/field_type.rb +1 -1
  53. data/lib/graphql/introspection/schema_type.rb +7 -2
  54. data/lib/graphql/introspection/type_type.rb +14 -8
  55. data/lib/graphql/introspection.rb +4 -1
  56. data/lib/graphql/language/block_string.rb +2 -2
  57. data/lib/graphql/language/document_from_schema_definition.rb +8 -3
  58. data/lib/graphql/language/lexer.rb +50 -25
  59. data/lib/graphql/language/lexer.rl +2 -0
  60. data/lib/graphql/language/nodes.rb +15 -3
  61. data/lib/graphql/language/parser.rb +829 -816
  62. data/lib/graphql/language/parser.y +8 -2
  63. data/lib/graphql/language/printer.rb +4 -0
  64. data/lib/graphql/object_type.rb +2 -2
  65. data/lib/graphql/pagination/active_record_relation_connection.rb +43 -6
  66. data/lib/graphql/pagination/relation_connection.rb +59 -29
  67. data/lib/graphql/query/context.rb +10 -0
  68. data/lib/graphql/query/input_validation_result.rb +9 -0
  69. data/lib/graphql/query/validation_pipeline.rb +2 -3
  70. data/lib/graphql/query/variable_validation_error.rb +2 -2
  71. data/lib/graphql/query/variables.rb +30 -3
  72. data/lib/graphql/query.rb +0 -1
  73. data/lib/graphql/relay/connection_type.rb +15 -2
  74. data/lib/graphql/relay/global_id_resolve.rb +1 -2
  75. data/lib/graphql/relay/mutation.rb +1 -1
  76. data/lib/graphql/relay/page_info.rb +1 -1
  77. data/lib/graphql/relay/range_add.rb +4 -0
  78. data/lib/graphql/rubocop/graphql/default_required_true.rb +4 -4
  79. data/lib/graphql/scalar_type.rb +1 -1
  80. data/lib/graphql/schema/argument.rb +29 -16
  81. data/lib/graphql/schema/build_from_definition.rb +9 -7
  82. data/lib/graphql/schema/directive.rb +25 -2
  83. data/lib/graphql/schema/enum.rb +4 -3
  84. data/lib/graphql/schema/enum_value.rb +3 -1
  85. data/lib/graphql/schema/field.rb +196 -92
  86. data/lib/graphql/schema/field_extension.rb +89 -2
  87. data/lib/graphql/schema/input_object.rb +27 -9
  88. data/lib/graphql/schema/interface.rb +8 -2
  89. data/lib/graphql/schema/introspection_system.rb +1 -1
  90. data/lib/graphql/schema/list.rb +21 -4
  91. data/lib/graphql/schema/loader.rb +3 -0
  92. data/lib/graphql/schema/member/accepts_definition.rb +7 -2
  93. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  94. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  95. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  96. data/lib/graphql/schema/member/has_fields.rb +1 -1
  97. data/lib/graphql/schema/member/has_interfaces.rb +11 -1
  98. data/lib/graphql/schema/member/validates_input.rb +2 -2
  99. data/lib/graphql/schema/non_null.rb +9 -3
  100. data/lib/graphql/schema/object.rb +3 -1
  101. data/lib/graphql/schema/relay_classic_mutation.rb +8 -0
  102. data/lib/graphql/schema/resolver.rb +19 -13
  103. data/lib/graphql/schema/scalar.rb +15 -1
  104. data/lib/graphql/schema/traversal.rb +1 -1
  105. data/lib/graphql/schema/union.rb +2 -0
  106. data/lib/graphql/schema/validator/required_validator.rb +29 -15
  107. data/lib/graphql/schema/validator.rb +4 -7
  108. data/lib/graphql/schema/warden.rb +11 -2
  109. data/lib/graphql/schema.rb +34 -10
  110. data/lib/graphql/static_validation/all_rules.rb +1 -0
  111. data/lib/graphql/static_validation/base_visitor.rb +1 -1
  112. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  113. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +1 -1
  114. data/lib/graphql/static_validation/rules/fields_will_merge.rb +14 -7
  115. data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
  116. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
  117. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
  118. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  119. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +6 -0
  120. data/lib/graphql/static_validation/validation_context.rb +4 -0
  121. data/lib/graphql/string_type.rb +1 -1
  122. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -0
  123. data/lib/graphql/subscriptions/serialize.rb +22 -2
  124. data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
  125. data/lib/graphql/tracing/data_dog_tracing.rb +24 -15
  126. data/lib/graphql/tracing/notifications_tracing.rb +59 -0
  127. data/lib/graphql/tracing/platform_tracing.rb +20 -10
  128. data/lib/graphql/types/iso_8601_date.rb +13 -5
  129. data/lib/graphql/types/iso_8601_date_time.rb +8 -1
  130. data/lib/graphql/types/relay/connection_behaviors.rb +28 -10
  131. data/lib/graphql/types/relay/default_relay.rb +5 -1
  132. data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
  133. data/lib/graphql/types/relay/node_field.rb +2 -3
  134. data/lib/graphql/types/relay/nodes_field.rb +19 -3
  135. data/lib/graphql/types/string.rb +1 -1
  136. data/lib/graphql/union_type.rb +1 -1
  137. data/lib/graphql/version.rb +1 -1
  138. data/lib/graphql.rb +14 -1
  139. metadata +56 -30
  140. /data/lib/generators/graphql/{templates → install/templates}/base_mutation.erb +0 -0
  141. /data/lib/generators/graphql/{templates → install/templates}/mutation_type.erb +0 -0
@@ -14,7 +14,7 @@ module GraphQL
14
14
  # argument :ingredient_id, ID, required: true
15
15
  # argument :cups, Integer, required: false
16
16
  # argument :tablespoons, Integer, required: false
17
- # argument :teaspoons, Integer, required: true
17
+ # argument :teaspoons, Integer, required: false
18
18
  # validates required: { one_of: [:cups, :tablespoons, :teaspoons] }
19
19
  # end
20
20
  #
@@ -28,11 +28,23 @@ module GraphQL
28
28
  # validates required: { one_of: [:node_id, [:object_type, :object_id]] }
29
29
  # end
30
30
  #
31
+ # @example require _some_ value for an argument, even if it's null
32
+ # field :update_settings, AccountSettings do
33
+ # # `required: :nullable` means this argument must be given, but may be `null`
34
+ # argument :age, Integer, required: :nullable
35
+ # end
36
+ #
31
37
  class RequiredValidator < Validator
32
38
  # @param one_of [Symbol, Array<Symbol>] An argument, or a list of arguments, that represents a valid set of inputs for this field
33
39
  # @param message [String]
34
- def initialize(one_of:, message: "%{validated} has the wrong arguments", **default_options)
35
- @one_of = one_of
40
+ def initialize(one_of: nil, argument: nil, message: "%{validated} has the wrong arguments", **default_options)
41
+ @one_of = if one_of
42
+ one_of
43
+ elsif argument
44
+ [argument]
45
+ else
46
+ raise ArgumentError, "`one_of:` or `argument:` must be given in `validates required: {...}`"
47
+ end
36
48
  @message = message
37
49
  super(**default_options)
38
50
  end
@@ -40,19 +52,21 @@ module GraphQL
40
52
  def validate(_object, _context, value)
41
53
  matched_conditions = 0
42
54
 
43
- @one_of.each do |one_of_condition|
44
- case one_of_condition
45
- when Symbol
46
- if value.key?(one_of_condition)
47
- matched_conditions += 1
48
- end
49
- when Array
50
- if one_of_condition.all? { |k| value.key?(k) }
51
- matched_conditions += 1
52
- break
55
+ if !value.nil?
56
+ @one_of.each do |one_of_condition|
57
+ case one_of_condition
58
+ when Symbol
59
+ if value.key?(one_of_condition)
60
+ matched_conditions += 1
61
+ end
62
+ when Array
63
+ if one_of_condition.all? { |k| value.key?(k) }
64
+ matched_conditions += 1
65
+ break
66
+ end
67
+ else
68
+ raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}"
53
69
  end
54
- else
55
- raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}"
56
70
  end
57
71
  end
58
72
 
@@ -48,16 +48,13 @@ module GraphQL
48
48
  EMPTY_ARRAY
49
49
  else
50
50
  validates_hash = validates_hash.dup
51
- allow_null = validates_hash.delete(:allow_null)
52
- allow_blank = validates_hash.delete(:allow_blank)
53
51
 
54
- # This could be {...}.compact on Ruby 2.4+
55
52
  default_options = {}
56
- if !allow_null.nil?
57
- default_options[:allow_null] = allow_null
53
+ if validates_hash[:allow_null]
54
+ default_options[:allow_null] = validates_hash.delete(:allow_null)
58
55
  end
59
- if !allow_blank.nil?
60
- default_options[:allow_blank] = allow_blank
56
+ if validates_hash[:allow_blank]
57
+ default_options[:allow_blank] = validates_hash.delete(:allow_blank)
61
58
  end
62
59
 
63
60
  # allow_nil or allow_blank are the _only_ validations:
@@ -78,6 +78,8 @@ module GraphQL
78
78
  def visible_type?(type, ctx); type.visible?(ctx); end
79
79
  def visible_enum_value?(ev, ctx); ev.visible?(ctx); end
80
80
  def visible_type_membership?(tm, ctx); tm.visible?(ctx); end
81
+ def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end
82
+ def arguments(owner, ctx); owner.arguments(ctx); end
81
83
  end
82
84
  end
83
85
 
@@ -172,8 +174,8 @@ module GraphQL
172
174
 
173
175
  # @param argument_owner [GraphQL::Field, GraphQL::InputObjectType]
174
176
  # @return [Array<GraphQL::Argument>] Visible arguments on `argument_owner`
175
- def arguments(argument_owner)
176
- @visible_arguments ||= read_through { |o| o.arguments(@context).each_value.select { |a| visible_argument?(a) } }
177
+ def arguments(argument_owner, ctx = nil)
178
+ @visible_arguments ||= read_through { |o| o.arguments(@context).each_value.select { |a| visible_argument?(a, @context) } }
177
179
  @visible_arguments[argument_owner]
178
180
  end
179
181
 
@@ -231,6 +233,13 @@ module GraphQL
231
233
  visible?(type_membership)
232
234
  end
233
235
 
236
+ def interface_type_memberships(obj_type, _ctx = nil)
237
+ @type_memberships ||= read_through do |obj_t|
238
+ obj_t.interface_type_memberships
239
+ end
240
+ @type_memberships[obj_type]
241
+ end
242
+
234
243
  private
235
244
 
236
245
  def visible_and_reachable_type?(type_defn)
@@ -161,7 +161,7 @@ module GraphQL
161
161
  include LazyHandlingMethods
162
162
  extend LazyHandlingMethods
163
163
 
164
- accepts_definitions \
164
+ deprecated_accepts_definitions \
165
165
  :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
166
166
  :validate_timeout, :validate_max_errors, :max_depth, :max_complexity, :default_max_page_size,
167
167
  :orphan_types, :resolve_type, :type_error, :parse_error,
@@ -502,7 +502,7 @@ module GraphQL
502
502
  # @param field_name [String]
503
503
  # @return [GraphQL::Field, nil] The field named `field_name` on `parent_type`
504
504
  # @see [GraphQL::Schema::Warden] Restricted access to members of a schema
505
- def get_field(parent_type, field_name)
505
+ def get_field(parent_type, field_name, _context = GraphQL::Query::NullContext)
506
506
  with_definition_error_check do
507
507
  parent_type_name = case parent_type
508
508
  when GraphQL::BaseType, Class, Module
@@ -845,7 +845,7 @@ module GraphQL
845
845
  # - Cause the Schema instance to be created, if it hasn't been created yet
846
846
  # - Delegate to that instance
847
847
  # Eventually, the methods will be moved into this class, removing the need for the singleton.
848
- def_delegators :graphql_definition,
848
+ def_delegators :deprecated_graphql_definition,
849
849
  # Execution
850
850
  :execution_strategy_for_operation,
851
851
  # Configuration
@@ -854,6 +854,10 @@ module GraphQL
854
854
  :id_from_object=, :object_from_id=,
855
855
  :remove_handler
856
856
 
857
+ def deprecated_graphql_definition
858
+ graphql_definition(silence_deprecation_warning: true)
859
+ end
860
+
857
861
  # @return [GraphQL::Subscriptions]
858
862
  attr_accessor :subscriptions
859
863
 
@@ -888,6 +892,17 @@ module GraphQL
888
892
  GraphQL::Language::DocumentFromSchemaDefinition.new(self).document
889
893
  end
890
894
 
895
+ # @return [String, nil]
896
+ def description(new_description = nil)
897
+ if new_description
898
+ @description = new_description
899
+ elsif defined?(@description)
900
+ @description
901
+ else
902
+ find_inherited_value(:description, nil)
903
+ end
904
+ end
905
+
891
906
  def find(path)
892
907
  if !@finder
893
908
  @find_cache = {}
@@ -896,8 +911,15 @@ module GraphQL
896
911
  @find_cache[path] ||= @finder.find(path)
897
912
  end
898
913
 
899
- def graphql_definition
900
- @graphql_definition ||= to_graphql
914
+ def graphql_definition(silence_deprecation_warning: false)
915
+ @graphql_definition ||= begin
916
+ unless silence_deprecation_warning
917
+ message = "Legacy `.graphql_definition` objects are deprecated and will be removed in GraphQL-Ruby 2.0. Use a class-based definition instead."
918
+ caller_message = "\n\nCalled on #{self.inspect} from:\n #{caller(1, 25).map { |l| " #{l}" }.join("\n")}"
919
+ GraphQL::Deprecation.warn(message + caller_message)
920
+ end
921
+ to_graphql(silence_deprecation_warning: silence_deprecation_warning)
922
+ end
901
923
  end
902
924
 
903
925
  def default_filter
@@ -929,19 +951,20 @@ module GraphQL
929
951
  find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins
930
952
  end
931
953
 
954
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
932
955
  def to_graphql
933
956
  schema_defn = self.new
934
957
  schema_defn.raise_definition_error = true
935
- schema_defn.query = query && query.graphql_definition
936
- schema_defn.mutation = mutation && mutation.graphql_definition
937
- schema_defn.subscription = subscription && subscription.graphql_definition
958
+ schema_defn.query = query && query.graphql_definition(silence_deprecation_warning: true)
959
+ schema_defn.mutation = mutation && mutation.graphql_definition(silence_deprecation_warning: true)
960
+ schema_defn.subscription = subscription && subscription.graphql_definition(silence_deprecation_warning: true)
938
961
  schema_defn.validate_timeout = validate_timeout
939
962
  schema_defn.validate_max_errors = validate_max_errors
940
963
  schema_defn.max_complexity = max_complexity
941
964
  schema_defn.error_bubbling = error_bubbling
942
965
  schema_defn.max_depth = max_depth
943
966
  schema_defn.default_max_page_size = default_max_page_size
944
- schema_defn.orphan_types = orphan_types.map(&:graphql_definition)
967
+ schema_defn.orphan_types = orphan_types.map { |t| t.graphql_definition(silence_deprecation_warning: true) }
945
968
  schema_defn.disable_introspection_entry_points = disable_introspection_entry_points?
946
969
  schema_defn.disable_schema_introspection_entry_point = disable_schema_introspection_entry_point?
947
970
  schema_defn.disable_type_introspection_entry_point = disable_type_introspection_entry_point?
@@ -1235,7 +1258,7 @@ module GraphQL
1235
1258
  when Module
1236
1259
  type_or_name
1237
1260
  else
1238
- raise ArgumentError, "unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})"
1261
+ raise GraphQL::InvariantError, "Unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})"
1239
1262
  end
1240
1263
 
1241
1264
  if parent_type.kind.fields? && (field = parent_type.get_field(field_name, context))
@@ -1707,6 +1730,7 @@ module GraphQL
1707
1730
  {
1708
1731
  backtrace: ctx[:backtrace],
1709
1732
  tracers: ctx[:tracers],
1733
+ dataloader: ctx[:dataloader],
1710
1734
  }
1711
1735
  else
1712
1736
  {}
@@ -33,6 +33,7 @@ module GraphQL
33
33
  GraphQL::StaticValidation::VariablesAreUsedAndDefined,
34
34
  GraphQL::StaticValidation::VariableUsagesAreAllowed,
35
35
  GraphQL::StaticValidation::MutationRootExists,
36
+ GraphQL::StaticValidation::QueryRootExists,
36
37
  GraphQL::StaticValidation::SubscriptionRootExists,
37
38
  GraphQL::StaticValidation::InputObjectNamesAreUnique,
38
39
  ]
@@ -110,7 +110,7 @@ module GraphQL
110
110
  end
111
111
 
112
112
  def on_directive(node, parent)
113
- directive_defn = @schema.directives[node.name]
113
+ directive_defn = @context.schema_directives[node.name]
114
114
  @directive_definitions.push(directive_defn)
115
115
  super
116
116
  @directive_definitions.pop
@@ -59,7 +59,7 @@ module GraphQL
59
59
  end
60
60
  end
61
61
  when GraphQL::Language::Nodes::Directive
62
- context.schema.directives[parent.name]
62
+ context.schema_directives[parent.name]
63
63
  when GraphQL::Language::Nodes::Field
64
64
  context.field_definition
65
65
  else
@@ -5,7 +5,7 @@ module GraphQL
5
5
  include GraphQL::Language
6
6
 
7
7
  def on_directive(node, parent)
8
- validate_location(node, parent, context.schema.directives)
8
+ validate_location(node, parent, context.schema_directives)
9
9
  super
10
10
  end
11
11
 
@@ -50,15 +50,15 @@ module GraphQL
50
50
  @arg_conflicts = nil
51
51
 
52
52
  yield
53
-
54
- field_conflicts.each_value { |error| add_error(error) }
55
- arg_conflicts.each_value { |error| add_error(error) }
53
+ # don't initialize these if they weren't initialized in the block:
54
+ @field_conflicts && @field_conflicts.each_value { |error| add_error(error) }
55
+ @arg_conflicts && @arg_conflicts.each_value { |error| add_error(error) }
56
56
  end
57
57
 
58
58
  def conflicts_within_selection_set(node, parent_type)
59
59
  return if parent_type.nil?
60
60
 
61
- fields, fragment_spreads = fields_and_fragments_from_selection(node, owner_type: parent_type, parents: [])
61
+ fields, fragment_spreads = fields_and_fragments_from_selection(node, owner_type: parent_type, parents: nil)
62
62
 
63
63
  # (A) Find find all conflicts "within" the fields of this selection set.
64
64
  find_conflicts_within(fields)
@@ -198,10 +198,14 @@ module GraphQL
198
198
  response_keys.each do |key, fields|
199
199
  next if fields.size < 2
200
200
  # find conflicts within nodes
201
- for i in 0..fields.size - 1
202
- for j in i + 1..fields.size - 1
201
+ i = 0
202
+ while i < fields.size
203
+ j = i + 1
204
+ while j < fields.size
203
205
  find_conflict(key, fields[i], fields[j])
206
+ j += 1
204
207
  end
208
+ i += 1
205
209
  end
206
210
  end
207
211
  end
@@ -243,7 +247,9 @@ module GraphQL
243
247
  end
244
248
 
245
249
  def find_conflicts_between_sub_selection_sets(field1, field2, mutually_exclusive:)
246
- return if field1.definition.nil? || field2.definition.nil?
250
+ return if field1.definition.nil? ||
251
+ field2.definition.nil? ||
252
+ (field1.node.selections.empty? && field2.node.selections.empty?)
247
253
 
248
254
  return_type1 = field1.definition.type.unwrap
249
255
  return_type2 = field2.definition.type.unwrap
@@ -323,6 +329,7 @@ module GraphQL
323
329
  if node.selections.empty?
324
330
  NO_SELECTIONS
325
331
  else
332
+ parents ||= []
326
333
  fields, fragment_spreads = find_fields_and_fragments(node.selections, owner_type: owner_type, parents: parents, fields: [], fragment_spreads: [])
327
334
  response_keys = fields.group_by { |f| f.node.alias || f.node.name }
328
335
  [response_keys, fragment_spreads]
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ module QueryRootExists
5
+ def on_operation_definition(node, _parent)
6
+ if (node.operation_type == 'query' || node.operation_type.nil?) && context.warden.root_type_for_operation("query").nil?
7
+ add_error(GraphQL::StaticValidation::QueryRootExistsError.new(
8
+ 'Schema is not configured for queries',
9
+ nodes: node
10
+ ))
11
+ else
12
+ super
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ class QueryRootExistsError < StaticValidation::Error
5
+
6
+ def initialize(message, path: nil, nodes: [])
7
+ super(message, path: path, nodes: nodes)
8
+ end
9
+
10
+ # A hash representation of this Message
11
+ def to_h
12
+ extensions = {
13
+ "code" => code,
14
+ }
15
+
16
+ super.merge({
17
+ "extensions" => extensions
18
+ })
19
+ end
20
+
21
+ def code
22
+ "missingQueryConfiguration"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -8,7 +8,7 @@ module GraphQL
8
8
  end
9
9
 
10
10
  def on_directive(node, _parent)
11
- directive_defn = context.schema.directives[node.name]
11
+ directive_defn = context.schema_directives[node.name]
12
12
  assert_required_args(node, directive_defn)
13
13
  super
14
14
  end
@@ -16,6 +16,8 @@ module GraphQL
16
16
  private
17
17
 
18
18
  def assert_required_args(ast_node, defn)
19
+ args = defn.arguments(context.query.context)
20
+ return if args.empty?
19
21
  present_argument_names = ast_node.arguments.map(&:name)
20
22
  required_argument_names = context.warden.arguments(defn)
21
23
  .select { |a| a.type.kind.non_null? && !a.default_value? && context.warden.get_argument(defn, a.name) }
@@ -40,7 +40,7 @@ module GraphQL
40
40
  nodes: [used_directives[directive_name], ast_directive],
41
41
  directive: directive_name,
42
42
  ))
43
- else
43
+ elsif !((dir_defn = context.schema_directives[directive_name]) && dir_defn.repeatable?)
44
44
  used_directives[directive_name] = ast_directive
45
45
  end
46
46
  end
@@ -68,6 +68,12 @@ module GraphQL
68
68
  arg_defn = context.warden.get_argument(argument_owner, arg_node.name)
69
69
  arg_defn_type = arg_defn.type
70
70
 
71
+ # If the argument is non-null, but it was given a default value,
72
+ # then treat it as nullable in practice, see https://github.com/rmosolgo/graphql-ruby/issues/3793
73
+ if arg_defn_type.non_null? && arg_defn.default_value?
74
+ arg_defn_type = arg_defn_type.of_type
75
+ end
76
+
71
77
  var_inner_type = var_type.unwrap
72
78
  arg_inner_type = arg_defn_type.unwrap
73
79
 
@@ -44,6 +44,10 @@ module GraphQL
44
44
  def too_many_errors?
45
45
  @errors.length >= @max_errors
46
46
  end
47
+
48
+ def schema_directives
49
+ @schema_directives ||= schema.directives
50
+ end
47
51
  end
48
52
  end
49
53
  end
@@ -1,2 +1,2 @@
1
1
  # frozen_string_literal: true
2
- GraphQL::STRING_TYPE = GraphQL::Types::String.graphql_definition
2
+ GraphQL::STRING_TYPE = GraphQL::Types::String.graphql_definition(silence_deprecation_warning: true)
@@ -216,6 +216,8 @@ module GraphQL
216
216
  # The channel was closed, forget about it.
217
217
  def delete_subscription(subscription_id)
218
218
  query = @subscriptions.delete(subscription_id)
219
+ # In case this came from the server, tell the client to unsubscribe:
220
+ @action_cable.server.broadcast(stream_subscription_name(subscription_id), { more: false })
219
221
  # This can be `nil` when `.trigger` happens inside an unsubscribed ActionCable channel,
220
222
  # see https://github.com/rmosolgo/graphql-ruby/issues/2478
221
223
  if query
@@ -71,9 +71,17 @@ module GraphQL
71
71
  when SYMBOL_KEY
72
72
  value[SYMBOL_KEY].to_sym
73
73
  when TIMESTAMP_KEY
74
- timestamp_class_name, timestamp_s = value[TIMESTAMP_KEY]
74
+ timestamp_class_name, *timestamp_args = value[TIMESTAMP_KEY]
75
75
  timestamp_class = Object.const_get(timestamp_class_name)
76
- timestamp_class.strptime(timestamp_s, TIMESTAMP_FORMAT)
76
+ if defined?(ActiveSupport::TimeWithZone) && timestamp_class <= ActiveSupport::TimeWithZone
77
+ zone_name, timestamp_s = timestamp_args
78
+ zone = ActiveSupport::TimeZone[zone_name]
79
+ raise "Zone #{zone_name} not found, unable to deserialize" unless zone
80
+ zone.strptime(timestamp_s, TIMESTAMP_FORMAT)
81
+ else
82
+ timestamp_s = timestamp_args.first
83
+ timestamp_class.strptime(timestamp_s, TIMESTAMP_FORMAT)
84
+ end
77
85
  when OPEN_STRUCT_KEY
78
86
  ostruct_values = load_value(value[OPEN_STRUCT_KEY])
79
87
  OpenStruct.new(ostruct_values)
@@ -123,6 +131,18 @@ module GraphQL
123
131
  { SYMBOL_KEY => obj.to_s }
124
132
  elsif obj.respond_to?(:to_gid_param)
125
133
  {GLOBALID_KEY => obj.to_gid_param}
134
+ elsif defined?(ActiveSupport::TimeWithZone) && obj.is_a?(ActiveSupport::TimeWithZone) && obj.class.name != Time.name
135
+ # This handles a case where Rails prior to 7 would
136
+ # make the class ActiveSupport::TimeWithZone return "Time" for
137
+ # its name. In Rails 7, it will now return "ActiveSupport::TimeWithZone",
138
+ # which happens to be incompatible with expectations we have
139
+ # with what a Time class supports ( notably, strptime in `load_value` ).
140
+ #
141
+ # This now passes along the name of the zone, such that a future deserialization
142
+ # of this string will use the correct time zone from the ActiveSupport TimeZone
143
+ # list to produce the time.
144
+ #
145
+ { TIMESTAMP_KEY => [obj.class.name, obj.time_zone.name, obj.strftime(TIMESTAMP_FORMAT)] }
126
146
  elsif obj.is_a?(Date) || obj.is_a?(Time)
127
147
  # DateTime extends Date; for TimeWithZone, call `.utc` first.
128
148
  { TIMESTAMP_KEY => [obj.class.name, obj.strftime(TIMESTAMP_FORMAT)] }
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'graphql/tracing/notifications_tracing'
4
+
3
5
  module GraphQL
4
6
  module Tracing
5
7
  # This implementation forwards events to ActiveSupport::Notifications
@@ -8,27 +10,11 @@ module GraphQL
8
10
  # @see KEYS for event names
9
11
  module ActiveSupportNotificationsTracing
10
12
  # A cache of frequently-used keys to avoid needless string allocations
11
- KEYS = {
12
- "lex" => "lex.graphql",
13
- "parse" => "parse.graphql",
14
- "validate" => "validate.graphql",
15
- "analyze_multiplex" => "analyze_multiplex.graphql",
16
- "analyze_query" => "analyze_query.graphql",
17
- "execute_query" => "execute_query.graphql",
18
- "execute_query_lazy" => "execute_query_lazy.graphql",
19
- "execute_field" => "execute_field.graphql",
20
- "execute_field_lazy" => "execute_field_lazy.graphql",
21
- "authorized" => "authorized.graphql",
22
- "authorized_lazy" => "authorized_lazy.graphql",
23
- "resolve_type" => "resolve_type.graphql",
24
- "resolve_type_lazy" => "resolve_type.graphql",
25
- }
13
+ KEYS = NotificationsTracing::KEYS
14
+ NOTIFICATIONS_ENGINE = NotificationsTracing.new(ActiveSupport::Notifications) if defined?(ActiveSupport::Notifications)
26
15
 
27
- def self.trace(key, metadata)
28
- prefixed_key = KEYS[key] || "#{key}.graphql"
29
- ActiveSupport::Notifications.instrument(prefixed_key, metadata) do
30
- yield
31
- end
16
+ def self.trace(key, metadata, &blk)
17
+ NOTIFICATIONS_ENGINE.trace(key, metadata, &blk)
32
18
  end
33
19
  end
34
20
  end
@@ -15,17 +15,23 @@ module GraphQL
15
15
  }
16
16
 
17
17
  def platform_trace(platform_key, key, data)
18
- tracer.trace(platform_key, service: service_name) do |span|
19
- span.span_type = 'custom'
18
+ tracer.trace(platform_key, service: options[:service], type: 'custom') do |span|
19
+ span.set_tag('component', 'graphql')
20
+ span.set_tag('operation', key)
20
21
 
21
22
  if key == 'execute_multiplex'
22
23
  operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ')
23
- span.resource = operations unless operations.empty?
24
24
 
25
- # For top span of query, set the analytics sample rate tag, if available.
26
- if analytics_enabled?
27
- Datadog::Contrib::Analytics.set_sample_rate(span, analytics_sample_rate)
25
+ resource = if operations.empty?
26
+ first_query = data[:multiplex].queries.first
27
+ fallback_transaction_name(first_query && first_query.context)
28
+ else
29
+ operations
28
30
  end
31
+ span.resource = resource if resource
32
+
33
+ # [Deprecated] will be removed in the future
34
+ span.set_metric('_dd1.sr.eausr', analytics_sample_rate) if analytics_enabled?
29
35
  end
30
36
 
31
37
  if key == 'execute_query'
@@ -34,26 +40,29 @@ module GraphQL
34
40
  span.set_tag(:query_string, data[:query].query_string)
35
41
  end
36
42
 
43
+ prepare_span(key, data, span)
44
+
37
45
  yield
38
46
  end
39
47
  end
40
48
 
41
- def service_name
42
- options.fetch(:service, 'ruby-graphql')
49
+ # Implement this method in a subclass to apply custom tags to datadog spans
50
+ # @param key [String] The event being traced
51
+ # @param data [Hash] The runtime data for this event (@see GraphQL::Tracing for keys for each event)
52
+ # @param span [Datadog::Tracing::SpanOperation] The datadog span for this event
53
+ def prepare_span(key, data, span)
43
54
  end
44
55
 
45
56
  def tracer
46
- options.fetch(:tracer, Datadog.tracer)
47
- end
57
+ default_tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer
48
58
 
49
- def analytics_available?
50
- defined?(Datadog::Contrib::Analytics) \
51
- && Datadog::Contrib::Analytics.respond_to?(:enabled?) \
52
- && Datadog::Contrib::Analytics.respond_to?(:set_sample_rate)
59
+ # [Deprecated] options[:tracer] will be removed in the future
60
+ options.fetch(:tracer, default_tracer)
53
61
  end
54
62
 
55
63
  def analytics_enabled?
56
- analytics_available? && Datadog::Contrib::Analytics.enabled?(options.fetch(:analytics_enabled, false))
64
+ # [Deprecated] options[:analytics_enabled] will be removed in the future
65
+ options.fetch(:analytics_enabled, false)
57
66
  end
58
67
 
59
68
  def analytics_sample_rate
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ # This implementation forwards events to a notification handler (i.e.
6
+ # ActiveSupport::Notifications or Dry::Monitor::Notifications)
7
+ # with a `graphql` suffix.
8
+ #
9
+ # @see KEYS for event names
10
+ class NotificationsTracing
11
+ # A cache of frequently-used keys to avoid needless string allocations
12
+ KEYS = {
13
+ "lex" => "lex.graphql",
14
+ "parse" => "parse.graphql",
15
+ "validate" => "validate.graphql",
16
+ "analyze_multiplex" => "analyze_multiplex.graphql",
17
+ "analyze_query" => "analyze_query.graphql",
18
+ "execute_query" => "execute_query.graphql",
19
+ "execute_query_lazy" => "execute_query_lazy.graphql",
20
+ "execute_field" => "execute_field.graphql",
21
+ "execute_field_lazy" => "execute_field_lazy.graphql",
22
+ "authorized" => "authorized.graphql",
23
+ "authorized_lazy" => "authorized_lazy.graphql",
24
+ "resolve_type" => "resolve_type.graphql",
25
+ "resolve_type_lazy" => "resolve_type.graphql",
26
+ }
27
+
28
+ MAX_KEYS_SIZE = 100
29
+
30
+ # Initialize a new NotificationsTracing instance
31
+ #
32
+ # @param [Object] notifications_engine The notifications engine to use
33
+ def initialize(notifications_engine)
34
+ @notifications_engine = notifications_engine
35
+ end
36
+
37
+ # Sends a GraphQL tracing event to the notification handler
38
+ #
39
+ # @example
40
+ # . notifications_engine = Dry::Monitor::Notifications.new(:graphql)
41
+ # . tracer = GraphQL::Tracing::NotificationsTracing.new(notifications_engine)
42
+ # . tracer.trace("lex") { ... }
43
+ #
44
+ # @param [string] key The key for the event
45
+ # @param [Hash] metadata The metadata for the event
46
+ # @yield The block to execute for the event
47
+ def trace(key, metadata, &blk)
48
+ prefixed_key = KEYS[key] || "#{key}.graphql"
49
+
50
+ # Cache the new keys while making sure not to induce a memory leak
51
+ if KEYS.size < MAX_KEYS_SIZE
52
+ KEYS[key] ||= prefixed_key
53
+ end
54
+
55
+ @notifications_engine.instrument(prefixed_key, metadata, &blk)
56
+ end
57
+ end
58
+ end
59
+ end