graphql 2.4.8 → 2.4.10

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 (44) 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/current.rb +5 -0
  5. data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
  6. data/lib/graphql/dataloader/active_record_source.rb +26 -0
  7. data/lib/graphql/dataloader/async_dataloader.rb +17 -5
  8. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  9. data/lib/graphql/dataloader/source.rb +2 -2
  10. data/lib/graphql/dataloader.rb +37 -5
  11. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +11 -4
  12. data/lib/graphql/execution/interpreter/runtime.rb +59 -32
  13. data/lib/graphql/execution/interpreter.rb +9 -1
  14. data/lib/graphql/execution/multiplex.rb +0 -4
  15. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  16. data/lib/graphql/language/parser.rb +1 -1
  17. data/lib/graphql/query.rb +8 -12
  18. data/lib/graphql/schema/build_from_definition.rb +1 -0
  19. data/lib/graphql/schema/enum.rb +21 -1
  20. data/lib/graphql/schema/interface.rb +1 -0
  21. data/lib/graphql/schema/loader.rb +1 -0
  22. data/lib/graphql/schema/member/has_dataloader.rb +56 -0
  23. data/lib/graphql/schema/member.rb +1 -0
  24. data/lib/graphql/schema/object.rb +17 -8
  25. data/lib/graphql/schema/resolver.rb +2 -5
  26. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  27. data/lib/graphql/schema/visibility/profile.rb +5 -5
  28. data/lib/graphql/schema/visibility.rb +14 -9
  29. data/lib/graphql/schema.rb +9 -25
  30. data/lib/graphql/static_validation/validator.rb +6 -1
  31. data/lib/graphql/subscriptions/serialize.rb +1 -3
  32. data/lib/graphql/tracing/appoptics_trace.rb +1 -1
  33. data/lib/graphql/tracing/new_relic_trace.rb +138 -41
  34. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  35. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  36. data/lib/graphql/tracing/perfetto_trace.rb +726 -0
  37. data/lib/graphql/tracing/trace.rb +125 -1
  38. data/lib/graphql/tracing.rb +1 -0
  39. data/lib/graphql/version.rb +1 -1
  40. metadata +135 -10
  41. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  42. data/lib/graphql/backtrace/trace.rb +0 -93
  43. data/lib/graphql/backtrace/tracer.rb +0 -80
  44. data/lib/graphql/schema/null_mask.rb +0 -11
@@ -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
@@ -218,8 +218,10 @@ module GraphQL
218
218
  result_name, field_ast_nodes_or_ast_node, selections_result
219
219
  )
220
220
  finished_jobs += 1
221
- if target_result && finished_jobs == enqueued_jobs
222
- selections_result.merge_into(target_result)
221
+ if finished_jobs == enqueued_jobs
222
+ if target_result
223
+ selections_result.merge_into(target_result)
224
+ end
223
225
  end
224
226
  @dataloader.clear_cache
225
227
  }
@@ -229,8 +231,10 @@ module GraphQL
229
231
  result_name, field_ast_nodes_or_ast_node, selections_result
230
232
  )
231
233
  finished_jobs += 1
232
- if target_result && finished_jobs == enqueued_jobs
233
- selections_result.merge_into(target_result)
234
+ if finished_jobs == enqueued_jobs
235
+ if target_result
236
+ selections_result.merge_into(target_result)
237
+ end
234
238
  end
235
239
  }
236
240
  end
@@ -371,6 +375,7 @@ module GraphQL
371
375
  end
372
376
  # Actually call the field resolver and capture the result
373
377
  app_result = begin
378
+ @current_trace.begin_execute_field(field_defn, object, kwarg_arguments, query)
374
379
  @current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
375
380
  field_defn.resolve(object, kwarg_arguments, context)
376
381
  end
@@ -383,6 +388,7 @@ module GraphQL
383
388
  ex_err
384
389
  end
385
390
  end
391
+ @current_trace.end_execute_field(field_defn, object, kwarg_arguments, query, app_result)
386
392
  after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_result, runtime_state|
387
393
  owner_type = selection_result.graphql_result_type
388
394
  return_type = field_defn.type
@@ -391,6 +397,8 @@ module GraphQL
391
397
  was_scoped = runtime_state.was_authorized_by_scope_items
392
398
  runtime_state.was_authorized_by_scope_items = nil
393
399
  continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result, was_scoped, runtime_state)
400
+ else
401
+ nil
394
402
  end
395
403
  end
396
404
  end
@@ -574,7 +582,7 @@ module GraphQL
574
582
  r = begin
575
583
  current_type.coerce_result(value, context)
576
584
  rescue StandardError => err
577
- schema.handle_or_reraise(context, err)
585
+ query.handle_or_reraise(err)
578
586
  end
579
587
  set_result(selection_result, result_name, r, false, is_non_null)
580
588
  r
@@ -609,11 +617,11 @@ module GraphQL
609
617
  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
618
  continue_value = continue_value(inner_object, field, is_non_null, ast_node, result_name, selection_result)
611
619
  if HALT != continue_value
612
- response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false)
620
+ response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
613
621
  set_result(selection_result, result_name, response_hash, true, is_non_null)
614
622
  each_gathered_selections(response_hash) do |selections, is_selection_array|
615
623
  if is_selection_array
616
- this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false)
624
+ this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false, ast_node, arguments, field)
617
625
  final_result = response_hash
618
626
  else
619
627
  this_result = response_hash
@@ -634,35 +642,43 @@ module GraphQL
634
642
  # This is true for objects, unions, and interfaces
635
643
  use_dataloader_job = !inner_type.unwrap.kind.input?
636
644
  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)
645
+ response_list = GraphQLResultArray.new(result_name, current_type, owner_object, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
638
646
  set_result(selection_result, result_name, response_list, true, is_non_null)
639
647
  idx = nil
640
648
  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
649
+ begin
650
+ value.each do |inner_value|
651
+ idx ||= 0
652
+ this_idx = idx
653
+ idx += 1
654
+ if use_dataloader_job
655
+ @dataloader.append_job do
656
+ 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)
657
+ end
658
+ else
647
659
  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
660
  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
661
  end
652
- end
653
662
 
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
663
+ response_list
664
+ rescue NoMethodError => err
665
+ # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
666
+ if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
667
+ # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
668
+ raise ListResultFailedError.new(value: value, field: field, path: current_path)
669
+ else
670
+ # This was some other NoMethodError -- let it bubble to reveal the real error.
671
+ raise
672
+ end
673
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
674
+ ex_err
675
+ rescue StandardError => err
676
+ begin
677
+ query.handle_or_reraise(err)
678
+ rescue GraphQL::ExecutionError => ex_err
679
+ ex_err
680
+ end
663
681
  end
664
- rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
665
- ex_err
666
682
  rescue StandardError => err
667
683
  begin
668
684
  query.handle_or_reraise(err)
@@ -773,8 +789,10 @@ module GraphQL
773
789
  runtime_state.was_authorized_by_scope_items = was_authorized_by_scope_items
774
790
  # Wrap the execution of _this_ method with tracing,
775
791
  # but don't wrap the continuation below
792
+ result = nil
776
793
  inner_obj = begin
777
- if trace
794
+ result = if trace
795
+ @current_trace.begin_execute_field(field, owner_object, arguments, query)
778
796
  @current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
779
797
  schema.sync_lazy(lazy_obj)
780
798
  end
@@ -789,6 +807,10 @@ module GraphQL
789
807
  rescue GraphQL::ExecutionError => ex_err
790
808
  ex_err
791
809
  end
810
+ ensure
811
+ if trace
812
+ @current_trace.end_execute_field(field, owner_object, arguments, query, result)
813
+ end
792
814
  end
793
815
  yield(inner_obj, runtime_state)
794
816
  end
@@ -832,14 +854,19 @@ module GraphQL
832
854
  end
833
855
 
834
856
  def resolve_type(type, value)
857
+ @current_trace.begin_resolve_type(type, value, context)
835
858
  resolved_type, resolved_value = @current_trace.resolve_type(query: query, type: type, object: value) do
836
859
  query.resolve_type(type, value)
837
860
  end
861
+ @current_trace.end_resolve_type(type, value, context, resolved_type)
838
862
 
839
863
  if lazy?(resolved_type)
840
864
  GraphQL::Execution::Lazy.new do
865
+ @current_trace.begin_resolve_type(type, value, context)
841
866
  @current_trace.resolve_type_lazy(query: query, type: type, object: value) do
842
- schema.sync_lazy(resolved_type)
867
+ rt = schema.sync_lazy(resolved_type)
868
+ @current_trace.end_resolve_type(type, value, context, rt)
869
+ rt
843
870
  end
844
871
  end
845
872
  else
@@ -33,9 +33,12 @@ module GraphQL
33
33
  end
34
34
  end
35
35
 
36
+
36
37
  multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
37
38
  Fiber[:__graphql_current_multiplex] = multiplex
38
- multiplex.current_trace.execute_multiplex(multiplex: multiplex) do
39
+ trace = multiplex.current_trace
40
+ trace.begin_execute_multiplex(multiplex)
41
+ trace.execute_multiplex(multiplex: multiplex) do
39
42
  schema = multiplex.schema
40
43
  queries = multiplex.queries
41
44
  lazies_at_depth = Hash.new { |h, k| h[k] = [] }
@@ -44,7 +47,10 @@ module GraphQL
44
47
  multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
45
48
  end
46
49
 
50
+ trace.begin_analyze_multiplex(multiplex, multiplex_analyzers)
47
51
  schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
52
+ trace.end_analyze_multiplex(multiplex, multiplex_analyzers)
53
+
48
54
  begin
49
55
  # Since this is basically the batching context,
50
56
  # share it for a whole multiplex
@@ -148,6 +154,8 @@ module GraphQL
148
154
  }
149
155
  end
150
156
  end
157
+ ensure
158
+ trace&.end_execute_multiplex(multiplex)
151
159
  end
152
160
  end
153
161
 
@@ -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
@@ -161,7 +161,7 @@ module GraphQL
161
161
  expect_token(:VAR_SIGN)
162
162
  var_name = parse_name
163
163
  expect_token(:COLON)
164
- var_type = self.type
164
+ var_type = self.type || raise_parse_error("Missing type definition for variable: $#{var_name}")
165
165
  default_value = if at?(:EQUALS)
166
166
  advance_token
167
167
  value
data/lib/graphql/query.rb CHANGED
@@ -97,21 +97,22 @@ module GraphQL
97
97
  # @param root_value [Object] the object used to resolve fields on the root type
98
98
  # @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value)
99
99
  # @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
100
- # @param visibility_profile [Symbol]
100
+ # @param visibility_profile [Symbol] Another way to assign `context[:visibility_profile]`
101
101
  def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, visibility_profile: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil, use_visibility_profile: nil)
102
102
  # Even if `variables: nil` is passed, use an empty hash for simpler logic
103
103
  variables ||= {}
104
104
  @schema = schema
105
105
  @context = schema.context_class.new(query: self, values: context)
106
+ if visibility_profile
107
+ @context[:visibility_profile] ||= visibility_profile
108
+ end
106
109
 
107
110
  if use_visibility_profile.nil?
108
111
  use_visibility_profile = warden ? false : schema.use_visibility_profile?
109
112
  end
110
113
 
111
- @visibility_profile = visibility_profile
112
-
113
114
  if use_visibility_profile
114
- @visibility_profile = @schema.visibility.profile_for(@context, visibility_profile)
115
+ @visibility_profile = @schema.visibility.profile_for(@context)
115
116
  @warden = Schema::Warden::NullWarden.new(context: @context, schema: @schema)
116
117
  else
117
118
  @visibility_profile = nil
@@ -127,14 +128,6 @@ module GraphQL
127
128
  context_tracers = (context ? context.fetch(:tracers, []) : [])
128
129
  @tracers = schema.tracers + context_tracers
129
130
 
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
131
  if !context_tracers.empty? && !(schema.trace_class <= GraphQL::Tracing::CallLegacyTracers)
139
132
  raise ArgumentError, "context[:tracers] are not supported without `trace_with(GraphQL::Tracing::CallLegacyTracers)` in the schema configuration, please add it."
140
133
  end
@@ -448,6 +441,7 @@ module GraphQL
448
441
  @warden ||= @schema.warden_class.new(schema: @schema, context: @context)
449
442
  parse_error = nil
450
443
  @document ||= begin
444
+ current_trace.begin_parse(query_string)
451
445
  if query_string
452
446
  GraphQL.parse(query_string, trace: self.current_trace, max_tokens: @schema.max_query_string_tokens)
453
447
  end
@@ -455,6 +449,8 @@ module GraphQL
455
449
  parse_error = err
456
450
  @schema.parse_error(err, @context)
457
451
  nil
452
+ ensure
453
+ current_trace.end_parse(query_string)
458
454
  end
459
455
 
460
456
  @fragments = {}
@@ -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;", __FILE__, __LINE__)
245
+ end
226
246
  end
227
247
 
228
248
  enum_value_class(GraphQL::Schema::EnumValue)
@@ -13,6 +13,7 @@ module GraphQL
13
13
  include GraphQL::Schema::Member::Scoped
14
14
  include GraphQL::Schema::Member::HasAstNode
15
15
  include GraphQL::Schema::Member::HasUnresolvedTypeError
16
+ include GraphQL::Schema::Member::HasDataloader
16
17
  include GraphQL::Schema::Member::HasDirectives
17
18
  include GraphQL::Schema::Member::HasInterfaces
18
19
 
@@ -108,6 +108,7 @@ module GraphQL
108
108
  enum_value["name"],
109
109
  description: enum_value["description"],
110
110
  deprecation_reason: enum_value["deprecationReason"],
111
+ value_method: respond_to?(enum_value["name"].downcase) ? false : nil
111
112
  )
112
113
  end
113
114
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ module HasDataloader
7
+ # @return [GraphQL::Dataloader] The dataloader for the currently-running query
8
+ def dataloader
9
+ context.dataloader
10
+ end
11
+
12
+ # A shortcut method for loading a key from a source.
13
+ # Identical to `dataloader.with(source_class, *source_args).load(load_key)`
14
+ # @param source_class [Class<GraphQL::Dataloader::Source>]
15
+ # @param source_args [Array<Object>] Any extra parameters defined in `source_class`'s `initialize` method
16
+ # @param load_key [Object] The key to look up using `def fetch`
17
+ def dataload(source_class, *source_args, load_key)
18
+ dataloader.with(source_class, *source_args).load(load_key)
19
+ end
20
+
21
+ # Find an object with ActiveRecord via {Dataloader::ActiveRecordSource}.
22
+ # @param model [Class<ActiveRecord::Base>]
23
+ # @param find_by_value [Object] Usually an `id`, might be another value if `find_by:` is also provided
24
+ # @param find_by [Symbol, String] A column name to look the record up by. (Defaults to the model's primary key.)
25
+ # @return [ActiveRecord::Base, nil]
26
+ def dataload_record(model, find_by_value, find_by: nil)
27
+ source = if find_by
28
+ dataloader.with(Dataloader::ActiveRecordSource, model, find_by: find_by)
29
+ else
30
+ dataloader.with(Dataloader::ActiveRecordSource, model)
31
+ end
32
+
33
+ source.load(find_by_value)
34
+ end
35
+
36
+ # Look up an associated record using a Rails association.
37
+ # @param association_name [Symbol] A `belongs_to` or `has_one` association. (If a `has_many` association is named here, it will be selected without pagination.)
38
+ # @param record [ActiveRecord::Base] The object that the association belongs to.
39
+ # @param scope [ActiveRecord::Relation] A scope to look up the associated record in
40
+ # @return [ActiveRecord::Base, nil] The associated record, if there is one
41
+ # @example Looking up a belongs_to on the current object
42
+ # dataload_association(:parent) # Equivalent to `object.parent`, but dataloaded
43
+ # @example Looking up an associated record on some other object
44
+ # dataload_association(:post, comment) # Equivalent to `comment.post`, but dataloaded
45
+ def dataload_association(record = object, association_name, scope: nil)
46
+ source = if scope
47
+ dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name, scope)
48
+ else
49
+ dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name)
50
+ end
51
+ source.load(record)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -2,6 +2,7 @@
2
2
  require 'graphql/schema/member/base_dsl_methods'
3
3
  require 'graphql/schema/member/graphql_type_names'
4
4
  require 'graphql/schema/member/has_ast_node'
5
+ require 'graphql/schema/member/has_dataloader'
5
6
  require 'graphql/schema/member/has_directives'
6
7
  require 'graphql/schema/member/has_deprecation_reason'
7
8
  require 'graphql/schema/member/has_interfaces'
@@ -7,6 +7,7 @@ module GraphQL
7
7
  class Object < GraphQL::Schema::Member
8
8
  extend GraphQL::Schema::Member::HasFields
9
9
  extend GraphQL::Schema::Member::HasInterfaces
10
+ include Member::HasDataloader
10
11
 
11
12
  # Raised when an Object doesn't have any field defined and hasn't explicitly opted out of this requirement
12
13
  class FieldsAreRequiredError < GraphQL::Error
@@ -65,20 +66,28 @@ module GraphQL
65
66
  # @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
66
67
  # @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
67
68
  def authorized_new(object, context)
68
- maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do
69
- begin
70
- authorized?(object, context)
71
- rescue GraphQL::UnauthorizedError => err
72
- context.schema.unauthorized_object(err)
73
- rescue StandardError => err
74
- context.query.handle_or_reraise(err)
69
+ context.query.current_trace.begin_authorized(self, object, context)
70
+ begin
71
+ maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do
72
+ begin
73
+ authorized?(object, context)
74
+ rescue GraphQL::UnauthorizedError => err
75
+ context.schema.unauthorized_object(err)
76
+ rescue StandardError => err
77
+ context.query.handle_or_reraise(err)
78
+ end
75
79
  end
80
+ ensure
81
+ context.query.current_trace.end_authorized(self, object, context, maybe_lazy_auth_val)
76
82
  end
77
83
 
78
84
  auth_val = if context.schema.lazy?(maybe_lazy_auth_val)
79
85
  GraphQL::Execution::Lazy.new do
86
+ context.query.current_trace.begin_authorized(self, object, context)
80
87
  context.query.current_trace.authorized_lazy(query: context.query, type: self, object: object) do
81
- context.schema.sync_lazy(maybe_lazy_auth_val)
88
+ res = context.schema.sync_lazy(maybe_lazy_auth_val)
89
+ context.query.current_trace.end_authorized(self, object, context, res)
90
+ res
82
91
  end
83
92
  end
84
93
  else
@@ -22,11 +22,13 @@ 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
28
29
  extend Schema::Member::HasPath
29
30
  extend Schema::Member::HasDirectives
31
+ include Schema::Member::HasDataloader
30
32
 
31
33
  # @param object [Object] The application object that this field is being resolved on
32
34
  # @param context [GraphQL::Query::Context]
@@ -49,11 +51,6 @@ module GraphQL
49
51
  # @return [GraphQL::Query::Context]
50
52
  attr_reader :context
51
53
 
52
- # @return [GraphQL::Dataloader]
53
- def dataloader
54
- context.dataloader
55
- end
56
-
57
54
  # @return [GraphQL::Schema::Field]
58
55
  attr_reader :field
59
56
 
@@ -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)
@@ -18,7 +18,7 @@ module GraphQL
18
18
  if ctx.respond_to?(:types) && (types = ctx.types).is_a?(self)
19
19
  types
20
20
  else
21
- schema.visibility.profile_for(ctx, nil)
21
+ schema.visibility.profile_for(ctx)
22
22
  end
23
23
  end
24
24
 
@@ -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
@@ -319,9 +319,9 @@ module GraphQL
319
319
  case type.kind.name
320
320
  when "INTERFACE"
321
321
  pts = []
322
- @schema.visibility.all_interface_type_memberships[type].each do |itm|
323
- if @cached_visible[itm] && (ot = itm.object_type) && @cached_visible[ot] && referenced?(ot)
324
- pts << ot
322
+ @schema.visibility.all_interface_type_memberships[type].each do |(itm, impl_type)|
323
+ if @cached_visible[itm] && @cached_visible[impl_type] && referenced?(impl_type)
324
+ pts << impl_type
325
325
  end
326
326
  end
327
327
  pts