graphql 2.3.9 → 2.3.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +46 -0
  3. data/lib/graphql/current.rb +52 -0
  4. data/lib/graphql/dataloader/source.rb +5 -2
  5. data/lib/graphql/dataloader.rb +4 -1
  6. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  7. data/lib/graphql/execution/interpreter/runtime.rb +2 -8
  8. data/lib/graphql/execution/interpreter.rb +2 -0
  9. data/lib/graphql/introspection/entry_points.rb +2 -2
  10. data/lib/graphql/introspection/schema_type.rb +1 -1
  11. data/lib/graphql/language/nodes.rb +2 -2
  12. data/lib/graphql/language/parser.rb +9 -1
  13. data/lib/graphql/query/context.rb +4 -2
  14. data/lib/graphql/query/null_context.rb +0 -4
  15. data/lib/graphql/query.rb +2 -6
  16. data/lib/graphql/schema/build_from_definition.rb +8 -1
  17. data/lib/graphql/schema/directive/flagged.rb +1 -1
  18. data/lib/graphql/schema/enum.rb +5 -1
  19. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  20. data/lib/graphql/schema/interface.rb +20 -4
  21. data/lib/graphql/schema/introspection_system.rb +3 -2
  22. data/lib/graphql/schema/member/has_arguments.rb +7 -3
  23. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  24. data/lib/graphql/schema/subset.rb +215 -102
  25. data/lib/graphql/schema/types_migration.rb +185 -0
  26. data/lib/graphql/schema/warden.rb +4 -7
  27. data/lib/graphql/schema.rb +30 -22
  28. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  29. data/lib/graphql/testing/helpers.rb +6 -3
  30. data/lib/graphql/version.rb +1 -1
  31. data/lib/graphql.rb +1 -0
  32. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa940f82a3f79bd899de908905d6656c5a4b60b6174f70380ed42d3643992085
4
- data.tar.gz: 3c20f6255693b62acc10cb4d9069c6433d7548a31f9ae2a8c4554f6d91646dc9
3
+ metadata.gz: 7fd32c95924aac8818df81050ba408953617ee69edf0b1e20a16b4927e198230
4
+ data.tar.gz: a938a1fbde00c6ba934e63f40248b890bc7cbb6d0374d51dd138a88acbc05465
5
5
  SHA512:
6
- metadata.gz: e92413d9e9a3e5fc507ef4522167aa2268f6c5645847f6c5f7b97bfad1a94cd043048d2a7a7cf7c3df6ff748e76266a63101c96c25bf0fd3601a1bd8f5e07311
7
- data.tar.gz: 58ca270e89fa01dd0bac02cfd559250b2088979bd6a7a57c2c722196d1bf7e824c35f29b3d1e98ffe390157d2bebcedbc3e33cddec21cb55e0abe4644d3275dc
6
+ metadata.gz: 595f05e3c28c15e05e65543d48493e101d92ac1238deb0ec9682b2b853293041438652ccdab3dbb9c4242d9b93b29e10da82851f7985ac286916a77697850e0d
7
+ data.tar.gz: 90c3315b9d03b6c8eb4cee5e44578ec6b968cfe6de5feeaf160ecdbce2428a6a774edff7e06d7e2afaae4a5e33faeb809b629fae486ae1737bcf8bd9d298a0b1
@@ -45,6 +45,13 @@ module Graphql
45
45
  # post "/graphql", to: "graphql#execute"
46
46
  # ```
47
47
  #
48
+ # Add ActiveRecord::QueryLogs metadata:
49
+ # ```ruby
50
+ # current_graphql_operation: -> { GraphQL::Current.operation_name },
51
+ # current_graphql_field: -> { GraphQL::Current.field&.path },
52
+ # current_dataloader_source: -> { GraphQL::Current.dataloader_source_class },
53
+ # ```
54
+ #
48
55
  # Accept a `--batch` option which adds `GraphQL::Batch` setup.
49
56
  #
50
57
  # Use `--skip-graphiql` to skip `graphiql-rails` installation.
@@ -92,6 +99,11 @@ module Graphql
92
99
  default: false,
93
100
  desc: "Use GraphQL Playground over Graphiql as IDE"
94
101
 
102
+ class_option :skip_query_logs,
103
+ type: :boolean,
104
+ default: false,
105
+ desc: "Skip ActiveRecord::QueryLogs hooks in config/application.rb"
106
+
95
107
  # These two options are taken from Rails' own generators'
96
108
  class_option :api,
97
109
  type: :boolean,
@@ -180,6 +192,40 @@ RUBY
180
192
  install_relay
181
193
  end
182
194
 
195
+ if !options[:skip_query_logs]
196
+ config_file = "config/application.rb"
197
+ current_app_rb = File.read(Rails.root.join(config_file))
198
+ existing_log_tags_pattern = /config.active_record.query_log_tags = \[\n?(\s*:[a-z_]+,?\s*\n?|\s*#[^\]]*\n)*/m
199
+ existing_log_tags = existing_log_tags_pattern.match(current_app_rb)
200
+ if existing_log_tags && behavior == :invoke
201
+ code = <<-RUBY
202
+ # GraphQL-Ruby query log tags:
203
+ current_graphql_operation: -> { GraphQL::Current.operation_name },
204
+ current_graphql_field: -> { GraphQL::Current.field&.path },
205
+ current_dataloader_source: -> { GraphQL::Current.dataloader_source_class },
206
+ RUBY
207
+ if !existing_log_tags.to_s.end_with?(",")
208
+ code = ",\n#{code} "
209
+ end
210
+ # Try to insert this code _after_ any plain symbol entries in the array of query log tags:
211
+ after_code = existing_log_tags_pattern
212
+ else
213
+ code = <<-RUBY
214
+ config.active_record.query_log_tags_enabled = true
215
+ config.active_record.query_log_tags = [
216
+ # Rails query log tags:
217
+ :application, :controller, :action, :job,
218
+ # GraphQL-Ruby query log tags:
219
+ current_graphql_operation: -> { GraphQL::Current.operation_name },
220
+ current_graphql_field: -> { GraphQL::Current.field&.path },
221
+ current_dataloader_source: -> { GraphQL::Current.dataloader_source_class },
222
+ ]
223
+ RUBY
224
+ after_code = "class Application < Rails::Application\n"
225
+ end
226
+ insert_into_file(config_file, code, after: after_code)
227
+ end
228
+
183
229
  if gemfile_modified?
184
230
  say "Gemfile has been modified, make sure you `bundle install`"
185
231
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ # This module exposes Fiber-level runtime information.
5
+ #
6
+ # It won't work across unrelated fibers, although it will work in child Fibers.
7
+ #
8
+ # @example Setting Up ActiveRecord::QueryLogs
9
+ #
10
+ # config.active_record.query_log_tags = [
11
+ # :namespaced_controller,
12
+ # :action,
13
+ # :job,
14
+ # # ...
15
+ # {
16
+ # # GraphQL runtime info:
17
+ # current_graphql_operation: -> { GraphQL::Current.operation_name },
18
+ # current_graphql_field: -> { GraphQL::Current.field&.path },
19
+ # current_dataloader_source: -> { GraphQL::Current.dataloader_source_class },
20
+ # # ...
21
+ # },
22
+ # ]
23
+ #
24
+ module Current
25
+ # @return [String, nil] Comma-joined operation names for the currently-running {Multiplex}. `nil` if all operations are anonymous.
26
+ def self.operation_name
27
+ if (m = Fiber[:__graphql_current_multiplex])
28
+ m.context[:__graphql_current_operation_name] ||= begin
29
+ names = m.queries.map { |q| q.selected_operation_name }
30
+ if names.all?(&:nil?)
31
+ nil
32
+ else
33
+ names.join(",")
34
+ end
35
+ end
36
+ else
37
+ nil
38
+ end
39
+ end
40
+
41
+ # @see GraphQL::Field#path for a string identifying this field
42
+ # @return [GraphQL::Field, nil] The currently-running field, if there is one.
43
+ def self.field
44
+ Thread.current[:__graphql_runtime_info]&.values&.first&.current_field
45
+ end
46
+
47
+ # @return [Class, nil] The currently-running {Dataloader::Source} class, if there is one.
48
+ def self.dataloader_source_class
49
+ Fiber[:__graphql_current_dataloader_source]&.class
50
+ end
51
+ end
52
+ end
@@ -186,8 +186,11 @@ This key should have been loaded already. This is a bug in GraphQL::Dataloader,
186
186
  ERR
187
187
  end
188
188
  result = @results[key]
189
-
190
- raise result if result.class <= StandardError
189
+ if result.is_a?(StandardError)
190
+ # Dup it because the rescuer may modify it.
191
+ # (This happens for GraphQL::ExecutionErrors, at least)
192
+ raise result.dup
193
+ end
191
194
 
192
195
  result
193
196
  end
@@ -271,7 +271,10 @@ module GraphQL
271
271
 
272
272
  if pending_sources
273
273
  spawn_fiber do
274
- pending_sources.each(&:run_pending_keys)
274
+ pending_sources.each do |source|
275
+ Fiber[:__graphql_current_dataloader_source] = source
276
+ source.run_pending_keys
277
+ end
275
278
  end
276
279
  end
277
280
  end
@@ -8,22 +8,17 @@ module GraphQL
8
8
  @query = query
9
9
  @dataloader = query.context.dataloader
10
10
  @storage = Hash.new do |h, argument_owner|
11
- args_by_parent = if argument_owner.arguments_statically_coercible?
11
+ h[argument_owner] = if argument_owner.arguments_statically_coercible?
12
12
  shared_values_cache = {}
13
13
  Hash.new do |h2, ignored_parent_object|
14
14
  h2[ignored_parent_object] = shared_values_cache
15
- end
15
+ end.compare_by_identity
16
16
  else
17
17
  Hash.new do |h2, parent_object|
18
- args_by_node = {}
19
- args_by_node.compare_by_identity
20
- h2[parent_object] = args_by_node
21
- end
18
+ h2[parent_object] = {}.compare_by_identity
19
+ end.compare_by_identity
22
20
  end
23
- args_by_parent.compare_by_identity
24
- h[argument_owner] = args_by_parent
25
- end
26
- @storage.compare_by_identity
21
+ end.compare_by_identity
27
22
  end
28
23
 
29
24
  def fetch(ast_node, argument_owner, parent_object)
@@ -53,8 +53,7 @@ module GraphQL
53
53
  end
54
54
  end
55
55
  # { Class => Boolean }
56
- @lazy_cache = {}
57
- @lazy_cache.compare_by_identity
56
+ @lazy_cache = {}.compare_by_identity
58
57
  end
59
58
 
60
59
  def final_result
@@ -727,12 +726,7 @@ module GraphQL
727
726
  end
728
727
 
729
728
  def get_current_runtime_state
730
- current_state = Thread.current[:__graphql_runtime_info] ||= begin
731
- per_query_state = {}
732
- per_query_state.compare_by_identity
733
- per_query_state
734
- end
735
-
729
+ current_state = Thread.current[:__graphql_runtime_info] ||= {}.compare_by_identity
736
730
  current_state[@query] ||= CurrentState.new
737
731
  end
738
732
 
@@ -34,6 +34,7 @@ module GraphQL
34
34
  end
35
35
 
36
36
  multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
37
+ Fiber[:__graphql_current_multiplex] = multiplex
37
38
  multiplex.current_trace.execute_multiplex(multiplex: multiplex) do
38
39
  schema = multiplex.schema
39
40
  queries = multiplex.queries
@@ -136,6 +137,7 @@ module GraphQL
136
137
  queries.map { |q| q.result_values ||= {} }
137
138
  raise
138
139
  ensure
140
+ Fiber[:__graphql_current_multiplex] = nil
139
141
  queries.map { |query|
140
142
  runtime = query.context.namespace(:interpreter_runtime)[:runtime]
141
143
  if runtime
@@ -15,8 +15,8 @@ module GraphQL
15
15
  end
16
16
 
17
17
  def __type(name:)
18
- if context.types.reachable_type?(name)
19
- context.types.type(name)
18
+ if context.types.reachable_type?(name) && (type = context.types.type(name))
19
+ type
20
20
  elsif (type = context.schema.extra_types.find { |t| t.graphql_name == name })
21
21
  type
22
22
  else
@@ -39,7 +39,7 @@ module GraphQL
39
39
  end
40
40
 
41
41
  def directives
42
- @context.types.directives
42
+ @context.types.directives.sort_by(&:graphql_name)
43
43
  end
44
44
  end
45
45
  end
@@ -34,11 +34,11 @@ module GraphQL
34
34
  attr_reader :filename
35
35
 
36
36
  def line
37
- @line ||= @source.line_at(@pos)
37
+ @line ||= @source&.line_at(@pos)
38
38
  end
39
39
 
40
40
  def col
41
- @col ||= @source.column_at(@pos)
41
+ @col ||= @source&.column_at(@pos)
42
42
  end
43
43
 
44
44
  def definition_line
@@ -141,7 +141,12 @@ module GraphQL
141
141
  parse_operation_type
142
142
  end
143
143
 
144
- op_name = at?(:IDENTIFIER) ? parse_name : nil
144
+ op_name = case token_name
145
+ when :LPAREN, :LCURLY, :DIR_SIGN
146
+ nil
147
+ else
148
+ parse_name
149
+ end
145
150
 
146
151
  variable_definitions = if at?(:LPAREN)
147
152
  expect_token(:LPAREN)
@@ -398,6 +403,9 @@ module GraphQL
398
403
  def parse_union_members
399
404
  if at?(:EQUALS)
400
405
  expect_token :EQUALS
406
+ if at?(:PIPE)
407
+ advance_token
408
+ end
401
409
  list = [parse_type_name]
402
410
  while at?(:PIPE)
403
411
  advance_token
@@ -82,12 +82,14 @@ module GraphQL
82
82
  @provided_values[key] = value
83
83
  end
84
84
 
85
- def_delegators :@query, :trace, :interpreter?
85
+ def_delegators :@query, :trace
86
86
 
87
87
  def types
88
- @query.types
88
+ @types ||= @query.types
89
89
  end
90
90
 
91
+ attr_writer :types
92
+
91
93
  RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path])
92
94
  # @!method []=(key, value)
93
95
  # Reassign `key` to the hash passed to {Schema#execute} as `context:`
@@ -27,10 +27,6 @@ module GraphQL
27
27
  @warden = Schema::Warden::NullWarden.new(context: self, schema: @schema)
28
28
  end
29
29
 
30
- def interpreter?
31
- true
32
- end
33
-
34
30
  def types
35
31
  @types ||= GraphQL::Schema::Warden::SchemaSubset.new(@warden)
36
32
  end
data/lib/graphql/query.rb CHANGED
@@ -106,8 +106,8 @@ module GraphQL
106
106
  end
107
107
 
108
108
  if use_schema_subset
109
- @schema_subset = @schema.subset_class.new(self)
110
- @warden = Schema::Warden::NullWarden.new(context: self, schema: @schema)
109
+ @schema_subset = @schema.subset_class.new(context: @context, schema: @schema)
110
+ @warden = Schema::Warden::NullWarden.new(context: @context, schema: @schema)
111
111
  else
112
112
  @schema_subset = nil
113
113
  @warden = warden
@@ -187,10 +187,6 @@ module GraphQL
187
187
  @query_string ||= (document ? document.to_query_string : nil)
188
188
  end
189
189
 
190
- def interpreter?
191
- true
192
- end
193
-
194
190
  attr_accessor :multiplex
195
191
 
196
192
  # @return [GraphQL::Tracing::Trace]
@@ -127,11 +127,12 @@ module GraphQL
127
127
  builder = self
128
128
 
129
129
  found_types = types.values
130
+ object_types = found_types.select { |t| t.respond_to?(:kind) && t.kind.object? }
130
131
  schema_class = Class.new(schema_superclass) do
131
132
  begin
132
133
  # Add these first so that there's some chance of resolving late-bound types
133
134
  add_type_and_traverse(found_types, root: false)
134
- orphan_types(found_types.select { |t| t.respond_to?(:kind) && t.kind.object? })
135
+ orphan_types(object_types)
135
136
  query query_root_type
136
137
  mutation mutation_root_type
137
138
  subscription subscription_root_type
@@ -141,6 +142,12 @@ module GraphQL
141
142
  raise InvalidDocumentError, "Type \"#{type_name}\" not found in document.", err_backtrace
142
143
  end
143
144
 
145
+ object_types.each do |t|
146
+ t.interfaces.each do |int_t|
147
+ int_t.orphan_types(t)
148
+ end
149
+ end
150
+
144
151
  if default_resolve.respond_to?(:resolve_type)
145
152
  def self.resolve_type(*args)
146
153
  self.definition_default_resolve.resolve_type(*args)
@@ -7,7 +7,7 @@ module GraphQL
7
7
  # In this case, the server hides types and fields _entirely_, unless the current context has certain `:flags` present.
8
8
  class Flagged < GraphQL::Schema::Directive
9
9
  def initialize(target, **options)
10
- if target.is_a?(Module) && !target.ancestors.include?(VisibleByFlag)
10
+ if target.is_a?(Module)
11
11
  # This is type class of some kind, `include` will put this module
12
12
  # in between the type class itself and its super class, so `super` will work fine
13
13
  target.include(VisibleByFlag)
@@ -166,7 +166,11 @@ module GraphQL
166
166
  end
167
167
 
168
168
  def inherited(child_class)
169
- child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError))
169
+ if child_class.name
170
+ # Don't assign a custom error class to anonymous classes
171
+ # because they would end up with names like `#<Class0x1234>::UnresolvedValueError` which messes up bug trackers
172
+ child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError))
173
+ end
170
174
  super
171
175
  end
172
176
 
@@ -50,7 +50,7 @@ module GraphQL
50
50
  if field.has_default_page_size? && !value.has_default_page_size_override?
51
51
  value.default_page_size = field.default_page_size
52
52
  end
53
- if context.schema.new_connections? && (custom_t = context.schema.connections.edge_class_for_field(@field))
53
+ if (custom_t = context.schema.connections.edge_class_for_field(@field))
54
54
  value.edge_class = custom_t
55
55
  end
56
56
  value
@@ -82,13 +82,29 @@ module GraphQL
82
82
  super
83
83
  end
84
84
 
85
+ # Register other Interface or Object types as implementers of this Interface.
86
+ #
87
+ # When those Interfaces or Objects aren't used as the return values of fields,
88
+ # they may have to be registered using this method so that GraphQL-Ruby can find them.
89
+ # @param types [Class, Module]
90
+ # @return [Array<Module, Class>] Implementers of this interface, if they're registered
85
91
  def orphan_types(*types)
86
92
  if types.any?
87
- @orphan_types = types
93
+ @orphan_types ||= []
94
+ @orphan_types.concat(types)
88
95
  else
89
- all_orphan_types = @orphan_types || []
90
- all_orphan_types += super if defined?(super)
91
- all_orphan_types.uniq
96
+ if defined?(@orphan_types)
97
+ all_orphan_types = @orphan_types.dup
98
+ if defined?(super)
99
+ all_orphan_types += super
100
+ all_orphan_types.uniq!
101
+ end
102
+ all_orphan_types
103
+ elsif defined?(super)
104
+ super
105
+ else
106
+ EmptyObjects::EMPTY_ARRAY
107
+ end
92
108
  end
93
109
  end
94
110
 
@@ -25,7 +25,7 @@ module GraphQL
25
25
  load_constant(:DirectiveLocationEnum)
26
26
  ]
27
27
  @types = {}
28
- @possible_types = {}.tap(&:compare_by_identity)
28
+ @possible_types = {}.compare_by_identity
29
29
  type_defns.each do |t|
30
30
  @types[t.graphql_name] = t
31
31
  @possible_types[t] = [t]
@@ -90,7 +90,8 @@ module GraphQL
90
90
  def resolve_late_binding(late_bound_type)
91
91
  case late_bound_type
92
92
  when GraphQL::Schema::LateBoundType
93
- @schema.get_type(late_bound_type.name)
93
+ type_name = late_bound_type.name
94
+ @types[type_name] || @schema.get_type(type_name)
94
95
  when GraphQL::Schema::List
95
96
  resolve_late_binding(late_bound_type.of_type).to_list_type
96
97
  when GraphQL::Schema::NonNull
@@ -198,9 +198,13 @@ module GraphQL
198
198
  end
199
199
 
200
200
  def all_argument_definitions
201
- all_defns = own_arguments.values
202
- all_defns.flatten!
203
- all_defns
201
+ if own_arguments.any?
202
+ all_defns = own_arguments.values
203
+ all_defns.flatten!
204
+ all_defns
205
+ else
206
+ EmptyObjects::EMPTY_ARRAY
207
+ end
204
208
  end
205
209
 
206
210
  # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
@@ -7,7 +7,11 @@ module GraphQL
7
7
  module HasUnresolvedTypeError
8
8
  private
9
9
  def add_unresolved_type_error(child_class)
10
- child_class.const_set(:UnresolvedTypeError, Class.new(GraphQL::UnresolvedTypeError))
10
+ if child_class.name # Don't set this for anonymous classes
11
+ child_class.const_set(:UnresolvedTypeError, Class.new(GraphQL::UnresolvedTypeError))
12
+ else
13
+ child_class.const_set(:UnresolvedTypeError, UnresolvedTypeError)
14
+ end
11
15
  end
12
16
  end
13
17
  end