graphql 2.4.7 → 2.4.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/backtrace/table.rb +95 -55
  3. data/lib/graphql/backtrace.rb +1 -19
  4. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +11 -4
  5. data/lib/graphql/execution/interpreter/runtime.rb +34 -26
  6. data/lib/graphql/execution/interpreter.rb +3 -1
  7. data/lib/graphql/execution/multiplex.rb +0 -4
  8. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  9. data/lib/graphql/query.rb +0 -8
  10. data/lib/graphql/schema/build_from_definition.rb +1 -0
  11. data/lib/graphql/schema/enum.rb +21 -1
  12. data/lib/graphql/schema/resolver.rb +1 -0
  13. data/lib/graphql/schema/subscription.rb +50 -4
  14. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  15. data/lib/graphql/schema/visibility/profile.rb +1 -1
  16. data/lib/graphql/schema.rb +8 -24
  17. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  18. data/lib/graphql/subscriptions/event.rb +12 -1
  19. data/lib/graphql/subscriptions/serialize.rb +1 -3
  20. data/lib/graphql/tracing/active_support_notifications_trace.rb +1 -1
  21. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  22. data/lib/graphql/tracing/appoptics_trace.rb +2 -0
  23. data/lib/graphql/tracing/appoptics_tracing.rb +2 -0
  24. data/lib/graphql/tracing/appsignal_trace.rb +2 -0
  25. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  26. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  27. data/lib/graphql/tracing/data_dog_trace.rb +2 -0
  28. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  29. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  30. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  31. data/lib/graphql/tracing/new_relic_trace.rb +2 -0
  32. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  33. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  34. data/lib/graphql/tracing/null_trace.rb +9 -0
  35. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  36. data/lib/graphql/tracing/prometheus_trace.rb +5 -0
  37. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  38. data/lib/graphql/tracing/scout_trace.rb +2 -0
  39. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  40. data/lib/graphql/tracing/sentry_trace.rb +2 -0
  41. data/lib/graphql/tracing/statsd_trace.rb +2 -0
  42. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  43. data/lib/graphql/tracing/trace.rb +4 -1
  44. data/lib/graphql/tracing.rb +28 -30
  45. data/lib/graphql/version.rb +1 -1
  46. metadata +103 -9
  47. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  48. data/lib/graphql/backtrace/trace.rb +0 -93
  49. data/lib/graphql/backtrace/tracer.rb +0 -80
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69f8b4084adf0530a973073bcacf76e536975b385e4927525504b648762e496e
4
- data.tar.gz: ef8e532575d445e0e96c1e5cccb2f6518d4a8759127ba8f27fd4d2aca4ef877a
3
+ metadata.gz: 687da1733554d6abed2daad506c7f9d55d9126e373274572af5b255c3d7b4918
4
+ data.tar.gz: c2d2f58175df62c91ab31c3216b970a0bc09d1752250fc8192d1faa958cf1fe9
5
5
  SHA512:
6
- metadata.gz: c9522e680f06a1810e019c64656fba5dd7d7d95e098f136e2f656837c928829eab7a1be5656c7c4c9fd68bf8f262ca9b7ed415d87c12997dc613c9f600e8994b
7
- data.tar.gz: 99a5a17272d80d555c98f8f091799e5b84656bfd42310e79d64b65140c7b2786e9f23f4759dfefb5b09c8ffc913349bfd0db3bcff5f6475087b350122aadd066
6
+ metadata.gz: f4001700112f8ed43e78b4c739daac90973f08035bef426d5611d91248dd60d4d0070f8fff7042ac57b0549ddac62ac1111262fe0a47c10a83cd3c7a9a0612b8
7
+ data.tar.gz: a6a1aaacf19bba38d2e0ee55ac9cdfa0d46b915706557c2dbac533d8fc58319d7c4ff8509019789fb39c46d6c01767f6cd27a02917fc7a3a7bdc30fc81eca5e4
@@ -36,7 +36,70 @@ module GraphQL
36
36
  private
37
37
 
38
38
  def rows
39
- @rows ||= build_rows(@context, rows: [HEADERS], top: true)
39
+ @rows ||= begin
40
+ query = @context.query
41
+ query_ctx = @context
42
+ runtime_inst = query_ctx.namespace(:interpreter_runtime)[:runtime]
43
+ result = runtime_inst.instance_variable_get(:@response)
44
+ rows = []
45
+ result_path = []
46
+ last_part = nil
47
+ path = @context.current_path
48
+ path.each do |path_part|
49
+ value = value_at(runtime_inst, result_path)
50
+
51
+ if result_path.empty?
52
+ name = query.selected_operation.operation_type || "query"
53
+ if (n = query.selected_operation_name)
54
+ name += " #{n}"
55
+ end
56
+ args = query.variables
57
+ else
58
+ name = result.graphql_field.path
59
+ args = result.graphql_arguments
60
+ end
61
+
62
+ object = result.graphql_parent ? result.graphql_parent.graphql_application_value : result.graphql_application_value
63
+ object = object.object.inspect
64
+
65
+ rows << [
66
+ result.ast_node.position.join(":"),
67
+ name,
68
+ "#{object}",
69
+ args.to_h.inspect,
70
+ inspect_result(value),
71
+ ]
72
+
73
+ result_path << path_part
74
+ if path_part == path.last
75
+ last_part = path_part
76
+ else
77
+ result = result[path_part]
78
+ end
79
+ end
80
+
81
+
82
+ object = result.graphql_application_value.object.inspect
83
+ ast_node = result.graphql_selections.find { |s| s.alias == last_part || s.name == last_part }
84
+ field_defn = query.get_field(result.graphql_result_type, ast_node.name)
85
+ args = query.arguments_for(ast_node, field_defn).to_h
86
+ field_path = field_defn.path
87
+ if ast_node.alias
88
+ field_path += " as #{ast_node.alias}"
89
+ end
90
+
91
+ rows << [
92
+ ast_node.position.join(":"),
93
+ field_path,
94
+ "#{object}",
95
+ args.inspect,
96
+ inspect_result(@override_value)
97
+ ]
98
+
99
+ rows << HEADERS
100
+ rows.reverse!
101
+ rows
102
+ end
40
103
  end
41
104
 
42
105
  # @return [String]
@@ -75,67 +138,44 @@ module GraphQL
75
138
  table
76
139
  end
77
140
 
78
- # @return [Array] 5 items for a backtrace table (not `key`)
79
- def build_rows(context_entry, rows:, top: false)
80
- case context_entry
81
- when Backtrace::Frame
82
- field_alias = context_entry.ast_node.respond_to?(:alias) && context_entry.ast_node.alias
83
- value = if top && @override_value
84
- @override_value
85
- else
86
- value_at(@context.query.context.namespace(:interpreter_runtime)[:runtime], context_entry.path)
87
- end
88
- rows << [
89
- "#{context_entry.ast_node ? context_entry.ast_node.position.join(":") : ""}",
90
- "#{context_entry.field.path}#{field_alias ? " as #{field_alias}" : ""}",
91
- "#{context_entry.object.object.inspect}",
92
- context_entry.arguments.to_h.inspect, # rubocop:disable Development/ContextIsPassedCop -- unrelated method
93
- Backtrace::InspectResult.inspect_result(value),
94
- ]
95
- if (parent = context_entry.parent_frame)
96
- build_rows(parent, rows: rows)
97
- else
98
- rows
99
- end
100
- when GraphQL::Query::Context
101
- query = context_entry.query
102
- op = query.selected_operation
103
- if op
104
- op_type = op.operation_type
105
- position = "#{op.line}:#{op.col}"
106
- else
107
- op_type = "query"
108
- position = "?:?"
109
- end
110
- op_name = query.selected_operation_name
111
- object = query.root_value
112
- if object.is_a?(GraphQL::Schema::Object)
113
- object = object.object
114
- end
115
- value = value_at(context_entry.namespace(:interpreter_runtime)[:runtime], [])
116
- rows << [
117
- "#{position}",
118
- "#{op_type}#{op_name ? " #{op_name}" : ""}",
119
- "#{object.inspect}",
120
- query.variables.to_h.inspect,
121
- Backtrace::InspectResult.inspect_result(value),
122
- ]
123
- else
124
- raise "Unexpected get_rows subject #{context_entry.class} (#{context_entry.inspect})"
125
- end
126
- end
127
141
 
128
142
  def value_at(runtime, path)
129
143
  response = runtime.final_result
130
144
  path.each do |key|
131
- if response && (response = response[key])
132
- next
133
- else
134
- break
135
- end
145
+ response && (response = response[key])
136
146
  end
137
147
  response
138
148
  end
149
+
150
+ def inspect_result(obj)
151
+ case obj
152
+ when Hash
153
+ "{" +
154
+ obj.map do |key, val|
155
+ "#{key}: #{inspect_truncated(val)}"
156
+ end.join(", ") +
157
+ "}"
158
+ when Array
159
+ "[" +
160
+ obj.map { |v| inspect_truncated(v) }.join(", ") +
161
+ "]"
162
+ else
163
+ inspect_truncated(obj)
164
+ end
165
+ end
166
+
167
+ def inspect_truncated(obj)
168
+ case obj
169
+ when Hash
170
+ "{...}"
171
+ when Array
172
+ "[...]"
173
+ when GraphQL::Execution::Lazy
174
+ "(unresolved)"
175
+ else
176
+ "#{obj.inspect}"
177
+ end
178
+ end
139
179
  end
140
180
  end
141
181
  end
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
- require "graphql/backtrace/inspect_result"
3
2
  require "graphql/backtrace/table"
4
3
  require "graphql/backtrace/traced_error"
5
- require "graphql/backtrace/tracer"
6
- require "graphql/backtrace/trace"
7
4
  module GraphQL
8
5
  # Wrap unhandled errors with {TracedError}.
9
6
  #
@@ -24,7 +21,7 @@ module GraphQL
24
21
  def_delegators :to_a, :each, :[]
25
22
 
26
23
  def self.use(schema_defn)
27
- schema_defn.trace_with(self::Trace)
24
+ schema_defn.using_backtrace = true
28
25
  end
29
26
 
30
27
  def initialize(context, value: nil)
@@ -40,20 +37,5 @@ module GraphQL
40
37
  def to_a
41
38
  @table.to_backtrace
42
39
  end
43
-
44
- # Used for internal bookkeeping
45
- # @api private
46
- class Frame
47
- attr_reader :path, :query, :ast_node, :object, :field, :arguments, :parent_frame
48
- def initialize(path:, query:, ast_node:, object:, field:, arguments:, parent_frame:)
49
- @path = path
50
- @query = query
51
- @ast_node = ast_node
52
- @field = field
53
- @object = object
54
- @arguments = arguments
55
- @parent_frame = parent_frame
56
- end
57
- end
58
40
  end
59
41
  end
@@ -5,7 +5,10 @@ module GraphQL
5
5
  class Interpreter
6
6
  class Runtime
7
7
  module GraphQLResult
8
- def initialize(result_name, result_type, application_value, parent_result, is_non_null_in_parent, selections, is_eager)
8
+ def initialize(result_name, result_type, application_value, parent_result, is_non_null_in_parent, selections, is_eager, ast_node, graphql_arguments, graphql_field) # rubocop:disable Metrics/ParameterLists
9
+ @ast_node = ast_node
10
+ @graphql_arguments = graphql_arguments
11
+ @graphql_field = graphql_field
9
12
  @graphql_parent = parent_result
10
13
  @graphql_application_value = application_value
11
14
  @graphql_result_type = result_type
@@ -31,14 +34,14 @@ module GraphQL
31
34
 
32
35
  attr_accessor :graphql_dead
33
36
  attr_reader :graphql_parent, :graphql_result_name, :graphql_is_non_null_in_parent,
34
- :graphql_application_value, :graphql_result_type, :graphql_selections, :graphql_is_eager
37
+ :graphql_application_value, :graphql_result_type, :graphql_selections, :graphql_is_eager, :ast_node, :graphql_arguments, :graphql_field
35
38
 
36
39
  # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
37
40
  attr_accessor :graphql_result_data
38
41
  end
39
42
 
40
43
  class GraphQLResultHash
41
- def initialize(_result_name, _result_type, _application_value, _parent_result, _is_non_null_in_parent, _selections, _is_eager)
44
+ def initialize(_result_name, _result_type, _application_value, _parent_result, _is_non_null_in_parent, _selections, _is_eager, _ast_node, _graphql_arguments, graphql_field) # rubocop:disable Metrics/ParameterLists
42
45
  super
43
46
  @graphql_result_data = {}
44
47
  end
@@ -126,7 +129,7 @@ module GraphQL
126
129
  class GraphQLResultArray
127
130
  include GraphQLResult
128
131
 
129
- def initialize(_result_name, _result_type, _application_value, _parent_result, _is_non_null_in_parent, _selections, _is_eager)
132
+ def initialize(_result_name, _result_type, _application_value, _parent_result, _is_non_null_in_parent, _selections, _is_eager, _ast_node, _graphql_arguments, graphql_field) # rubocop:disable Metrics/ParameterLists
130
133
  super
131
134
  @graphql_result_data = []
132
135
  end
@@ -168,6 +171,10 @@ module GraphQL
168
171
  def values
169
172
  (@graphql_metadata || @graphql_result_data)
170
173
  end
174
+
175
+ def [](idx)
176
+ (@graphql_metadata || @graphql_result_data)[idx]
177
+ end
171
178
  end
172
179
  end
173
180
  end
@@ -74,7 +74,7 @@ module GraphQL
74
74
  runtime_object = root_type.wrap(query.root_value, context)
75
75
  runtime_object = schema.sync_lazy(runtime_object)
76
76
  is_eager = root_op_type == "mutation"
77
- @response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, root_operation.selections, is_eager)
77
+ @response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, root_operation.selections, is_eager, root_operation, nil, nil)
78
78
  st = get_current_runtime_state
79
79
  st.current_result = @response
80
80
 
@@ -85,7 +85,7 @@ module GraphQL
85
85
  call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
86
86
  each_gathered_selections(@response) do |selections, is_selection_array|
87
87
  if is_selection_array
88
- selection_response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, selections, is_eager)
88
+ selection_response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, selections, is_eager, root_operation, nil, nil)
89
89
  final_response = @response
90
90
  else
91
91
  selection_response = @response
@@ -574,7 +574,7 @@ module GraphQL
574
574
  r = begin
575
575
  current_type.coerce_result(value, context)
576
576
  rescue StandardError => err
577
- schema.handle_or_reraise(context, err)
577
+ query.handle_or_reraise(err)
578
578
  end
579
579
  set_result(selection_result, result_name, r, false, is_non_null)
580
580
  r
@@ -609,11 +609,11 @@ module GraphQL
609
609
  after_lazy(object_proxy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_object, runtime_state|
610
610
  continue_value = continue_value(inner_object, field, is_non_null, ast_node, result_name, selection_result)
611
611
  if HALT != continue_value
612
- response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false)
612
+ response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
613
613
  set_result(selection_result, result_name, response_hash, true, is_non_null)
614
614
  each_gathered_selections(response_hash) do |selections, is_selection_array|
615
615
  if is_selection_array
616
- this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false)
616
+ this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false, ast_node, arguments, field)
617
617
  final_result = response_hash
618
618
  else
619
619
  this_result = response_hash
@@ -634,35 +634,43 @@ module GraphQL
634
634
  # This is true for objects, unions, and interfaces
635
635
  use_dataloader_job = !inner_type.unwrap.kind.input?
636
636
  inner_type_non_null = inner_type.non_null?
637
- response_list = GraphQLResultArray.new(result_name, current_type, owner_object, selection_result, is_non_null, next_selections, false)
637
+ response_list = GraphQLResultArray.new(result_name, current_type, owner_object, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
638
638
  set_result(selection_result, result_name, response_list, true, is_non_null)
639
639
  idx = nil
640
640
  list_value = begin
641
- value.each do |inner_value|
642
- idx ||= 0
643
- this_idx = idx
644
- idx += 1
645
- if use_dataloader_job
646
- @dataloader.append_job do
641
+ begin
642
+ value.each do |inner_value|
643
+ idx ||= 0
644
+ this_idx = idx
645
+ idx += 1
646
+ if use_dataloader_job
647
+ @dataloader.append_job do
648
+ resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state)
649
+ end
650
+ else
647
651
  resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state)
648
652
  end
649
- else
650
- resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state)
651
653
  end
652
- end
653
654
 
654
- response_list
655
- rescue NoMethodError => err
656
- # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
657
- if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
658
- # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
659
- raise ListResultFailedError.new(value: value, field: field, path: current_path)
660
- else
661
- # This was some other NoMethodError -- let it bubble to reveal the real error.
662
- raise
655
+ response_list
656
+ rescue NoMethodError => err
657
+ # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
658
+ if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
659
+ # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
660
+ raise ListResultFailedError.new(value: value, field: field, path: current_path)
661
+ else
662
+ # This was some other NoMethodError -- let it bubble to reveal the real error.
663
+ raise
664
+ end
665
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
666
+ ex_err
667
+ rescue StandardError => err
668
+ begin
669
+ query.handle_or_reraise(err)
670
+ rescue GraphQL::ExecutionError => ex_err
671
+ ex_err
672
+ end
663
673
  end
664
- rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
665
- ex_err
666
674
  rescue StandardError => err
667
675
  begin
668
676
  query.handle_or_reraise(err)
@@ -53,7 +53,9 @@ module GraphQL
53
53
  results = []
54
54
  queries.each_with_index do |query, idx|
55
55
  if query.subscription? && !query.subscription_update?
56
- query.context.namespace(:subscriptions)[:events] = []
56
+ subs_namespace = query.context.namespace(:subscriptions)
57
+ subs_namespace[:events] = []
58
+ subs_namespace[:subscriptions] = {}
57
59
  end
58
60
  multiplex.dataloader.append_job {
59
61
  operation = query.selected_operation
@@ -35,10 +35,6 @@ module GraphQL
35
35
  @current_trace = @context[:trace] || schema.new_trace(multiplex: self)
36
36
  @dataloader = @context[:dataloader] ||= @schema.dataloader_class.new
37
37
  @tracers = schema.tracers + (context[:tracers] || [])
38
- # Support `context: {backtrace: true}`
39
- if context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer)
40
- @tracers << GraphQL::Backtrace::Tracer
41
- end
42
38
  @max_complexity = max_complexity
43
39
  end
44
40
  end
@@ -7,7 +7,7 @@ module GraphQL
7
7
  "a __DirectiveLocation describes one such possible adjacencies."
8
8
 
9
9
  GraphQL::Schema::Directive::LOCATIONS.each do |location|
10
- value(location.to_s, GraphQL::Schema::Directive::LOCATION_DESCRIPTIONS[location], value: location)
10
+ value(location.to_s, GraphQL::Schema::Directive::LOCATION_DESCRIPTIONS[location], value: location, value_method: false)
11
11
  end
12
12
  introspection true
13
13
  end
data/lib/graphql/query.rb CHANGED
@@ -127,14 +127,6 @@ module GraphQL
127
127
  context_tracers = (context ? context.fetch(:tracers, []) : [])
128
128
  @tracers = schema.tracers + context_tracers
129
129
 
130
- # Support `ctx[:backtrace] = true` for wrapping backtraces
131
- if context && context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer)
132
- if schema.trace_class <= GraphQL::Tracing::CallLegacyTracers
133
- context_tracers += [GraphQL::Backtrace::Tracer]
134
- @tracers << GraphQL::Backtrace::Tracer
135
- end
136
- end
137
-
138
130
  if !context_tracers.empty? && !(schema.trace_class <= GraphQL::Tracing::CallLegacyTracers)
139
131
  raise ArgumentError, "context[:tracers] are not supported without `trace_with(GraphQL::Tracing::CallLegacyTracers)` in the schema configuration, please add it."
140
132
  end
@@ -298,6 +298,7 @@ module GraphQL
298
298
  description: enum_value_definition.description,
299
299
  directives: builder.prepare_directives(enum_value_definition, type_resolver),
300
300
  ast_node: enum_value_definition,
301
+ value_method: GraphQL::Schema::Enum.respond_to?(enum_value_definition.name.downcase) ? false : nil,
301
302
  )
302
303
  end
303
304
  end
@@ -61,12 +61,17 @@ module GraphQL
61
61
  # @option kwargs [String] :description, the GraphQL description for this value, present in documentation
62
62
  # @option kwargs [String] :comment, the GraphQL comment for this value, present in documentation
63
63
  # @option kwargs [::Object] :value the translated Ruby value for this object (defaults to `graphql_name`)
64
+ # @option kwargs [::Object] :value_method, the method name to fetch `graphql_name` (defaults to `graphql_name.downcase`)
64
65
  # @option kwargs [String] :deprecation_reason if this object is deprecated, include a message here
66
+ # @param value_method [Symbol, false] A method to generate for this value, or `false` to skip generation
65
67
  # @return [void]
66
68
  # @see {Schema::EnumValue} which handles these inputs by default
67
- def value(*args, **kwargs, &block)
69
+ def value(*args, value_method: nil, **kwargs, &block)
68
70
  kwargs[:owner] = self
69
71
  value = enum_value_class.new(*args, **kwargs, &block)
72
+
73
+ generate_value_method(value, value_method)
74
+
70
75
  key = value.graphql_name
71
76
  prev_value = own_values[key]
72
77
  case prev_value
@@ -223,6 +228,21 @@ module GraphQL
223
228
  def own_values
224
229
  @own_values ||= {}
225
230
  end
231
+
232
+ def generate_value_method(value, configured_value_method)
233
+ return if configured_value_method == false
234
+
235
+ value_method_name = configured_value_method || value.graphql_name.downcase
236
+
237
+ if respond_to?(value_method_name.to_sym)
238
+ warn "Failed to define value method for :#{value_method_name}, because " \
239
+ "#{value.owner.name || value.owner.graphql_name} already responds to that method. Use `value_method:` to override the method name " \
240
+ "or `value_method: false` to disable Enum value method generation."
241
+ return
242
+ end
243
+
244
+ instance_eval("def #{value_method_name}; #{value.graphql_name.inspect}; end;")
245
+ end
226
246
  end
227
247
 
228
248
  enum_value_class(GraphQL::Schema::EnumValue)
@@ -22,6 +22,7 @@ module GraphQL
22
22
  include Schema::Member::GraphQLTypeNames
23
23
  # Really we only need description & comment from here, but:
24
24
  extend Schema::Member::BaseDSLMethods
25
+ extend Member::BaseDSLMethods::ConfigurationExtension
25
26
  extend GraphQL::Schema::Member::HasArguments
26
27
  extend GraphQL::Schema::Member::HasValidators
27
28
  include Schema::Member::HasPath
@@ -19,13 +19,22 @@ module GraphQL
19
19
  # propagate null.
20
20
  null false
21
21
 
22
+ # @api private
22
23
  def initialize(object:, context:, field:)
23
24
  super
24
25
  # Figure out whether this is an update or an initial subscription
25
26
  @mode = context.query.subscription_update? ? :update : :subscribe
27
+ @subscription_written = false
28
+ @original_arguments = nil
29
+ if (subs_ns = context.namespace(:subscriptions)) &&
30
+ (sub_insts = subs_ns[:subscriptions])
31
+ sub_insts[context.current_path] = self
32
+ end
26
33
  end
27
34
 
35
+ # @api private
28
36
  def resolve_with_support(**args)
37
+ @original_arguments = args # before `loads:` have been run
29
38
  result = nil
30
39
  unsubscribed = true
31
40
  unsubscribed_result = catch :graphql_subscription_unsubscribed do
@@ -46,7 +55,9 @@ module GraphQL
46
55
  end
47
56
  end
48
57
 
49
- # Implement the {Resolve} API
58
+ # Implement the {Resolve} API.
59
+ # You can implement this if you want code to run for _both_ the initial subscription
60
+ # and for later updates. Or, implement {#subscribe} and {#update}
50
61
  def resolve(**args)
51
62
  # Dispatch based on `@mode`, which will raise a `NoMethodError` if we ever
52
63
  # have an unexpected `@mode`
@@ -54,6 +65,7 @@ module GraphQL
54
65
  end
55
66
 
56
67
  # Wrap the user-defined `#subscribe` hook
68
+ # @api private
57
69
  def resolve_subscribe(**args)
58
70
  ret_val = !args.empty? ? subscribe(**args) : subscribe
59
71
  if ret_val == :no_response
@@ -71,6 +83,7 @@ module GraphQL
71
83
  end
72
84
 
73
85
  # Wrap the user-provided `#update` hook
86
+ # @api private
74
87
  def resolve_update(**args)
75
88
  ret_val = !args.empty? ? update(**args) : update
76
89
  if ret_val == NO_UPDATE
@@ -106,14 +119,13 @@ module GraphQL
106
119
  throw :graphql_subscription_unsubscribed, update_value
107
120
  end
108
121
 
109
- READING_SCOPE = ::Object.new
110
122
  # Call this method to provide a new subscription_scope; OR
111
123
  # call it without an argument to get the subscription_scope
112
124
  # @param new_scope [Symbol]
113
125
  # @param optional [Boolean] If true, then don't require `scope:` to be provided to updates to this subscription.
114
126
  # @return [Symbol]
115
- def self.subscription_scope(new_scope = READING_SCOPE, optional: false)
116
- if new_scope != READING_SCOPE
127
+ def self.subscription_scope(new_scope = NOT_CONFIGURED, optional: false)
128
+ if new_scope != NOT_CONFIGURED
117
129
  @subscription_scope = new_scope
118
130
  @subscription_scope_optional = optional
119
131
  elsif defined?(@subscription_scope)
@@ -150,6 +162,40 @@ module GraphQL
150
162
  def self.topic_for(arguments:, field:, scope:)
151
163
  Subscriptions::Serialize.dump_recursive([scope, field.graphql_name, arguments])
152
164
  end
165
+
166
+ # Calls through to `schema.subscriptions` to register this subscription with the backend.
167
+ # This is automatically called by GraphQL-Ruby after a query finishes successfully,
168
+ # but if you need to commit the subscription during `#subscribe`, you can call it there.
169
+ # (This method also sets a flag showing that this subscription was already written.)
170
+ #
171
+ # If you call this method yourself, you may also need to {#unsubscribe}
172
+ # or call `subscriptions.delete_subscription` to clean up the database if the query crashes with an error
173
+ # later in execution.
174
+ # @return [void]
175
+ def write_subscription
176
+ if subscription_written?
177
+ raise GraphQL::Error, "`write_subscription` was called but `#{self.class}#subscription_written?` is already true. Remove a call to `write subscription`."
178
+ else
179
+ @subscription_written = true
180
+ context.schema.subscriptions.write_subscription(context.query, [event])
181
+ end
182
+ nil
183
+ end
184
+
185
+ # @return [Boolean] `true` if {#write_subscription} was called already
186
+ def subscription_written?
187
+ @subscription_written
188
+ end
189
+
190
+ # @return [Subscriptions::Event] This object is used as a representation of this subscription for the backend
191
+ def event
192
+ @event ||= Subscriptions::Event.new(
193
+ name: field.name,
194
+ arguments: @original_arguments,
195
+ context: context,
196
+ field: field,
197
+ )
198
+ end
153
199
  end
154
200
  end
155
201
  end
@@ -51,19 +51,36 @@ module GraphQL
51
51
  end
52
52
 
53
53
  def validate(_object, context, value)
54
- matched_conditions = 0
54
+ fully_matched_conditions = 0
55
+ partially_matched_conditions = 0
55
56
 
56
57
  if !value.nil?
57
58
  @one_of.each do |one_of_condition|
58
59
  case one_of_condition
59
60
  when Symbol
60
61
  if value.key?(one_of_condition)
61
- matched_conditions += 1
62
+ fully_matched_conditions += 1
62
63
  end
63
64
  when Array
64
- if one_of_condition.all? { |k| value.key?(k) }
65
- matched_conditions += 1
66
- break
65
+ any_match = false
66
+ full_match = true
67
+
68
+ one_of_condition.each do |k|
69
+ if value.key?(k)
70
+ any_match = true
71
+ else
72
+ full_match = false
73
+ end
74
+ end
75
+
76
+ partial_match = !full_match && any_match
77
+
78
+ if full_match
79
+ fully_matched_conditions += 1
80
+ end
81
+
82
+ if partial_match
83
+ partially_matched_conditions += 1
67
84
  end
68
85
  else
69
86
  raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}"
@@ -71,7 +88,7 @@ module GraphQL
71
88
  end
72
89
  end
73
90
 
74
- if matched_conditions == 1
91
+ if fully_matched_conditions == 1 && partially_matched_conditions == 0
75
92
  nil # OK
76
93
  else
77
94
  @message || build_message(context)
@@ -159,7 +159,7 @@ module GraphQL
159
159
  end
160
160
  end
161
161
  end
162
- visible_f.ensure_loaded
162
+ visible_f&.ensure_loaded
163
163
  elsif f && @cached_visible_fields[owner][f.ensure_loaded]
164
164
  f
165
165
  else