graphql 2.1.0 → 2.1.2

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 (68) 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 +90 -251
  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 +30 -19
  35. data/lib/graphql/language/nodes.rb +1 -1
  36. data/lib/graphql/language/printer.rb +88 -27
  37. data/lib/graphql/language/sanitized_printer.rb +6 -1
  38. data/lib/graphql/language/static_visitor.rb +167 -0
  39. data/lib/graphql/language/visitor.rb +2 -0
  40. data/lib/graphql/language.rb +1 -0
  41. data/lib/graphql/query/context/scoped_context.rb +101 -0
  42. data/lib/graphql/query/context.rb +32 -98
  43. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  44. data/lib/graphql/schema/field/connection_extension.rb +1 -15
  45. data/lib/graphql/schema/field.rb +7 -4
  46. data/lib/graphql/schema/has_single_input_argument.rb +156 -0
  47. data/lib/graphql/schema/introspection_system.rb +2 -0
  48. data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
  49. data/lib/graphql/schema/member/has_arguments.rb +19 -4
  50. data/lib/graphql/schema/member/has_fields.rb +4 -1
  51. data/lib/graphql/schema/member/has_interfaces.rb +21 -7
  52. data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
  53. data/lib/graphql/schema/resolver.rb +4 -0
  54. data/lib/graphql/schema/scalar.rb +3 -3
  55. data/lib/graphql/schema/warden.rb +84 -55
  56. data/lib/graphql/schema.rb +123 -36
  57. data/lib/graphql/static_validation/all_rules.rb +1 -1
  58. data/lib/graphql/static_validation/base_visitor.rb +1 -1
  59. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  60. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  61. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  62. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  63. data/lib/graphql/static_validation/validation_context.rb +5 -2
  64. data/lib/graphql/tracing/appoptics_trace.rb +2 -2
  65. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  66. data/lib/graphql/version.rb +1 -1
  67. data/lib/graphql.rb +1 -1
  68. metadata +9 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 785d064bf9279c1c66291e629607bbfe14f33ea0a8ad3e22549d253116159f81
4
- data.tar.gz: a66884908e066f99293c3171aa1856977ba01fde58dca3a65a978ef025de8f32
3
+ metadata.gz: c2b3d848591e918d34df3851a973b6510912d6472f5847f493b5b0bfed4df997
4
+ data.tar.gz: 3fcf17d2bc7259ef01cbe6d85aa23b31ccbed2321700c0474124e5eb6f0792d2
5
5
  SHA512:
6
- metadata.gz: f27e18f66f871cde5a6aedfea89621e57014c02062736b55aa86ebc4f64bdec83de93dca83c4aaede60c321113f4099efeab362cc3d4a2047d725e5ec2e7d73b
7
- data.tar.gz: 4071a8921d60279b5ffc3ba11bb653e07596a9a97c0bab783c6a183558ff632996431d365052c27ea571b0b0c3a6a267f3cc10d1d4eff202eec513fc469f094e
6
+ metadata.gz: 8b2b258c108210008131e610ff0e486817ceebea1782d4f329f1f776ac8c70bdc03177f22a746c2f918d9a1c15e3b6e3971f2569cd3d2c15c6dd714e5a5d38d7
7
+ data.tar.gz: 28e7f3c992987507e74e42320133b710aa6ca6af593dd2b6300359e30728ad415ff27e7671b5e8cfedb4b1a44324a06bc9fdd4ab95535c1f99e142aafebc3ea5
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Mutations
3
5
  class BaseMutation < GraphQL::Schema::RelayClassicMutation
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
5
  class MutationType < Types::BaseObject
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
5
  class BaseArgument < GraphQL::Schema::Argument
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
5
  class BaseConnection < Types::BaseObject
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
5
  class BaseEdge < Types::BaseObject
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
5
  class BaseEnum < GraphQL::Schema::Enum
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
5
  class BaseField < GraphQL::Schema::Field
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
5
  class BaseInputObject < GraphQL::Schema::InputObject
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
5
  module BaseInterface
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
5
  class BaseObject < GraphQL::Schema::Object
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
5
  class BaseScalar < GraphQL::Schema::Scalar
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
5
  class BaseUnion < GraphQL::Schema::Union
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  class GraphqlController < ApplicationController
3
5
  # If accessing from outside this domain, nullify the session
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Loaders
3
5
  class <%= class_name %> < GraphQL::Batch::Loader
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Mutations
3
5
  class <%= class_name %> < BaseMutation
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
5
  module NodeType
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
5
  class QueryType < Types::BaseObject
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  class <%= schema_name %> < GraphQL::Schema
3
5
  query(Types::QueryType)
@@ -29,6 +29,13 @@ module GraphQL
29
29
  true
30
30
  end
31
31
 
32
+ # Analyzer hook to decide at analysis time whether analysis
33
+ # requires a visitor pass; can be disabled for precomputed results.
34
+ # @return [Boolean] If analysis requires visitation or not
35
+ def visit?
36
+ true
37
+ end
38
+
32
39
  # The result for this analyzer. Returning {GraphQL::AnalysisError} results
33
40
  # in a query error.
34
41
  # @return [Any] The analyzer result
@@ -28,17 +28,22 @@ module GraphQL
28
28
  def initialize(query)
29
29
  @max_depth = 0
30
30
  @current_depth = 0
31
+ @count_introspection_fields = query.schema.count_introspection_fields
31
32
  super
32
33
  end
33
34
 
34
35
  def on_enter_field(node, parent, visitor)
35
- return if visitor.skipping? || visitor.visiting_fragment_definition?
36
+ return if visitor.skipping? ||
37
+ visitor.visiting_fragment_definition? ||
38
+ (@count_introspection_fields == false && visitor.field_definition.introspection?)
36
39
 
37
40
  @current_depth += 1
38
41
  end
39
42
 
40
43
  def on_leave_field(node, parent, visitor)
41
- return if visitor.skipping? || visitor.visiting_fragment_definition?
44
+ return if visitor.skipping? ||
45
+ visitor.visiting_fragment_definition? ||
46
+ (@count_introspection_fields == false && visitor.field_definition.introspection?)
42
47
 
43
48
  if @max_depth < @current_depth
44
49
  @max_depth = @current_depth
@@ -5,12 +5,12 @@ module GraphQL
5
5
  # Depth first traversal through a query AST, calling AST analyzers
6
6
  # along the way.
7
7
  #
8
- # The visitor is a special case of GraphQL::Language::Visitor, visiting
8
+ # The visitor is a special case of GraphQL::Language::StaticVisitor, visiting
9
9
  # only the selected operation, providing helpers for common use cases such
10
10
  # as skipped fields and visiting fragment spreads.
11
11
  #
12
12
  # @see {GraphQL::Analysis::AST::Analyzer} AST Analyzers for queries
13
- class Visitor < GraphQL::Language::Visitor
13
+ class Visitor < GraphQL::Language::StaticVisitor
14
14
  def initialize(query:, analyzers:)
15
15
  @analyzers = analyzers
16
16
  @path = []
@@ -51,22 +51,26 @@ module GraphQL
51
51
  query.current_trace.analyze_query(query: query) do
52
52
  query_analyzers = analyzers
53
53
  .map { |analyzer| analyzer.new(query) }
54
- .select { |analyzer| analyzer.analyze? }
54
+ .tap { _1.select!(&:analyze?) }
55
55
 
56
56
  analyzers_to_run = query_analyzers + multiplex_analyzers
57
57
  if analyzers_to_run.any?
58
- visitor = GraphQL::Analysis::AST::Visitor.new(
59
- query: query,
60
- analyzers: analyzers_to_run
61
- )
62
58
 
63
- visitor.visit
59
+ analyzers_to_run.select!(&:visit?)
60
+ if analyzers_to_run.any?
61
+ visitor = GraphQL::Analysis::AST::Visitor.new(
62
+ query: query,
63
+ analyzers: analyzers_to_run
64
+ )
64
65
 
65
- if visitor.rescued_errors.any?
66
- visitor.rescued_errors
67
- else
68
- query_analyzers.map(&:result)
66
+ visitor.visit
67
+
68
+ if visitor.rescued_errors.any?
69
+ return visitor.rescued_errors
70
+ end
69
71
  end
72
+
73
+ query_analyzers.map(&:result)
70
74
  else
71
75
  []
72
76
  end
@@ -74,7 +78,7 @@ module GraphQL
74
78
  end
75
79
 
76
80
  def analysis_errors(results)
77
- results.flatten.select { |r| r.is_a?(GraphQL::AnalysisError) }
81
+ results.flatten.tap { _1.select! { |r| r.is_a?(GraphQL::AnalysisError) } }
78
82
  end
79
83
  end
80
84
  end
@@ -161,6 +161,13 @@ module GraphQL
161
161
  [*batch_args, **batch_kwargs]
162
162
  end
163
163
 
164
+ # Clear any already-loaded objects for this source
165
+ # @return [void]
166
+ def clear_cache
167
+ @results.clear
168
+ nil
169
+ end
170
+
164
171
  attr_reader :pending
165
172
 
166
173
  private
@@ -61,6 +61,32 @@ module GraphQL
61
61
  @nonblocking
62
62
  end
63
63
 
64
+ # This is called before the fiber is spawned, from the parent context (i.e. from
65
+ # the thread or fiber that it is scheduled from).
66
+ #
67
+ # @return [Hash<Symbol, Object>] Current fiber-local variables
68
+ def get_fiber_variables
69
+ fiber_vars = {}
70
+ Thread.current.keys.each do |fiber_var_key|
71
+ # This variable should be fresh in each new fiber
72
+ if fiber_var_key != :__graphql_runtime_info
73
+ fiber_vars[fiber_var_key] = Thread.current[fiber_var_key]
74
+ end
75
+ end
76
+ fiber_vars
77
+ end
78
+
79
+ # Set up the fiber variables in a new fiber.
80
+ #
81
+ # This is called within the fiber, right after it is spawned.
82
+ #
83
+ # @param vars [Hash<Symbol, Object>] Fiber-local variables from {get_fiber_variables}
84
+ # @return [void]
85
+ def set_fiber_variables(vars)
86
+ vars.each { |k, v| Thread.current[k] = v }
87
+ nil
88
+ end
89
+
64
90
  # Get a Source instance from this dataloader, for calling `.load(...)` or `.request(...)` on.
65
91
  #
66
92
  # @param source_class [Class<GraphQL::Dataloader::Source]
@@ -104,6 +130,15 @@ module GraphQL
104
130
  nil
105
131
  end
106
132
 
133
+ # Clear any already-loaded objects from {Source} caches
134
+ # @return [void]
135
+ def clear_cache
136
+ @source_cache.each do |_source_class, batched_sources|
137
+ batched_sources.each_value(&:clear_cache)
138
+ end
139
+ nil
140
+ end
141
+
107
142
  # Use a self-contained queue for the work in the block.
108
143
  def run_isolated
109
144
  prev_queue = @pending_jobs
@@ -286,23 +321,16 @@ module GraphQL
286
321
  #
287
322
  # @see https://github.com/rmosolgo/graphql-ruby/issues/3449
288
323
  def spawn_fiber
289
- fiber_locals = {}
290
-
291
- Thread.current.keys.each do |fiber_var_key|
292
- # This variable should be fresh in each new fiber
293
- if fiber_var_key != :__graphql_runtime_info
294
- fiber_locals[fiber_var_key] = Thread.current[fiber_var_key]
295
- end
296
- end
324
+ fiber_vars = get_fiber_variables
297
325
 
298
326
  if @nonblocking
299
327
  Fiber.new(blocking: false) do
300
- fiber_locals.each { |k, v| Thread.current[k] = v }
328
+ set_fiber_variables(fiber_vars)
301
329
  yield
302
330
  end
303
331
  else
304
332
  Fiber.new do
305
- fiber_locals.each { |k, v| Thread.current[k] = v }
333
+ set_fiber_variables(fiber_vars)
306
334
  yield
307
335
  end
308
336
  end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Execution
5
+ class Interpreter
6
+ class Runtime
7
+ module GraphQLResult
8
+ def initialize(result_name, parent_result, is_non_null_in_parent)
9
+ @graphql_parent = parent_result
10
+ if parent_result && parent_result.graphql_dead
11
+ @graphql_dead = true
12
+ end
13
+ @graphql_result_name = result_name
14
+ @graphql_is_non_null_in_parent = is_non_null_in_parent
15
+ # Jump through some hoops to avoid creating this duplicate storage if at all possible.
16
+ @graphql_metadata = nil
17
+ end
18
+
19
+ def path
20
+ @path ||= build_path([])
21
+ end
22
+
23
+ def build_path(path_array)
24
+ graphql_result_name && path_array.unshift(graphql_result_name)
25
+ @graphql_parent ? @graphql_parent.build_path(path_array) : path_array
26
+ end
27
+
28
+ attr_accessor :graphql_dead
29
+ attr_reader :graphql_parent, :graphql_result_name, :graphql_is_non_null_in_parent
30
+
31
+ # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
32
+ attr_accessor :graphql_result_data
33
+ end
34
+
35
+ class GraphQLResultHash
36
+ def initialize(_result_name, _parent_result, _is_non_null_in_parent)
37
+ super
38
+ @graphql_result_data = {}
39
+ end
40
+
41
+ include GraphQLResult
42
+
43
+ attr_accessor :graphql_merged_into
44
+
45
+ def set_leaf(key, value)
46
+ # This is a hack.
47
+ # Basically, this object is merged into the root-level result at some point.
48
+ # But the problem is, some lazies are created whose closures retain reference to _this_
49
+ # object. When those lazies are resolved, they cause an update to this object.
50
+ #
51
+ # In order to return a proper top-level result, we have to update that top-level result object.
52
+ # In order to return a proper partial result (eg, for a directive), we have to update this object, too.
53
+ # Yowza.
54
+ if (t = @graphql_merged_into)
55
+ t.set_leaf(key, value)
56
+ end
57
+
58
+ @graphql_result_data[key] = value
59
+ # keep this up-to-date if it's been initialized
60
+ @graphql_metadata && @graphql_metadata[key] = value
61
+
62
+ value
63
+ end
64
+
65
+ def set_child_result(key, value)
66
+ if (t = @graphql_merged_into)
67
+ t.set_child_result(key, value)
68
+ end
69
+ @graphql_result_data[key] = value.graphql_result_data
70
+ # If we encounter some part of this response that requires metadata tracking,
71
+ # then create the metadata hash if necessary. It will be kept up-to-date after this.
72
+ (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
73
+ value
74
+ end
75
+
76
+ def delete(key)
77
+ @graphql_metadata && @graphql_metadata.delete(key)
78
+ @graphql_result_data.delete(key)
79
+ end
80
+
81
+ def each
82
+ (@graphql_metadata || @graphql_result_data).each { |k, v| yield(k, v) }
83
+ end
84
+
85
+ def values
86
+ (@graphql_metadata || @graphql_result_data).values
87
+ end
88
+
89
+ def key?(k)
90
+ @graphql_result_data.key?(k)
91
+ end
92
+
93
+ def [](k)
94
+ (@graphql_metadata || @graphql_result_data)[k]
95
+ end
96
+
97
+ def merge_into(into_result)
98
+ self.each do |key, value|
99
+ case value
100
+ when GraphQLResultHash
101
+ next_into = into_result[key]
102
+ if next_into
103
+ value.merge_into(next_into)
104
+ else
105
+ into_result.set_child_result(key, value)
106
+ end
107
+ when GraphQLResultArray
108
+ # There's no special handling of arrays because currently, there's no way to split the execution
109
+ # of a list over several concurrent flows.
110
+ into_result.set_child_result(key, value)
111
+ else
112
+ # We have to assume that, since this passed the `fields_will_merge` selection,
113
+ # that the old and new values are the same.
114
+ into_result.set_leaf(key, value)
115
+ end
116
+ end
117
+ @graphql_merged_into = into_result
118
+ end
119
+ end
120
+
121
+ class GraphQLResultArray
122
+ include GraphQLResult
123
+
124
+ def initialize(_result_name, _parent_result, _is_non_null_in_parent)
125
+ super
126
+ @graphql_result_data = []
127
+ end
128
+
129
+ def graphql_skip_at(index)
130
+ # Mark this index as dead. It's tricky because some indices may already be storing
131
+ # `Lazy`s. So the runtime is still holding indexes _before_ skipping,
132
+ # this object has to coordinate incoming writes to account for any already-skipped indices.
133
+ @skip_indices ||= []
134
+ @skip_indices << index
135
+ offset_by = @skip_indices.count { |skipped_idx| skipped_idx < index}
136
+ delete_at_index = index - offset_by
137
+ @graphql_metadata && @graphql_metadata.delete_at(delete_at_index)
138
+ @graphql_result_data.delete_at(delete_at_index)
139
+ end
140
+
141
+ def set_leaf(idx, value)
142
+ if @skip_indices
143
+ offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
144
+ idx -= offset_by
145
+ end
146
+ @graphql_result_data[idx] = value
147
+ @graphql_metadata && @graphql_metadata[idx] = value
148
+ value
149
+ end
150
+
151
+ def set_child_result(idx, value)
152
+ if @skip_indices
153
+ offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
154
+ idx -= offset_by
155
+ end
156
+ @graphql_result_data[idx] = value.graphql_result_data
157
+ # If we encounter some part of this response that requires metadata tracking,
158
+ # then create the metadata hash if necessary. It will be kept up-to-date after this.
159
+ (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
160
+ value
161
+ end
162
+
163
+ def values
164
+ (@graphql_metadata || @graphql_result_data)
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end