graphql 2.5.11 → 2.6.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 (154) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/field_extractor.rb +17 -1
  4. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  5. data/lib/generators/graphql/templates/schema.erb +2 -1
  6. data/lib/graphql/analysis/query_complexity.rb +31 -15
  7. data/lib/graphql/analysis.rb +20 -13
  8. data/lib/graphql/backtrace/table.rb +10 -1
  9. data/lib/graphql/current.rb +7 -1
  10. data/lib/graphql/dashboard/application_controller.rb +45 -0
  11. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  12. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  13. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  14. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
  15. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
  16. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
  17. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
  18. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
  19. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
  20. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
  21. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
  22. data/lib/graphql/dashboard.rb +14 -73
  23. data/lib/graphql/dataloader/async_dataloader.rb +336 -60
  24. data/lib/graphql/dataloader/null_dataloader.rb +48 -10
  25. data/lib/graphql/dataloader/source.rb +32 -19
  26. data/lib/graphql/dataloader.rb +107 -39
  27. data/lib/graphql/date_encoding_error.rb +1 -1
  28. data/lib/graphql/execution/directive_checks.rb +2 -0
  29. data/lib/graphql/execution/field_resolve_step.rb +804 -0
  30. data/lib/graphql/execution/finalize.rb +229 -0
  31. data/lib/graphql/execution/input_values.rb +333 -0
  32. data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
  33. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  34. data/lib/graphql/execution/interpreter/resolve.rb +7 -13
  35. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
  36. data/lib/graphql/execution/interpreter/runtime.rb +60 -40
  37. data/lib/graphql/execution/interpreter.rb +17 -23
  38. data/lib/graphql/execution/lazy.rb +1 -1
  39. data/lib/graphql/execution/load_argument_step.rb +102 -0
  40. data/lib/graphql/execution/multiplex.rb +1 -1
  41. data/lib/graphql/execution/next.rb +98 -0
  42. data/lib/graphql/execution/prepare_object_step.rb +151 -0
  43. data/lib/graphql/execution/resolve_type_step.rb +27 -0
  44. data/lib/graphql/execution/runner.rb +457 -0
  45. data/lib/graphql/execution/selections_step.rb +95 -0
  46. data/lib/graphql/execution.rb +15 -4
  47. data/lib/graphql/execution_error.rb +17 -12
  48. data/lib/graphql/float_decoding_error.rb +13 -0
  49. data/lib/graphql/float_encoding_error.rb +28 -0
  50. data/lib/graphql/introspection/directive_type.rb +7 -3
  51. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  52. data/lib/graphql/introspection/entry_points.rb +11 -3
  53. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  54. data/lib/graphql/introspection/field_type.rb +13 -5
  55. data/lib/graphql/introspection/input_value_type.rb +21 -13
  56. data/lib/graphql/introspection/schema_type.rb +6 -2
  57. data/lib/graphql/introspection/type_type.rb +64 -28
  58. data/lib/graphql/invalid_null_error.rb +11 -5
  59. data/lib/graphql/language/block_string.rb +6 -11
  60. data/lib/graphql/language/cache.rb +84 -14
  61. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  62. data/lib/graphql/language/lexer.rb +27 -17
  63. data/lib/graphql/language/nodes.rb +2 -1
  64. data/lib/graphql/language/parser.rb +1 -1
  65. data/lib/graphql/language.rb +32 -17
  66. data/lib/graphql/pagination/connection.rb +2 -0
  67. data/lib/graphql/pagination/connections.rb +30 -0
  68. data/lib/graphql/pagination/relation_connection.rb +10 -1
  69. data/lib/graphql/query/context.rb +10 -3
  70. data/lib/graphql/query/null_context.rb +9 -3
  71. data/lib/graphql/query/partial.rb +18 -3
  72. data/lib/graphql/query/variable_validation_error.rb +16 -1
  73. data/lib/graphql/query/variables.rb +1 -1
  74. data/lib/graphql/query.rb +12 -3
  75. data/lib/graphql/railtie.rb +2 -1
  76. data/lib/graphql/runtime_error.rb +6 -0
  77. data/lib/graphql/schema/argument.rb +13 -1
  78. data/lib/graphql/schema/build_from_definition.rb +28 -1
  79. data/lib/graphql/schema/directive/feature.rb +4 -0
  80. data/lib/graphql/schema/directive/transform.rb +20 -0
  81. data/lib/graphql/schema/directive.rb +48 -13
  82. data/lib/graphql/schema/field/connection_extension.rb +4 -37
  83. data/lib/graphql/schema/field/scope_extension.rb +18 -13
  84. data/lib/graphql/schema/field.rb +79 -48
  85. data/lib/graphql/schema/field_extension.rb +11 -8
  86. data/lib/graphql/schema/has_single_input_argument.rb +24 -13
  87. data/lib/graphql/schema/input_object.rb +6 -2
  88. data/lib/graphql/schema/interface.rb +26 -0
  89. data/lib/graphql/schema/introspection_system.rb +6 -21
  90. data/lib/graphql/schema/list.rb +5 -1
  91. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
  92. data/lib/graphql/schema/member/has_arguments.rb +43 -14
  93. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  94. data/lib/graphql/schema/member/has_dataloader.rb +37 -0
  95. data/lib/graphql/schema/member/has_fields.rb +86 -5
  96. data/lib/graphql/schema/member.rb +5 -0
  97. data/lib/graphql/schema/non_null.rb +1 -1
  98. data/lib/graphql/schema/object.rb +1 -0
  99. data/lib/graphql/schema/printer.rb +1 -1
  100. data/lib/graphql/schema/ractor_shareable.rb +1 -0
  101. data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
  102. data/lib/graphql/schema/resolver.rb +76 -1
  103. data/lib/graphql/schema/subscription.rb +53 -10
  104. data/lib/graphql/schema/timeout.rb +2 -2
  105. data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
  106. data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
  107. data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
  108. data/lib/graphql/schema/validator/format_validator.rb +3 -3
  109. data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
  110. data/lib/graphql/schema/validator/length_validator.rb +6 -6
  111. data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
  112. data/lib/graphql/schema/validator/required_validator.rb +38 -5
  113. data/lib/graphql/schema/validator.rb +9 -0
  114. data/lib/graphql/schema/visibility/profile.rb +74 -53
  115. data/lib/graphql/schema/visibility/visit.rb +1 -1
  116. data/lib/graphql/schema/visibility.rb +32 -24
  117. data/lib/graphql/schema/wrapper.rb +11 -1
  118. data/lib/graphql/schema.rb +93 -27
  119. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  120. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  121. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  122. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  123. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  124. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  125. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
  126. data/lib/graphql/static_validation/rules/fields_will_merge.rb +351 -248
  127. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  128. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  129. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  130. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  131. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  132. data/lib/graphql/static_validation/validation_context.rb +1 -1
  133. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  134. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +40 -10
  135. data/lib/graphql/subscriptions/serialize.rb +10 -4
  136. data/lib/graphql/subscriptions.rb +36 -1
  137. data/lib/graphql/testing/helpers.rb +12 -9
  138. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  139. data/lib/graphql/testing.rb +1 -0
  140. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  141. data/lib/graphql/tracing/detailed_trace.rb +70 -7
  142. data/lib/graphql/tracing/perfetto_trace.rb +213 -81
  143. data/lib/graphql/tracing/sentry_trace.rb +3 -1
  144. data/lib/graphql/tracing/trace.rb +6 -0
  145. data/lib/graphql/types/float.rb +18 -4
  146. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  147. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  148. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  149. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  150. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  151. data/lib/graphql/unauthorized_error.rb +9 -1
  152. data/lib/graphql/version.rb +1 -1
  153. data/lib/graphql.rb +8 -2
  154. metadata +23 -59
@@ -8,11 +8,17 @@ module GraphQL
8
8
  # Use this plugin to make some parts of your schema hidden from some viewers.
9
9
  #
10
10
  class Visibility
11
+ class TypeConfigurationError < GraphQL::Error
12
+ def initialize(config_message, config_str)
13
+ message = "GraphQL::Schema::Visibility already preloaded, but #{config_message} added to the schema. Move this `#{config_str}` configuration above `use(GraphQL::Schema::Visibility)"
14
+ super(message)
15
+ end
16
+ end
11
17
  # @param schema [Class<GraphQL::Schema>]
12
18
  # @param profiles [Hash<Symbol => Hash>] A hash of `name => context` pairs for preloading visibility profiles
13
- # @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?`)
19
+ # @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?` and `Rails.env.staging?`)
14
20
  # @param migration_errors [Boolean] if `true`, raise an error when `Visibility` and `Warden` return different results
15
- def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails.env) ? Rails.env.production? : nil), migration_errors: false)
21
+ def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails.env) ? (Rails.env.production? || Rails.env.staging? || nil) : false), migration_errors: false)
16
22
  profiles&.each { |name, ctx|
17
23
  ctx[:visibility_profile] = name
18
24
  ctx.freeze
@@ -20,7 +26,7 @@ module GraphQL
20
26
  schema.visibility = self.new(schema, dynamic: dynamic, preload: preload, profiles: profiles, migration_errors: migration_errors)
21
27
  end
22
28
 
23
- def initialize(schema, dynamic:, preload:, profiles:, migration_errors:)
29
+ def initialize(schema, dynamic:, preload:, profiles:, migration_errors:, configuration_inherited: false)
24
30
  @schema = schema
25
31
  schema.use_visibility_profile = true
26
32
  schema.visibility_profile_class = if migration_errors
@@ -40,6 +46,7 @@ module GraphQL
40
46
  @types = nil
41
47
  @all_references = nil
42
48
  @loaded_all = false
49
+ @configuration_inherited = configuration_inherited
43
50
  if preload
44
51
  self.preload
45
52
  end
@@ -94,6 +101,7 @@ module GraphQL
94
101
  # Root types may have been nil:
95
102
  types_to_visit.compact!
96
103
  ensure_all_loaded(types_to_visit)
104
+ @cached_profiles.clear
97
105
  @profiles.each do |profile_name, example_ctx|
98
106
  prof = profile_for(example_ctx)
99
107
  prof.preload
@@ -102,41 +110,27 @@ module GraphQL
102
110
 
103
111
  # @api private
104
112
  def query_configured(query_type)
105
- if @preload
106
- ensure_all_loaded([query_type])
107
- end
113
+ require_if_preloaded("a query type was", "query(...)")
108
114
  end
109
115
 
110
116
  # @api private
111
117
  def mutation_configured(mutation_type)
112
- if @preload
113
- ensure_all_loaded([mutation_type])
114
- end
118
+ require_if_preloaded("a mutation type was", "mutation(...)")
115
119
  end
116
120
 
117
121
  # @api private
118
122
  def subscription_configured(subscription_type)
119
- if @preload
120
- ensure_all_loaded([subscription_type])
121
- end
123
+ require_if_preloaded("a mutation type was", "subscription(...)")
122
124
  end
123
125
 
124
126
  # @api private
125
127
  def orphan_types_configured(orphan_types)
126
- if @preload
127
- ensure_all_loaded(orphan_types)
128
- end
128
+ require_if_preloaded("orphan types were", "orphan_types(...)")
129
129
  end
130
130
 
131
131
  # @api private
132
132
  def introspection_system_configured(introspection_system)
133
- if @preload
134
- introspection_types = [
135
- *@schema.introspection_system.types.values,
136
- *@schema.introspection_system.entry_points.map { |ep| ep.type.unwrap },
137
- ]
138
- ensure_all_loaded(introspection_types)
139
- end
133
+ require_if_preloaded("custom introspection was", "introspection(...)")
140
134
  end
141
135
 
142
136
  # Make another Visibility for `schema` based on this one
@@ -148,7 +142,8 @@ module GraphQL
148
142
  dynamic: @dynamic,
149
143
  preload: @preload,
150
144
  profiles: @profiles,
151
- migration_errors: @migration_errors
145
+ migration_errors: @migration_errors,
146
+ configuration_inherited: true,
152
147
  )
153
148
  end
154
149
 
@@ -191,11 +186,24 @@ module GraphQL
191
186
  if refresh
192
187
  @top_level_profile = nil
193
188
  end
194
- @top_level_profile ||= @schema.visibility_profile_class.new(context: Query::NullContext.instance, schema: @schema, visibility: self)
189
+ @top_level_profile ||= @schema.visibility_profile_class.new(context: @schema.null_context, schema: @schema, visibility: self)
195
190
  end
196
191
 
197
192
  private
198
193
 
194
+ def require_if_preloaded(config_message, config_code)
195
+ case @preload
196
+ when false
197
+ # Rails.env wasn't defined, so this won't try to preload unless manually set to true
198
+ when true, nil
199
+ if @configuration_inherited
200
+ preload
201
+ else
202
+ raise TypeConfigurationError.new(config_message, config_code)
203
+ end
204
+ end
205
+ end
206
+
199
207
  def ensure_all_loaded(types_to_visit)
200
208
  while (type = types_to_visit.shift)
201
209
  if type.kind.fields? && @preloaded_types.add?(type)
@@ -13,12 +13,22 @@ module GraphQL
13
13
  end
14
14
 
15
15
  def unwrap
16
- @of_type.unwrap
16
+ @unwrapped ||= @of_type.unwrap
17
+ end
18
+
19
+ def freeze
20
+ unwrap
21
+ to_type_signature
22
+ super
17
23
  end
18
24
 
19
25
  def ==(other)
20
26
  self.class == other.class && of_type == other.of_type
21
27
  end
28
+
29
+ def deconstruct_keys(_keys)
30
+ { of_type: of_type }
31
+ end
22
32
  end
23
33
  end
24
34
  end
@@ -330,10 +330,16 @@ module GraphQL
330
330
  find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins
331
331
  end
332
332
 
333
+ attr_writer :null_context
334
+
335
+ def null_context
336
+ @null_context || GraphQL::Query::NullContext.instance
337
+ end
338
+
333
339
  # Build a map of `{ name => type }` and return it
334
340
  # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
335
341
  # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
336
- def types(context = GraphQL::Query::NullContext.instance)
342
+ def types(context = null_context)
337
343
  if use_visibility_profile?
338
344
  types = Visibility::Profile.from_context(context, self)
339
345
  return types.all_types_h
@@ -366,7 +372,7 @@ module GraphQL
366
372
  # @param context [GraphQL::Query::Context] Used for filtering definitions at query-time
367
373
  # @param use_visibility_profile Private, for migration to {Schema::Visibility}
368
374
  # @return [Module, nil] A type, or nil if there's no type called `type_name`
369
- def get_type(type_name, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
375
+ def get_type(type_name, context = null_context, use_visibility_profile = use_visibility_profile?)
370
376
  if use_visibility_profile
371
377
  profile = Visibility::Profile.from_context(context, self)
372
378
  return profile.type(type_name)
@@ -617,7 +623,7 @@ module GraphQL
617
623
  # @param use_visibility_profile Private, for migration to {Schema::Visibility}
618
624
  # @return [Hash<String, Module>] All possible types, if no `type` is given.
619
625
  # @return [Array<Module>] Possible types for `type`, if it's given.
620
- def possible_types(type = nil, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
626
+ def possible_types(type = nil, context = null_context, use_visibility_profile = use_visibility_profile?)
621
627
  if use_visibility_profile
622
628
  if type
623
629
  return Visibility::Profile.from_context(context, self).possible_types(type)
@@ -659,8 +665,8 @@ module GraphQL
659
665
  inherited_um = find_inherited_value(:union_memberships, EMPTY_HASH).fetch(type.graphql_name, EMPTY_ARRAY)
660
666
  own_um + inherited_um
661
667
  else
662
- joined_um = own_union_memberships.dup
663
- find_inherited_value(:union_memberhips, EMPTY_HASH).each do |k, v|
668
+ joined_um = own_union_memberships.transform_values(&:dup)
669
+ find_inherited_value(:union_memberships, EMPTY_HASH).each do |k, v|
664
670
  um = joined_um[k] ||= []
665
671
  um.concat(v)
666
672
  end
@@ -701,7 +707,7 @@ module GraphQL
701
707
  GraphQL::Schema::TypeExpression.build_type(context.query.types, ast_node)
702
708
  end
703
709
 
704
- def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
710
+ def get_field(type_or_name, field_name, context = null_context, use_visibility_profile = use_visibility_profile?)
705
711
  if use_visibility_profile
706
712
  profile = Visibility::Profile.from_context(context, self)
707
713
  parent_type = case type_or_name
@@ -738,7 +744,7 @@ module GraphQL
738
744
  end
739
745
  end
740
746
 
741
- def get_fields(type, context = GraphQL::Query::NullContext.instance)
747
+ def get_fields(type, context = null_context)
742
748
  type.fields(context)
743
749
  end
744
750
 
@@ -751,6 +757,7 @@ module GraphQL
751
757
  # reset this cached value:
752
758
  @introspection_system = nil
753
759
  introspection_system
760
+ self.visibility&.introspection_system_configured(introspection_system)
754
761
  @introspection
755
762
  else
756
763
  @introspection || find_inherited_value(:introspection)
@@ -762,7 +769,6 @@ module GraphQL
762
769
  if !@introspection_system
763
770
  @introspection_system = Schema::IntrospectionSystem.new(self)
764
771
  @introspection_system.resolve_late_bindings
765
- self.visibility&.introspection_system_configured(@introspection_system)
766
772
  end
767
773
  @introspection_system
768
774
  end
@@ -854,7 +860,7 @@ module GraphQL
854
860
  # @return [Array<GraphQL::StaticValidation::Error >]
855
861
  def validate(string_or_document, rules: nil, context: nil)
856
862
  doc = if string_or_document.is_a?(String)
857
- GraphQL.parse(string_or_document)
863
+ GraphQL.parse(string_or_document, max_tokens: max_query_string_tokens)
858
864
  else
859
865
  string_or_document
860
866
  end
@@ -1144,17 +1150,14 @@ module GraphQL
1144
1150
  attr_accessor :using_backtrace
1145
1151
 
1146
1152
  # @api private
1147
- def handle_or_reraise(context, err)
1153
+ def handle_or_reraise(context, err, object: context[:current_object], arguments: context[:current_arguments], field: context[:current_field])
1148
1154
  handler = Execution::Errors.find_handler_for(self, err.class)
1149
1155
  if handler
1150
- obj = context[:current_object]
1151
- args = context[:current_arguments]
1152
- args = args && args.respond_to?(:keyword_arguments) ? args.keyword_arguments : nil
1153
- field = context[:current_field]
1154
- if obj.is_a?(GraphQL::Schema::Object)
1155
- obj = obj.object
1156
+ arguments = arguments.respond_to?(:keyword_arguments) ? arguments.keyword_arguments : arguments
1157
+ if object.is_a?(GraphQL::Schema::Object)
1158
+ object = object.object
1156
1159
  end
1157
- handler[:handler].call(err, obj, args, context, field)
1160
+ handler[:handler].call(err, object, arguments, context, field)
1158
1161
  else
1159
1162
  if (context[:backtrace] || using_backtrace) && !err.is_a?(GraphQL::ExecutionError)
1160
1163
  err = GraphQL::Backtrace::TracedError.new(err, context)
@@ -1228,6 +1231,7 @@ module GraphQL
1228
1231
  vis = self.visibility
1229
1232
  child_class.visibility = vis.dup_for(child_class)
1230
1233
  end
1234
+ child_class.null_context = Query::NullContext.new(schema: child_class)
1231
1235
  super
1232
1236
  end
1233
1237
 
@@ -1329,13 +1333,14 @@ module GraphQL
1329
1333
  def type_error(type_error, context)
1330
1334
  case type_error
1331
1335
  when GraphQL::InvalidNullError
1332
- execution_error = GraphQL::ExecutionError.new(type_error.message, ast_node: type_error.ast_node)
1333
- execution_error.path = context[:current_path]
1336
+ execution_error = GraphQL::ExecutionError.new(type_error.message, ast_nodes: type_error.ast_nodes)
1337
+ execution_error.path = type_error.path || context[:current_path]
1334
1338
 
1335
1339
  context.errors << execution_error
1336
- when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
1340
+ execution_error
1341
+ when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::FloatEncodingError, GraphQL::IntegerEncodingError
1337
1342
  raise type_error
1338
- when GraphQL::IntegerDecodingError
1343
+ when GraphQL::FloatDecodingError, GraphQL::IntegerDecodingError
1339
1344
  nil
1340
1345
  end
1341
1346
  end
@@ -1354,6 +1359,16 @@ module GraphQL
1354
1359
  lazy_methods.set(lazy_class, value_method)
1355
1360
  end
1356
1361
 
1362
+ def resolves_lazies?
1363
+ lazy_method_count = 0
1364
+ lazy_methods.each do |k, v|
1365
+ if !v.nil?
1366
+ lazy_method_count += 1
1367
+ end
1368
+ end
1369
+ lazy_method_count > 2
1370
+ end
1371
+
1357
1372
  def instrument(instrument_step, instrumenter, options = {})
1358
1373
  warn <<~WARN
1359
1374
  Schema.instrument is deprecated, use `trace_with` instead: https://graphql-ruby.org/queries/tracing.html"
@@ -1424,7 +1439,16 @@ module GraphQL
1424
1439
  end
1425
1440
 
1426
1441
  def tracers
1427
- find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
1442
+ inherited = find_inherited_value(:tracers, EMPTY_ARRAY)
1443
+ if inherited.length > 0
1444
+ if own_tracers.length > 0
1445
+ inherited + own_tracers
1446
+ else
1447
+ inherited
1448
+ end
1449
+ else
1450
+ own_tracers
1451
+ end
1428
1452
  end
1429
1453
 
1430
1454
  # Mix `trace_mod` into this schema's `Trace` class so that its methods will be called at runtime.
@@ -1534,7 +1558,8 @@ module GraphQL
1534
1558
  end
1535
1559
 
1536
1560
  def query_analyzers
1537
- find_inherited_value(:query_analyzers, EMPTY_ARRAY) + own_query_analyzers
1561
+ inherited_qa = find_inherited_value(:query_analyzers, EMPTY_ARRAY)
1562
+ inherited_qa.empty? ? own_query_analyzers : (inherited_qa + own_query_analyzers)
1538
1563
  end
1539
1564
 
1540
1565
  # @param new_analyzer [Class<GraphQL::Analysis::Analyzer>] An analyzer to run on multiplexes to this schema
@@ -1559,6 +1584,14 @@ module GraphQL
1559
1584
  # @see {Query#initialize} for arguments.
1560
1585
  # @return [GraphQL::Query::Result] query result, ready to be serialized as JSON
1561
1586
  def execute(query_str = nil, **kwargs)
1587
+ if default_execution_next
1588
+ execute_next(query_str, **kwargs)
1589
+ else
1590
+ execute_legacy(query_str, **kwargs)
1591
+ end
1592
+ end
1593
+
1594
+ def execute_legacy(query_str = nil, **kwargs)
1562
1595
  if query_str
1563
1596
  kwargs[:query] = query_str
1564
1597
  end
@@ -1600,7 +1633,23 @@ module GraphQL
1600
1633
  # @option kwargs [nil, Integer] :max_complexity (nil)
1601
1634
  # @return [Array<GraphQL::Query::Result>] One result for each query in the input
1602
1635
  def multiplex(queries, **kwargs)
1603
- GraphQL::Execution::Interpreter.run_all(self, queries, **kwargs)
1636
+ if @default_execution_next
1637
+ multiplex_next(queries, **kwargs)
1638
+ else
1639
+ GraphQL::Execution::Interpreter.run_all(self, queries, **kwargs)
1640
+ end
1641
+ end
1642
+
1643
+ def default_execution_next(new_value = NOT_CONFIGURED)
1644
+ if !NOT_CONFIGURED.equal?(new_value)
1645
+ @default_execution_next = new_value
1646
+ elsif instance_variable_defined?(:@default_execution_next)
1647
+ @default_execution_next
1648
+ elsif superclass.respond_to?(:default_execution_next)
1649
+ superclass.default_execution_next
1650
+ else
1651
+ false
1652
+ end
1604
1653
  end
1605
1654
 
1606
1655
  def instrumenters
@@ -1708,7 +1757,7 @@ module GraphQL
1708
1757
  # If you need to support previous, non-spec behavior which allowed selecting union fields
1709
1758
  # but *not* selecting any fields on that union, set this to `true` to continue allowing that behavior.
1710
1759
  #
1711
- # If this is `true`, then {.legacy_invalid_empty_selections_on_union} will be called with {Query} objects
1760
+ # If this is `true`, then {.legacy_invalid_empty_selections_on_union_with_type} will be called with {Query} objects
1712
1761
  # with that kind of selections. You must implement that method
1713
1762
  # @param new_value [Boolean]
1714
1763
  # @return [true, false, nil]
@@ -1724,6 +1773,22 @@ module GraphQL
1724
1773
  end
1725
1774
  end
1726
1775
 
1776
+ # This method is called during validation when a previously-allowed, but non-spec
1777
+ # query is encountered where a union field has no child selections on it.
1778
+ #
1779
+ # If `legacy_invalid_empty_selections_on_union_with_type` is overridden, this method will not be called.
1780
+ #
1781
+ # You should implement this method or `legacy_invalid_empty_selections_on_union_with_type`
1782
+ # to log the violation so that you can contact clients and notify them about changing their queries.
1783
+ # Then return a suitable value to tell GraphQL-Ruby how to continue.
1784
+ # @param query [GraphQL::Query]
1785
+ # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
1786
+ # @return [String] A validation error to return for this query
1787
+ # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
1788
+ def legacy_invalid_empty_selections_on_union(query)
1789
+ raise "Implement `def self.legacy_invalid_empty_selections_on_union_with_type(query, type)` or `def self.legacy_invalid_empty_selections_on_union(query)` to handle this scenario"
1790
+ end
1791
+
1727
1792
  # This method is called during validation when a previously-allowed, but non-spec
1728
1793
  # query is encountered where a union field has no child selections on it.
1729
1794
  #
@@ -1731,11 +1796,12 @@ module GraphQL
1731
1796
  # and notify them about changing their queries. Then return a suitable value to
1732
1797
  # tell GraphQL-Ruby how to continue.
1733
1798
  # @param query [GraphQL::Query]
1799
+ # @param type [Module] A GraphQL type definition
1734
1800
  # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
1735
1801
  # @return [String] A validation error to return for this query
1736
1802
  # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
1737
- def legacy_invalid_empty_selections_on_union(query)
1738
- raise "Implement `def self.legacy_invalid_empty_selections_on_union(query)` to handle this scenario"
1803
+ def legacy_invalid_empty_selections_on_union_with_type(query, type)
1804
+ legacy_invalid_empty_selections_on_union(query)
1739
1805
  end
1740
1806
 
1741
1807
  # This setting controls how GraphQL-Ruby handles overlapping selections on scalar types when the types
@@ -4,25 +4,26 @@ module GraphQL
4
4
  class BaseVisitor < GraphQL::Language::StaticVisitor
5
5
  def initialize(document, context)
6
6
  @path = []
7
- @object_types = []
8
- @directives = []
9
- @field_definitions = []
10
- @argument_definitions = []
11
- @directive_definitions = []
7
+ @path_depth = 0
8
+ @current_object_type = nil
9
+ @parent_object_type = nil
10
+ @current_field_definition = nil
11
+ @current_argument_definition = nil
12
+ @parent_argument_definition = nil
13
+ @current_directive_definition = nil
12
14
  @context = context
13
15
  @types = context.query.types
14
16
  @schema = context.schema
17
+ @inline_fragment_paths = {}
18
+ @field_unwrapped_types = {}.compare_by_identity
15
19
  super(document)
16
20
  end
17
21
 
18
22
  attr_reader :context
19
23
 
20
- # @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
21
- attr_reader :object_types
22
-
23
24
  # @return [Array<String>] The nesting of the current position in the AST
24
25
  def path
25
- @path.dup
26
+ @path[0, @path_depth]
26
27
  end
27
28
 
28
29
  # Build a class to visit the AST and perform validation,
@@ -55,86 +56,125 @@ module GraphQL
55
56
  module ContextMethods
56
57
  def on_operation_definition(node, parent)
57
58
  object_type = @schema.root_type_for_operation(node.operation_type)
58
- push_type(object_type)
59
- @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
59
+ prev_parent_ot = @parent_object_type
60
+ @parent_object_type = @current_object_type
61
+ @current_object_type = object_type
62
+ @path[@path_depth] = "#{node.operation_type}#{node.name ? " #{node.name}" : ""}"
63
+ @path_depth += 1
60
64
  super
61
- @object_types.pop
62
- @path.pop
65
+ @current_object_type = @parent_object_type
66
+ @parent_object_type = prev_parent_ot
67
+ @path_depth -= 1
63
68
  end
64
69
 
65
70
  def on_fragment_definition(node, parent)
66
- on_fragment_with_type(node) do
67
- @path.push("fragment #{node.name}")
68
- super
71
+ object_type = if node.type
72
+ @types.type(node.type.name)
73
+ else
74
+ @current_object_type
69
75
  end
76
+ prev_parent_ot = @parent_object_type
77
+ @parent_object_type = @current_object_type
78
+ @current_object_type = object_type
79
+ @path[@path_depth] = "fragment #{node.name}"
80
+ @path_depth += 1
81
+ super
82
+ @current_object_type = @parent_object_type
83
+ @parent_object_type = prev_parent_ot
84
+ @path_depth -= 1
70
85
  end
71
86
 
87
+ INLINE_FRAGMENT_NO_TYPE = "..."
88
+
72
89
  def on_inline_fragment(node, parent)
73
- on_fragment_with_type(node) do
74
- @path.push("...#{node.type ? " on #{node.type.to_query_string}" : ""}")
75
- super
90
+ if node.type
91
+ object_type = @types.type(node.type.name)
92
+ @path[@path_depth] = @inline_fragment_paths[node.type.name] ||= -"... on #{node.type.to_query_string}"
93
+ @path_depth += 1
94
+ else
95
+ object_type = @current_object_type
96
+ @path[@path_depth] = INLINE_FRAGMENT_NO_TYPE
97
+ @path_depth += 1
76
98
  end
99
+ prev_parent_ot = @parent_object_type
100
+ @parent_object_type = @current_object_type
101
+ @current_object_type = object_type
102
+ super
103
+ @current_object_type = @parent_object_type
104
+ @parent_object_type = prev_parent_ot
105
+ @path_depth -= 1
77
106
  end
78
107
 
79
108
  def on_field(node, parent)
80
- parent_type = @object_types.last
109
+ parent_type = @current_object_type
81
110
  field_definition = @types.field(parent_type, node.name)
82
- @field_definitions.push(field_definition)
83
- if !field_definition.nil?
84
- next_object_type = field_definition.type.unwrap
85
- push_type(next_object_type)
111
+ prev_field_definition = @current_field_definition
112
+ @current_field_definition = field_definition
113
+ prev_parent_ot = @parent_object_type
114
+ @parent_object_type = @current_object_type
115
+ if field_definition
116
+ @current_object_type = @field_unwrapped_types[field_definition] ||= field_definition.type.unwrap
86
117
  else
87
- push_type(nil)
118
+ @current_object_type = nil
88
119
  end
89
- @path.push(node.alias || node.name)
120
+ @path[@path_depth] = node.alias || node.name
121
+ @path_depth += 1
90
122
  super
91
- @field_definitions.pop
92
- @object_types.pop
93
- @path.pop
123
+ @current_field_definition = prev_field_definition
124
+ @current_object_type = @parent_object_type
125
+ @parent_object_type = prev_parent_ot
126
+ @path_depth -= 1
94
127
  end
95
128
 
96
129
  def on_directive(node, parent)
97
130
  directive_defn = @context.schema_directives[node.name]
98
- @directive_definitions.push(directive_defn)
131
+ prev_directive_definition = @current_directive_definition
132
+ @current_directive_definition = directive_defn
99
133
  super
100
- @directive_definitions.pop
134
+ @current_directive_definition = prev_directive_definition
101
135
  end
102
136
 
103
137
  def on_argument(node, parent)
104
- argument_defn = if (arg = @argument_definitions.last)
138
+ argument_defn = if (arg = @current_argument_definition)
105
139
  arg_type = arg.type.unwrap
106
140
  if arg_type.kind.input_object?
107
141
  @types.argument(arg_type, node.name)
108
142
  else
109
143
  nil
110
144
  end
111
- elsif (directive_defn = @directive_definitions.last)
145
+ elsif (directive_defn = @current_directive_definition)
112
146
  @types.argument(directive_defn, node.name)
113
- elsif (field_defn = @field_definitions.last)
147
+ elsif (field_defn = @current_field_definition)
114
148
  @types.argument(field_defn, node.name)
115
149
  else
116
150
  nil
117
151
  end
118
152
 
119
- @argument_definitions.push(argument_defn)
120
- @path.push(node.name)
153
+ prev_parent = @parent_argument_definition
154
+ @parent_argument_definition = @current_argument_definition
155
+ @current_argument_definition = argument_defn
156
+ @path[@path_depth] = node.name
157
+ @path_depth += 1
121
158
  super
122
- @argument_definitions.pop
123
- @path.pop
159
+ @current_argument_definition = @parent_argument_definition
160
+ @parent_argument_definition = prev_parent
161
+ @path_depth -= 1
124
162
  end
125
163
 
126
164
  def on_fragment_spread(node, parent)
127
- @path.push("... #{node.name}")
165
+ @path[@path_depth] = "... #{node.name}"
166
+ @path_depth += 1
128
167
  super
129
- @path.pop
168
+ @path_depth -= 1
130
169
  end
131
170
 
132
171
  def on_input_object(node, parent)
133
- arg_defn = @argument_definitions.last
172
+ arg_defn = @current_argument_definition
134
173
  if arg_defn && arg_defn.type.list?
135
- @path.push(parent.children.index(node))
174
+ @path[@path_depth] = parent.children.index(node)
175
+ @path_depth += 1
136
176
  super
137
- @path.pop
177
+ @path_depth -= 1
138
178
  else
139
179
  super
140
180
  end
@@ -142,48 +182,32 @@ module GraphQL
142
182
 
143
183
  # @return [GraphQL::BaseType] The current object type
144
184
  def type_definition
145
- @object_types.last
185
+ @current_object_type
146
186
  end
147
187
 
148
188
  # @return [GraphQL::BaseType] The type which the current type came from
149
189
  def parent_type_definition
150
- @object_types[-2]
190
+ @parent_object_type
151
191
  end
152
192
 
153
193
  # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
154
194
  def field_definition
155
- @field_definitions.last
195
+ @current_field_definition
156
196
  end
157
197
 
158
198
  # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
159
199
  def directive_definition
160
- @directive_definitions.last
200
+ @current_directive_definition
161
201
  end
162
202
 
163
203
  # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
164
204
  def argument_definition
165
- # Don't get the _last_ one because that's the current one.
166
- # Get the second-to-last one, which is the parent of the current one.
167
- @argument_definitions[-2]
205
+ # Return the parent argument definition (not the current one).
206
+ @parent_argument_definition
168
207
  end
169
208
 
170
209
  private
171
210
 
172
- def on_fragment_with_type(node)
173
- object_type = if node.type
174
- @types.type(node.type.name)
175
- else
176
- @object_types.last
177
- end
178
- push_type(object_type)
179
- yield(node)
180
- @object_types.pop
181
- @path.pop
182
- end
183
-
184
- def push_type(t)
185
- @object_types.push(t)
186
- end
187
211
  end
188
212
 
189
213
  private
@@ -192,7 +216,7 @@ module GraphQL
192
216
  if @context.too_many_errors?
193
217
  throw :too_many_validation_errors
194
218
  end
195
- error.path ||= (path || @path.dup)
219
+ error.path ||= (path || @path[0, @path_depth])
196
220
  context.errors << error
197
221
  end
198
222