graphql 2.0.27 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  3. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  4. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  5. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  6. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  7. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  8. data/lib/generators/graphql/templates/base_field.erb +2 -0
  9. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  10. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  11. data/lib/generators/graphql/templates/base_object.erb +2 -0
  12. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  13. data/lib/generators/graphql/templates/base_union.erb +2 -0
  14. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  15. data/lib/generators/graphql/templates/loader.erb +2 -0
  16. data/lib/generators/graphql/templates/mutation.erb +2 -0
  17. data/lib/generators/graphql/templates/node_type.erb +2 -0
  18. data/lib/generators/graphql/templates/query_type.erb +2 -0
  19. data/lib/generators/graphql/templates/schema.erb +2 -0
  20. data/lib/graphql/analysis/ast/analyzer.rb +7 -0
  21. data/lib/graphql/analysis/ast/query_depth.rb +7 -2
  22. data/lib/graphql/analysis/ast/visitor.rb +2 -2
  23. data/lib/graphql/analysis/ast.rb +15 -11
  24. data/lib/graphql/dataloader/source.rb +7 -0
  25. data/lib/graphql/dataloader.rb +38 -10
  26. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +170 -0
  27. data/lib/graphql/execution/interpreter/runtime.rb +95 -254
  28. data/lib/graphql/execution/interpreter.rb +0 -6
  29. data/lib/graphql/execution/lookahead.rb +1 -1
  30. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  31. data/lib/graphql/introspection/entry_points.rb +2 -2
  32. data/lib/graphql/language/block_string.rb +28 -16
  33. data/lib/graphql/language/definition_slice.rb +1 -1
  34. data/lib/graphql/language/document_from_schema_definition.rb +36 -35
  35. data/lib/graphql/language/nodes.rb +2 -2
  36. data/lib/graphql/language/printer.rb +294 -145
  37. data/lib/graphql/language/sanitized_printer.rb +20 -22
  38. data/lib/graphql/language/static_visitor.rb +167 -0
  39. data/lib/graphql/language/visitor.rb +20 -81
  40. data/lib/graphql/language.rb +1 -0
  41. data/lib/graphql/pagination/connection.rb +23 -1
  42. data/lib/graphql/query/context/scoped_context.rb +101 -0
  43. data/lib/graphql/query/context.rb +32 -98
  44. data/lib/graphql/query.rb +2 -19
  45. data/lib/graphql/rake_task.rb +3 -12
  46. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  47. data/lib/graphql/schema/field/connection_extension.rb +1 -15
  48. data/lib/graphql/schema/field/scope_extension.rb +7 -1
  49. data/lib/graphql/schema/field.rb +7 -4
  50. data/lib/graphql/schema/has_single_input_argument.rb +156 -0
  51. data/lib/graphql/schema/introspection_system.rb +2 -0
  52. data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
  53. data/lib/graphql/schema/member/has_arguments.rb +19 -4
  54. data/lib/graphql/schema/member/has_fields.rb +4 -1
  55. data/lib/graphql/schema/member/has_interfaces.rb +21 -7
  56. data/lib/graphql/schema/member/scoped.rb +19 -0
  57. data/lib/graphql/schema/object.rb +8 -0
  58. data/lib/graphql/schema/printer.rb +8 -7
  59. data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
  60. data/lib/graphql/schema/resolver.rb +4 -0
  61. data/lib/graphql/schema/scalar.rb +3 -3
  62. data/lib/graphql/schema/subscription.rb +11 -4
  63. data/lib/graphql/schema/warden.rb +87 -89
  64. data/lib/graphql/schema.rb +125 -55
  65. data/lib/graphql/static_validation/all_rules.rb +1 -1
  66. data/lib/graphql/static_validation/base_visitor.rb +1 -1
  67. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  68. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  69. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  70. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  71. data/lib/graphql/static_validation/validation_context.rb +5 -5
  72. data/lib/graphql/static_validation.rb +0 -1
  73. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
  74. data/lib/graphql/subscriptions.rb +11 -6
  75. data/lib/graphql/tracing/appoptics_trace.rb +2 -2
  76. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  77. data/lib/graphql/types/relay/connection_behaviors.rb +19 -2
  78. data/lib/graphql/types/relay/edge_behaviors.rb +7 -0
  79. data/lib/graphql/version.rb +1 -1
  80. data/lib/graphql.rb +1 -2
  81. metadata +23 -20
  82. data/lib/graphql/filter.rb +0 -59
  83. data/lib/graphql/static_validation/type_stack.rb +0 -216
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ require "graphql/query/context/scoped_context"
3
+
2
4
  module GraphQL
3
5
  class Query
4
6
  # Expose some query-specific info to field resolve functions.
@@ -90,104 +92,6 @@ module GraphQL
90
92
  @scoped_context = ScopedContext.new(self)
91
93
  end
92
94
 
93
- class ScopedContext
94
- NO_PATH = GraphQL::EmptyObjects::EMPTY_ARRAY
95
- NO_CONTEXT = GraphQL::EmptyObjects::EMPTY_HASH
96
-
97
- def initialize(query_context)
98
- @query_context = query_context
99
- @scoped_contexts = nil
100
- @all_keys = nil
101
- end
102
-
103
- def merged_context
104
- if @scoped_contexts.nil?
105
- NO_CONTEXT
106
- else
107
- merged_ctx = {}
108
- each_present_path_ctx do |path_ctx|
109
- merged_ctx = path_ctx.merge(merged_ctx)
110
- end
111
- merged_ctx
112
- end
113
- end
114
-
115
- def merge!(hash)
116
- @all_keys ||= Set.new
117
- @all_keys.merge(hash.keys)
118
- ctx = @scoped_contexts ||= {}
119
- current_path.each do |path_part|
120
- ctx = ctx[path_part] ||= { parent: ctx }
121
- end
122
- this_scoped_ctx = ctx[:scoped_context] ||= {}
123
- this_scoped_ctx.merge!(hash)
124
- end
125
-
126
- def key?(key)
127
- if @all_keys && @all_keys.include?(key)
128
- each_present_path_ctx do |path_ctx|
129
- if path_ctx.key?(key)
130
- return true
131
- end
132
- end
133
- end
134
- false
135
- end
136
-
137
- def [](key)
138
- each_present_path_ctx do |path_ctx|
139
- if path_ctx.key?(key)
140
- return path_ctx[key]
141
- end
142
- end
143
- nil
144
- end
145
-
146
- def current_path
147
- @query_context.current_path || NO_PATH
148
- end
149
-
150
- def dig(key, *other_keys)
151
- each_present_path_ctx do |path_ctx|
152
- if path_ctx.key?(key)
153
- found_value = path_ctx[key]
154
- if other_keys.any?
155
- return found_value.dig(*other_keys)
156
- else
157
- return found_value
158
- end
159
- end
160
- end
161
- nil
162
- end
163
-
164
- private
165
-
166
- # Start at the current location,
167
- # but look up the tree for previously-assigned scoped values
168
- def each_present_path_ctx
169
- ctx = @scoped_contexts
170
- if ctx.nil?
171
- # no-op
172
- else
173
- current_path.each do |path_part|
174
- if ctx.key?(path_part)
175
- ctx = ctx[path_part]
176
- else
177
- break
178
- end
179
- end
180
-
181
- while ctx
182
- if (scoped_ctx = ctx[:scoped_context])
183
- yield(scoped_ctx)
184
- end
185
- ctx = ctx[:parent]
186
- end
187
- end
188
- end
189
- end
190
-
191
95
  # @return [Hash] A hash that will be added verbatim to the result hash, as `"extensions" => { ... }`
192
96
  def response_extensions
193
97
  namespace(:__query_result_extensions__)
@@ -345,6 +249,36 @@ module GraphQL
345
249
  scoped_merge!(key => value)
346
250
  nil
347
251
  end
252
+
253
+ # Use this when you need to do a scoped set _inside_ a lazy-loaded (or batch-loaded)
254
+ # block of code.
255
+ #
256
+ # @example using scoped context inside a promise
257
+ # scoped_ctx = context.scoped
258
+ # SomeBatchLoader.load(...).then do |thing|
259
+ # # use a scoped_ctx which was created _before_ dataloading:
260
+ # scoped_ctx.set!(:thing, thing)
261
+ # end
262
+ # @return [Context::Scoped]
263
+ def scoped
264
+ Scoped.new(@scoped_context, current_path)
265
+ end
266
+
267
+ class Scoped
268
+ def initialize(scoped_context, path)
269
+ @path = path
270
+ @scoped_context = scoped_context
271
+ end
272
+
273
+ def merge!(hash)
274
+ @scoped_context.merge!(hash, at: @path)
275
+ end
276
+
277
+ def set!(key, value)
278
+ @scoped_context.merge!({ key => value }, at: @path)
279
+ nil
280
+ end
281
+ end
348
282
  end
349
283
  end
350
284
  end
data/lib/graphql/query.rb CHANGED
@@ -95,15 +95,10 @@ module GraphQL
95
95
  # @param root_value [Object] the object used to resolve fields on the root type
96
96
  # @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value)
97
97
  # @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
98
- # @param except [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns truthy
99
- # @param only [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns false
100
- def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, except: nil, only: nil, warden: nil)
98
+ def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil)
101
99
  # Even if `variables: nil` is passed, use an empty hash for simpler logic
102
100
  variables ||= {}
103
101
  @schema = schema
104
- if only || except
105
- merge_filters(except: except, only: only)
106
- end
107
102
  @context = schema.context_class.new(query: self, object: root_value, values: context)
108
103
  @warden = warden
109
104
  @subscription_topic = subscription_topic
@@ -129,7 +124,6 @@ module GraphQL
129
124
  raise ArgumentError, "context[:tracers] are not supported without `trace_with(GraphQL::Tracing::CallLegacyTracers)` in the schema configuration, please add it."
130
125
  end
131
126
 
132
-
133
127
  @analysis_errors = []
134
128
  if variables.is_a?(String)
135
129
  raise ArgumentError, "Query variables should be a Hash, not a String. Try JSON.parse to prepare variables."
@@ -354,17 +348,6 @@ module GraphQL
354
348
  with_prepared_ast { @query }
355
349
  end
356
350
 
357
- # @return [void]
358
- def merge_filters(only: nil, except: nil)
359
- if @prepared_ast
360
- raise "Can't add filters after preparing the query"
361
- else
362
- @filter ||= @schema.default_filter
363
- @filter = @filter.merge(only: only, except: except)
364
- end
365
- nil
366
- end
367
-
368
351
  def subscription?
369
352
  with_prepared_ast { @subscription }
370
353
  end
@@ -400,7 +383,7 @@ module GraphQL
400
383
 
401
384
  def prepare_ast
402
385
  @prepared_ast = true
403
- @warden ||= @schema.warden_class.new(@filter, schema: @schema, context: @context)
386
+ @warden ||= @schema.warden_class.new(schema: @schema, context: @context)
404
387
  parse_error = nil
405
388
  @document ||= begin
406
389
  if query_string
@@ -9,8 +9,7 @@ module GraphQL
9
9
  # By default, schemas are looked up by name as constants using `schema_name:`.
10
10
  # You can provide a `load_schema` function to return your schema another way.
11
11
  #
12
- # `load_context:`, `only:` and `except:` are supported so that
13
- # you can keep an eye on how filters affect your schema.
12
+ # Use `load_context:` and `visible?` to dump schemas under certain visibility constraints.
14
13
  #
15
14
  # @example Dump a Schema to .graphql + .json files
16
15
  # require "graphql/rake_task"
@@ -36,8 +35,6 @@ module GraphQL
36
35
  schema_name: nil,
37
36
  load_schema: ->(task) { Object.const_get(task.schema_name) },
38
37
  load_context: ->(task) { {} },
39
- only: nil,
40
- except: nil,
41
38
  directory: ".",
42
39
  idl_outfile: "schema.graphql",
43
40
  json_outfile: "schema.json",
@@ -68,12 +65,6 @@ module GraphQL
68
65
  # @return [<#call(task)>] A callable for loading the query context
69
66
  attr_accessor :load_context
70
67
 
71
- # @return [<#call(member, ctx)>, nil] A filter for this task
72
- attr_accessor :only
73
-
74
- # @return [<#call(member, ctx)>, nil] A filter for this task
75
- attr_accessor :except
76
-
77
68
  # @return [String] target for IDL task
78
69
  attr_accessor :idl_outfile
79
70
 
@@ -117,10 +108,10 @@ module GraphQL
117
108
  include_is_repeatable: include_is_repeatable,
118
109
  include_specified_by_url: include_specified_by_url,
119
110
  include_schema_description: include_schema_description,
120
- only: @only, except: @except, context: context
111
+ context: context
121
112
  )
122
113
  when :to_definition
123
- schema.to_definition(only: @only, except: @except, context: context)
114
+ schema.to_definition(context: context)
124
115
  else
125
116
  raise ArgumentError, "Unexpected schema dump method: #{method_name.inspect}"
126
117
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Directive < GraphQL::Schema::Member
5
+ class SpecifiedBy < GraphQL::Schema::Directive
6
+ description "Exposes a URL that specifies the behavior of this scalar."
7
+ locations(GraphQL::Schema::Directive::SCALAR)
8
+ default_directive true
9
+
10
+ argument :url, String, description: "The URL that specifies the behavior of this scalar."
11
+ end
12
+ end
13
+ end
14
+ end
@@ -54,23 +54,9 @@ module GraphQL
54
54
  value.edge_class = custom_t
55
55
  end
56
56
  value
57
- elsif context.schema.new_connections?
57
+ else
58
58
  context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
59
59
  context.schema.connections.wrap(field, object.object, value, original_arguments, context)
60
- else
61
- if object.is_a?(GraphQL::Schema::Object)
62
- object = object.object
63
- end
64
- connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(value)
65
- connection_class.new(
66
- value,
67
- original_arguments,
68
- field: field,
69
- max_page_size: field.max_page_size,
70
- default_page_size: field.default_page_size,
71
- parent: object,
72
- context: context,
73
- )
74
60
  end
75
61
  end
76
62
  end
@@ -10,7 +10,13 @@ module GraphQL
10
10
  else
11
11
  ret_type = @field.type.unwrap
12
12
  if ret_type.respond_to?(:scope_items)
13
- ret_type.scope_items(value, context)
13
+ scoped_items = ret_type.scope_items(value, context)
14
+ if !scoped_items.equal?(value) && !ret_type.reauthorize_scoped_objects
15
+ current_runtime_state = Thread.current[:__graphql_runtime_info]
16
+ query_runtime_state = current_runtime_state[context.query]
17
+ query_runtime_state.was_authorized_by_scope_items = true
18
+ end
19
+ scoped_items
14
20
  else
15
21
  value
16
22
  end
@@ -218,8 +218,8 @@ module GraphQL
218
218
  # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
219
219
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
220
220
  # @param validates [Array<Hash>] Configurations for validating this field
221
- # @fallback_value [Object] A fallback value if the method is not defined
222
- def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, &definition_block)
221
+ # @param fallback_value [Object] A fallback value if the method is not defined
222
+ def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
223
223
  if name.nil?
224
224
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
225
225
  end
@@ -267,6 +267,7 @@ module GraphQL
267
267
  @method_sym = method_name.to_sym
268
268
  @resolver_method = (resolver_method || name_s).to_sym
269
269
  @complexity = complexity
270
+ @dynamic_introspection = dynamic_introspection
270
271
  @return_type_expr = type
271
272
  @return_type_null = if !null.nil?
272
273
  null
@@ -351,6 +352,8 @@ module GraphQL
351
352
  @call_after_define = true
352
353
  end
353
354
 
355
+ attr_accessor :dynamic_introspection
356
+
354
357
  # If true, subscription updates with this field can be shared between viewers
355
358
  # @return [Boolean, nil]
356
359
  # @see GraphQL::Subscriptions::BroadcastAnalyzer
@@ -659,7 +662,7 @@ module GraphQL
659
662
  method_to_call = nil
660
663
  method_args = nil
661
664
 
662
- Schema::Validator.validate!(validators, application_object, query_ctx, args)
665
+ @own_validators && Schema::Validator.validate!(validators, application_object, query_ctx, args)
663
666
 
664
667
  query_ctx.query.after_lazy(self.authorized?(application_object, args, query_ctx)) do |is_authorized|
665
668
  if is_authorized
@@ -701,7 +704,7 @@ module GraphQL
701
704
  inner_object.dig(*@dig_keys)
702
705
  elsif inner_object.key?(@method_sym)
703
706
  inner_object[@method_sym]
704
- elsif inner_object.key?(@method_str)
707
+ elsif inner_object.key?(@method_str) || !inner_object.default_proc.nil?
705
708
  inner_object[@method_str]
706
709
  elsif @fallback_value != NOT_CONFIGURED
707
710
  @fallback_value
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ module HasSingleInputArgument
6
+ def resolve_with_support(**inputs)
7
+ if inputs[:input].is_a?(InputObject)
8
+ input = inputs[:input].to_kwargs
9
+ else
10
+ input = inputs[:input]
11
+ end
12
+
13
+ new_extras = field ? field.extras : []
14
+ all_extras = self.class.extras + new_extras
15
+
16
+ # Transfer these from the top-level hash to the
17
+ # shortcutted `input:` object
18
+ all_extras.each do |ext|
19
+ # It's possible that the `extra` was not passed along by this point,
20
+ # don't re-add it if it wasn't given here.
21
+ if inputs.key?(ext)
22
+ input[ext] = inputs[ext]
23
+ end
24
+ end
25
+
26
+ if input
27
+ # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
28
+ input_kwargs = input.to_h
29
+ else
30
+ # Relay Classic Mutations with no `argument`s
31
+ # don't require `input:`
32
+ input_kwargs = {}
33
+ end
34
+
35
+ if input_kwargs.any?
36
+ super(**input_kwargs)
37
+ else
38
+ super()
39
+ end
40
+ end
41
+
42
+ def self.included(base)
43
+ base.extend(ClassMethods)
44
+ end
45
+
46
+ module ClassMethods
47
+ def dummy
48
+ @dummy ||= begin
49
+ d = Class.new(GraphQL::Schema::Resolver)
50
+ d.argument_class(self.argument_class)
51
+ # TODO make this lazier?
52
+ d.argument(:input, input_type, description: "Parameters for #{self.graphql_name}")
53
+ d
54
+ end
55
+ end
56
+
57
+ def field_arguments(context = GraphQL::Query::NullContext)
58
+ dummy.arguments(context)
59
+ end
60
+
61
+ def get_field_argument(name, context = GraphQL::Query::NullContext)
62
+ dummy.get_argument(name, context)
63
+ end
64
+
65
+ def own_field_arguments
66
+ dummy.own_arguments
67
+ end
68
+
69
+ def any_field_arguments?
70
+ dummy.any_arguments?
71
+ end
72
+
73
+ def all_field_argument_definitions
74
+ dummy.all_argument_definitions
75
+ end
76
+
77
+ # Also apply this argument to the input type:
78
+ def argument(*args, own_argument: false, **kwargs, &block)
79
+ it = input_type # make sure any inherited arguments are already added to it
80
+ arg = super(*args, **kwargs, &block)
81
+
82
+ # This definition might be overriding something inherited;
83
+ # if it is, remove the inherited definition so it's not confused at runtime as having multiple definitions
84
+ prev_args = it.own_arguments[arg.graphql_name]
85
+ case prev_args
86
+ when GraphQL::Schema::Argument
87
+ if prev_args.owner != self
88
+ it.own_arguments.delete(arg.graphql_name)
89
+ end
90
+ when Array
91
+ prev_args.reject! { |a| a.owner != self }
92
+ if prev_args.empty?
93
+ it.own_arguments.delete(arg.graphql_name)
94
+ end
95
+ end
96
+
97
+ it.add_argument(arg)
98
+ arg
99
+ end
100
+
101
+ # The base class for generated input object types
102
+ # @param new_class [Class] The base class to use for generating input object definitions
103
+ # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject})
104
+ def input_object_class(new_class = nil)
105
+ if new_class
106
+ @input_object_class = new_class
107
+ end
108
+ @input_object_class || (superclass.respond_to?(:input_object_class) ? superclass.input_object_class : GraphQL::Schema::InputObject)
109
+ end
110
+
111
+ # @param new_input_type [Class, nil] If provided, it configures this mutation to accept `new_input_type` instead of generating an input type
112
+ # @return [Class] The generated {Schema::InputObject} class for this mutation's `input`
113
+ def input_type(new_input_type = nil)
114
+ if new_input_type
115
+ @input_type = new_input_type
116
+ end
117
+ @input_type ||= generate_input_type
118
+ end
119
+
120
+ private
121
+
122
+ # Generate the input type for the `input:` argument
123
+ # To customize how input objects are generated, override this method
124
+ # @return [Class] a subclass of {.input_object_class}
125
+ def generate_input_type
126
+ mutation_args = all_argument_definitions
127
+ mutation_class = self
128
+ Class.new(input_object_class) do
129
+ class << self
130
+ def default_graphql_name
131
+ "#{self.mutation.graphql_name}Input"
132
+ end
133
+
134
+ def description(new_desc = nil)
135
+ super || "Autogenerated input type of #{self.mutation.graphql_name}"
136
+ end
137
+ end
138
+ mutation(mutation_class)
139
+ # these might be inherited:
140
+ mutation_args.each do |arg|
141
+ add_argument(arg)
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def authorize_arguments(args, values)
150
+ # remove the `input` wrapper to match values
151
+ input_args = args["input"].type.unwrap.arguments(context)
152
+ super(input_args, values)
153
+ end
154
+ end
155
+ end
156
+ end
@@ -39,7 +39,9 @@ module GraphQL
39
39
  entry_point_fields.delete('__type') if schema.disable_type_introspection_entry_point?
40
40
  entry_point_fields
41
41
  end
42
+ @entry_point_fields.each { |k, v| v.dynamic_introspection = true }
42
43
  @dynamic_fields = get_fields_from_class(class_sym: :DynamicFields)
44
+ @dynamic_fields.each { |k, v| v.dynamic_introspection = true }
43
45
  end
44
46
 
45
47
  def entry_points
@@ -102,7 +102,8 @@ module GraphQL
102
102
  def default_graphql_name
103
103
  @default_graphql_name ||= begin
104
104
  raise GraphQL::RequiredImplementationMissingError, 'Anonymous class should declare a `graphql_name`' if name.nil?
105
- -name.split("::").last.sub(/Type\Z/, "")
105
+ g_name = -name.split("::").last
106
+ g_name.end_with?("Type") ? g_name.sub(/Type\Z/, "") : g_name
106
107
  end
107
108
  end
108
109
 
@@ -122,6 +122,10 @@ module GraphQL
122
122
  own_arguments_that_apply || own_arguments
123
123
  end
124
124
 
125
+ def any_arguments?
126
+ own_arguments.any?
127
+ end
128
+
125
129
  module ClassConfigured
126
130
  def inherited(child_class)
127
131
  super
@@ -145,6 +149,10 @@ module GraphQL
145
149
  end
146
150
  end
147
151
 
152
+ def any_arguments?
153
+ super || superclass.any_arguments?
154
+ end
155
+
148
156
  def all_argument_definitions
149
157
  all_defns = {}
150
158
  ancestors.reverse_each do |ancestor|
@@ -175,7 +183,7 @@ module GraphQL
175
183
  module FieldConfigured
176
184
  def arguments(context = GraphQL::Query::NullContext)
177
185
  own_arguments = super
178
- if defined?(@resolver_class) && @resolver_class
186
+ if @resolver_class
179
187
  inherited_arguments = @resolver_class.field_arguments(context)
180
188
  if own_arguments.any?
181
189
  if inherited_arguments.any?
@@ -191,8 +199,12 @@ module GraphQL
191
199
  end
192
200
  end
193
201
 
202
+ def any_arguments?
203
+ super || (@resolver_class && @resolver_class.any_field_arguments?)
204
+ end
205
+
194
206
  def all_argument_definitions
195
- if defined?(@resolver_class) && @resolver_class
207
+ if @resolver_class
196
208
  all_defns = {}
197
209
  @resolver_class.all_field_argument_definitions.each do |arg_defn|
198
210
  key = arg_defn.graphql_name
@@ -380,8 +392,11 @@ module GraphQL
380
392
  application_object_type = resolve_type_result
381
393
  # application_object is already assigned
382
394
  end
383
- possible_object_types = context.warden.possible_types(argument.loads)
384
- if !possible_object_types.include?(application_object_type)
395
+
396
+ if !(
397
+ context.warden.possible_types(argument.loads).include?(application_object_type) ||
398
+ context.warden.loadable?(argument.loads, context)
399
+ )
385
400
  err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
386
401
  load_application_object_failed(err)
387
402
  else
@@ -133,13 +133,16 @@ module GraphQL
133
133
  def get_field(field_name, context = GraphQL::Query::NullContext)
134
134
  # Objects need to check that the interface implementation is visible, too
135
135
  warden = Warden.from_context(context)
136
- for ancestor in ancestors
136
+ ancs = ancestors
137
+ i = 0
138
+ while (ancestor = ancs[i])
137
139
  if ancestor.respond_to?(:own_fields) &&
138
140
  visible_interface_implementation?(ancestor, context, warden) &&
139
141
  (f_entry = ancestor.own_fields[field_name]) &&
140
142
  (f = Warden.visible_entry?(:visible_field?, f_entry, context, warden))
141
143
  return f
142
144
  end
145
+ i += 1
143
146
  end
144
147
  nil
145
148
  end
@@ -72,9 +72,18 @@ module GraphQL
72
72
  module InheritedInterfaces
73
73
  def interfaces(context = GraphQL::Query::NullContext)
74
74
  visible_interfaces = super
75
- visible_interfaces.concat(superclass.interfaces(context))
76
- visible_interfaces.uniq!
77
- visible_interfaces
75
+ inherited_interfaces = superclass.interfaces(context)
76
+ if visible_interfaces.any?
77
+ if inherited_interfaces.any?
78
+ visible_interfaces.concat(inherited_interfaces)
79
+ visible_interfaces.uniq!
80
+ end
81
+ visible_interfaces
82
+ elsif inherited_interfaces.any?
83
+ inherited_interfaces
84
+ else
85
+ EmptyObjects::EMPTY_ARRAY
86
+ end
78
87
  end
79
88
 
80
89
  def interface_type_memberships
@@ -92,23 +101,28 @@ module GraphQL
92
101
  # param context [Query::Context] If omitted, skip filtering.
93
102
  def interfaces(context = GraphQL::Query::NullContext)
94
103
  warden = Warden.from_context(context)
95
- visible_interfaces = []
104
+ visible_interfaces = nil
96
105
  own_interface_type_memberships.each do |type_membership|
97
106
  case type_membership
98
107
  when Schema::TypeMembership
99
108
  if warden.visible_type_membership?(type_membership, context)
109
+ visible_interfaces ||= []
100
110
  visible_interfaces << type_membership.abstract_type
101
111
  end
102
112
  when String, Schema::LateBoundType
103
113
  # During initialization, `type_memberships` can hold late-bound types
114
+ visible_interfaces ||= []
104
115
  visible_interfaces << type_membership
105
116
  else
106
117
  raise "Invariant: Unexpected type_membership #{type_membership.class}: #{type_membership.inspect}"
107
118
  end
108
119
  end
109
- visible_interfaces.uniq!
110
-
111
- visible_interfaces
120
+ if visible_interfaces
121
+ visible_interfaces.uniq!
122
+ visible_interfaces
123
+ else
124
+ EmptyObjects::EMPTY_ARRAY
125
+ end
112
126
  end
113
127
 
114
128
  private
@@ -15,6 +15,25 @@ module GraphQL
15
15
  def scope_items(items, context)
16
16
  items
17
17
  end
18
+
19
+ def reauthorize_scoped_objects(new_value = nil)
20
+ if new_value.nil?
21
+ if @reauthorize_scoped_objects != nil
22
+ @reauthorize_scoped_objects
23
+ else
24
+ find_inherited_value(:reauthorize_scoped_objects, nil)
25
+ end
26
+ else
27
+ @reauthorize_scoped_objects = new_value
28
+ end
29
+ end
30
+
31
+ def inherited(subclass)
32
+ super
33
+ subclass.class_eval do
34
+ @reauthorize_scoped_objects = nil
35
+ end
36
+ end
18
37
  end
19
38
  end
20
39
  end