graphql 2.4.5 → 2.5.21

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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  4. data/lib/graphql/analysis/analyzer.rb +2 -1
  5. data/lib/graphql/analysis/query_complexity.rb +87 -7
  6. data/lib/graphql/analysis/visitor.rb +37 -40
  7. data/lib/graphql/analysis.rb +12 -9
  8. data/lib/graphql/autoload.rb +1 -0
  9. data/lib/graphql/backtrace/table.rb +118 -55
  10. data/lib/graphql/backtrace.rb +1 -19
  11. data/lib/graphql/current.rb +6 -1
  12. data/lib/graphql/dashboard/application_controller.rb +41 -0
  13. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  14. data/lib/graphql/dashboard/installable.rb +22 -0
  15. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  16. data/lib/graphql/dashboard/limiters.rb +93 -0
  17. data/lib/graphql/dashboard/operation_store.rb +199 -0
  18. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  19. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  20. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  21. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  22. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  23. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  24. data/lib/graphql/dashboard/statics/icon.png +0 -0
  25. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  26. data/lib/graphql/dashboard/subscriptions.rb +97 -0
  27. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  28. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  29. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  30. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  31. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +24 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  37. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  38. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  39. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  40. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  41. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  42. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  43. data/lib/graphql/dashboard.rb +96 -0
  44. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  45. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  46. data/lib/graphql/dataloader/async_dataloader.rb +38 -15
  47. data/lib/graphql/dataloader/null_dataloader.rb +55 -10
  48. data/lib/graphql/dataloader/source.rb +18 -6
  49. data/lib/graphql/dataloader.rb +110 -26
  50. data/lib/graphql/date_encoding_error.rb +1 -1
  51. data/lib/graphql/dig.rb +2 -1
  52. data/lib/graphql/execution/interpreter/resolve.rb +10 -16
  53. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +58 -5
  54. data/lib/graphql/execution/interpreter/runtime.rb +229 -93
  55. data/lib/graphql/execution/interpreter.rb +15 -24
  56. data/lib/graphql/execution/multiplex.rb +7 -6
  57. data/lib/graphql/execution/next/field_resolve_step.rb +690 -0
  58. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  59. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  60. data/lib/graphql/execution/next/runner.rb +389 -0
  61. data/lib/graphql/execution/next/selections_step.rb +37 -0
  62. data/lib/graphql/execution/next.rb +69 -0
  63. data/lib/graphql/execution.rb +1 -0
  64. data/lib/graphql/execution_error.rb +13 -10
  65. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  66. data/lib/graphql/introspection/directive_type.rb +7 -3
  67. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  68. data/lib/graphql/introspection/entry_points.rb +11 -3
  69. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  70. data/lib/graphql/introspection/field_type.rb +13 -5
  71. data/lib/graphql/introspection/input_value_type.rb +21 -13
  72. data/lib/graphql/introspection/type_type.rb +64 -28
  73. data/lib/graphql/invalid_name_error.rb +1 -1
  74. data/lib/graphql/invalid_null_error.rb +25 -16
  75. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  76. data/lib/graphql/language/lexer.rb +16 -5
  77. data/lib/graphql/language/nodes.rb +8 -1
  78. data/lib/graphql/language/parser.rb +16 -8
  79. data/lib/graphql/language/static_visitor.rb +37 -33
  80. data/lib/graphql/language/visitor.rb +59 -55
  81. data/lib/graphql/language.rb +21 -12
  82. data/lib/graphql/pagination/connection.rb +2 -0
  83. data/lib/graphql/pagination/connections.rb +32 -0
  84. data/lib/graphql/query/context.rb +6 -10
  85. data/lib/graphql/query/null_context.rb +9 -3
  86. data/lib/graphql/query/partial.rb +179 -0
  87. data/lib/graphql/query.rb +64 -64
  88. data/lib/graphql/railtie.rb +1 -1
  89. data/lib/graphql/schema/addition.rb +3 -1
  90. data/lib/graphql/schema/always_visible.rb +1 -0
  91. data/lib/graphql/schema/argument.rb +24 -8
  92. data/lib/graphql/schema/build_from_definition.rb +113 -54
  93. data/lib/graphql/schema/directive/flagged.rb +2 -0
  94. data/lib/graphql/schema/directive.rb +52 -2
  95. data/lib/graphql/schema/enum.rb +36 -1
  96. data/lib/graphql/schema/enum_value.rb +1 -1
  97. data/lib/graphql/schema/field/connection_extension.rb +15 -35
  98. data/lib/graphql/schema/field/scope_extension.rb +22 -13
  99. data/lib/graphql/schema/field.rb +101 -51
  100. data/lib/graphql/schema/field_extension.rb +33 -0
  101. data/lib/graphql/schema/input_object.rb +45 -38
  102. data/lib/graphql/schema/interface.rb +2 -1
  103. data/lib/graphql/schema/list.rb +1 -1
  104. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  105. data/lib/graphql/schema/member/has_arguments.rb +56 -19
  106. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  107. data/lib/graphql/schema/member/has_dataloader.rb +79 -0
  108. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  109. data/lib/graphql/schema/member/has_directives.rb +1 -1
  110. data/lib/graphql/schema/member/has_fields.rb +81 -5
  111. data/lib/graphql/schema/member/has_interfaces.rb +3 -3
  112. data/lib/graphql/schema/member/scoped.rb +1 -1
  113. data/lib/graphql/schema/member/type_system_helpers.rb +17 -3
  114. data/lib/graphql/schema/member.rb +6 -0
  115. data/lib/graphql/schema/object.rb +18 -8
  116. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  117. data/lib/graphql/schema/resolver.rb +52 -6
  118. data/lib/graphql/schema/scalar.rb +1 -6
  119. data/lib/graphql/schema/subscription.rb +50 -4
  120. data/lib/graphql/schema/timeout.rb +19 -2
  121. data/lib/graphql/schema/validator/required_validator.rb +71 -14
  122. data/lib/graphql/schema/visibility/migration.rb +3 -2
  123. data/lib/graphql/schema/visibility/profile.rb +115 -23
  124. data/lib/graphql/schema/visibility.rb +49 -32
  125. data/lib/graphql/schema/warden.rb +23 -2
  126. data/lib/graphql/schema.rb +333 -68
  127. data/lib/graphql/static_validation/all_rules.rb +2 -2
  128. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  129. data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -17
  130. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  131. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  132. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  133. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  134. data/lib/graphql/static_validation/validator.rb +6 -1
  135. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  136. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  137. data/lib/graphql/subscriptions/event.rb +12 -1
  138. data/lib/graphql/subscriptions/serialize.rb +1 -1
  139. data/lib/graphql/subscriptions.rb +1 -1
  140. data/lib/graphql/testing/helpers.rb +17 -11
  141. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  142. data/lib/graphql/testing.rb +1 -0
  143. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  144. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  145. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  146. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  147. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  148. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  149. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  150. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  151. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  152. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  153. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  154. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  155. data/lib/graphql/tracing/detailed_trace.rb +156 -0
  156. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  157. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  158. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  159. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  160. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  161. data/lib/graphql/tracing/notifications_trace.rb +184 -34
  162. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  163. data/lib/graphql/tracing/null_trace.rb +9 -0
  164. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  165. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  166. data/lib/graphql/tracing/perfetto_trace.rb +864 -0
  167. data/lib/graphql/tracing/platform_trace.rb +5 -0
  168. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  169. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  170. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  171. data/lib/graphql/tracing/scout_trace.rb +32 -55
  172. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  173. data/lib/graphql/tracing/sentry_trace.rb +64 -94
  174. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  175. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  176. data/lib/graphql/tracing/trace.rb +111 -1
  177. data/lib/graphql/tracing.rb +31 -30
  178. data/lib/graphql/type_kinds.rb +1 -0
  179. data/lib/graphql/types/relay/connection_behaviors.rb +9 -7
  180. data/lib/graphql/types/relay/edge_behaviors.rb +5 -4
  181. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  182. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  183. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  184. data/lib/graphql/unauthorized_error.rb +5 -1
  185. data/lib/graphql/version.rb +1 -1
  186. data/lib/graphql.rb +12 -31
  187. metadata +174 -11
  188. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  189. data/lib/graphql/backtrace/trace.rb +0 -93
  190. data/lib/graphql/backtrace/tracer.rb +0 -80
  191. data/lib/graphql/schema/null_mask.rb +0 -11
  192. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -38,7 +38,7 @@ module GraphQL
38
38
  # Weirdly, procs are applied during coercion, but not methods.
39
39
  # Probably because these methods require a `self`.
40
40
  if arg_defn.prepare.is_a?(Symbol) || context.nil?
41
- prepared_value = arg_defn.prepare_value(self, @ruby_style_hash[ruby_kwargs_key])
41
+ prepared_value = arg_defn.prepare_value(self, @ruby_style_hash[ruby_kwargs_key], context: context)
42
42
  overwrite_argument(ruby_kwargs_key, prepared_value)
43
43
  end
44
44
  end
@@ -59,19 +59,12 @@ module GraphQL
59
59
  else
60
60
  new_h = {}
61
61
  keys.each { |k| @ruby_style_hash.key?(k) && new_h[k] = @ruby_style_hash[k] }
62
- new_h
62
+ new_h
63
63
  end
64
64
  end
65
65
 
66
66
  def prepare
67
- if @context
68
- object = @context[:current_object]
69
- # Pass this object's class with `as` so that messages are rendered correctly from inherited validators
70
- Schema::Validator.validate!(self.class.validators, object, @context, @ruby_style_hash, as: self.class)
71
- self
72
- else
73
- self
74
- end
67
+ self
75
68
  end
76
69
 
77
70
  def unwrap_value(value)
@@ -111,6 +104,14 @@ module GraphQL
111
104
  @ruby_style_hash.dup
112
105
  end
113
106
 
107
+ # @api private
108
+ def validate_for(context)
109
+ object = context[:current_object]
110
+ # Pass this object's class with `as` so that messages are rendered correctly from inherited validators
111
+ Schema::Validator.validate!(self.class.validators, object, context, @ruby_style_hash, as: self.class)
112
+ nil
113
+ end
114
+
114
115
  class << self
115
116
  def authorized?(obj, value, ctx)
116
117
  # Authorize each argument (but this doesn't apply if `prepare` is implemented):
@@ -150,14 +151,8 @@ module GraphQL
150
151
  end
151
152
  end
152
153
  # Add a method access
153
- method_name = argument_defn.keyword
154
154
  suppress_redefinition_warning do
155
- class_eval <<-RUBY, __FILE__, __LINE__
156
- def #{method_name}
157
- self[#{method_name.inspect}]
158
- end
159
- alias_method :#{method_name}, :#{method_name}
160
- RUBY
155
+ define_accessor_method(argument_defn.keyword)
161
156
  end
162
157
  argument_defn
163
158
  end
@@ -181,30 +176,37 @@ module GraphQL
181
176
  return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
182
177
  end
183
178
 
184
- # Inject missing required arguments
185
- missing_required_inputs = ctx.types.arguments(self).reduce({}) do |m, (argument)|
186
- if !input.key?(argument.graphql_name) && argument.type.non_null? && !argument.default_value? && types.argument(self, argument.graphql_name)
187
- m[argument.graphql_name] = nil
188
- end
189
-
190
- m
191
- end
192
179
 
193
180
  result = nil
194
- [input, missing_required_inputs].each do |args_to_validate|
195
- args_to_validate.each do |argument_name, value|
196
- argument = types.argument(self, argument_name)
197
- # Items in the input that are unexpected
198
- if argument.nil?
199
- result ||= Query::InputValidationResult.new
200
- result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
201
- else
202
- # Items in the input that are expected, but have invalid values
203
- argument_result = argument.type.validate_input(value, ctx)
181
+
182
+
183
+ input.each do |argument_name, value|
184
+ argument = types.argument(self, argument_name)
185
+ if argument.nil? && ctx.is_a?(Query::NullContext) && argument_name.is_a?(Symbol)
186
+ # Validating definition directive arguments which come in as Symbols
187
+ argument = types.arguments(self).find { |arg| arg.keyword == argument_name }
188
+ end
189
+ # Items in the input that are unexpected
190
+ if argument.nil?
191
+ result ||= Query::InputValidationResult.new
192
+ result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
193
+ else
194
+ # Items in the input that are expected, but have invalid values
195
+ argument_result = argument.type.validate_input(value, ctx)
196
+ if !argument_result.valid?
204
197
  result ||= Query::InputValidationResult.new
205
- if !argument_result.valid?
206
- result.merge_result!(argument_name, argument_result)
207
- end
198
+ result.merge_result!(argument_name, argument_result)
199
+ end
200
+ end
201
+ end
202
+
203
+ # Check for missing non-null arguments
204
+ ctx.types.arguments(self).each do |argument|
205
+ if !input.key?(argument.graphql_name) && argument.type.non_null? && !argument.default_value?
206
+ result ||= Query::InputValidationResult.new
207
+ argument_result = argument.type.validate_input(nil, ctx)
208
+ if !argument_result.valid?
209
+ result.merge_result!(argument.graphql_name, argument_result)
208
210
  end
209
211
  end
210
212
  end
@@ -293,6 +295,11 @@ module GraphQL
293
295
  ensure
294
296
  $VERBOSE = verbose
295
297
  end
298
+
299
+ def define_accessor_method(method_name)
300
+ define_method(method_name) { self[method_name] }
301
+ alias_method(method_name, method_name)
302
+ end
296
303
  end
297
304
 
298
305
  private
@@ -13,6 +13,7 @@ module GraphQL
13
13
  include GraphQL::Schema::Member::Scoped
14
14
  include GraphQL::Schema::Member::HasAstNode
15
15
  include GraphQL::Schema::Member::HasUnresolvedTypeError
16
+ include GraphQL::Schema::Member::HasDataloader
16
17
  include GraphQL::Schema::Member::HasDirectives
17
18
  include GraphQL::Schema::Member::HasInterfaces
18
19
 
@@ -29,7 +30,7 @@ module GraphQL
29
30
  const_set(:DefinitionMethods, defn_methods_module)
30
31
  extend(self::DefinitionMethods)
31
32
  end
32
- self::DefinitionMethods.module_eval(&block)
33
+ self::DefinitionMethods.module_exec(&block)
33
34
  end
34
35
 
35
36
  # @see {Schema::Warden} hides interfaces without visible implementations
@@ -4,7 +4,7 @@ module GraphQL
4
4
  class Schema
5
5
  # Represents a list type in the schema.
6
6
  # Wraps a {Schema::Member} as a list type.
7
- # @see {Schema::Member::TypeSystemHelpers#to_list_type}
7
+ # @see Schema::Member::TypeSystemHelpers#to_list_type Create a list type from another GraphQL type
8
8
  class List < GraphQL::Schema::Wrapper
9
9
  include Schema::Member::ValidatesInput
10
10
 
@@ -130,7 +130,7 @@ module GraphQL
130
130
  true
131
131
  end
132
132
 
133
- def default_relay
133
+ def default_relay?
134
134
  false
135
135
  end
136
136
 
@@ -14,29 +14,52 @@ module GraphQL
14
14
  cls.extend(ClassConfigured)
15
15
  end
16
16
 
17
- # @see {GraphQL::Schema::Argument#initialize} for parameters
18
- # @return [GraphQL::Schema::Argument] An instance of {argument_class}, created from `*args`
19
- def argument(*args, **kwargs, &block)
20
- kwargs[:owner] = self
21
- loads = kwargs[:loads]
22
- if loads
23
- name = args[0]
24
- name_as_string = name.to_s
25
-
26
- inferred_arg_name = case name_as_string
17
+ # @param arg_name [Symbol] The underscore-cased name of this argument, `name:` keyword also accepted
18
+ # @param type_expr The GraphQL type of this argument; `type:` keyword also accepted
19
+ # @param desc [String] Argument description, `description:` keyword also accepted
20
+ # @option kwargs [Boolean, :nullable] :required if true, this argument is non-null; if false, this argument is nullable. If `:nullable`, then the argument must be provided, though it may be `null`.
21
+ # @option kwargs [String] :description Positional argument also accepted
22
+ # @option kwargs [Class, Array<Class>] :type Input type; positional argument also accepted
23
+ # @option kwargs [Symbol] :name positional argument also accepted
24
+ # @option kwargs [Object] :default_value
25
+ # @option kwargs [Class, Array<Class>] :loads A GraphQL type to load for the given ID when one is present
26
+ # @option kwargs [Symbol] :as Override the keyword name when passed to a method
27
+ # @option kwargs [Symbol] :prepare A method to call to transform this argument's valuebefore sending it to field resolution
28
+ # @option kwargs [Boolean] :camelize if true, the name will be camelized when building the schema
29
+ # @option kwargs [Boolean] :from_resolver if true, a Resolver class defined this argument
30
+ # @option kwargs [Hash{Class => Hash}] :directives
31
+ # @option kwargs [String] :deprecation_reason
32
+ # @option kwargs [String] :comment Private, used by GraphQL-Ruby when parsing GraphQL schema files
33
+ # @option kwargs [GraphQL::Language::Nodes::InputValueDefinition] :ast_node Private, used by GraphQL-Ruby when parsing schema files
34
+ # @option kwargs [Hash, nil] :validates Options for building validators, if any should be applied
35
+ # @option kwargs [Boolean] :replace_null_with_default if `true`, incoming values of `null` will be replaced with the configured `default_value`
36
+ # @param definition_block [Proc] Called with the newly-created {Argument}
37
+ # @param kwargs [Hash] Keywords for defining an argument. Any keywords not documented here must be handled by your base Argument class.
38
+ # @return [GraphQL::Schema::Argument] An instance of {argument_class} created from these arguments
39
+ def argument(arg_name = nil, type_expr = nil, desc = nil, **kwargs, &definition_block)
40
+ if kwargs[:loads]
41
+ loads_name = arg_name || kwargs[:name]
42
+ loads_name_as_string = loads_name.to_s
43
+
44
+ inferred_arg_name = case loads_name_as_string
27
45
  when /_id$/
28
- name_as_string.sub(/_id$/, "").to_sym
46
+ loads_name_as_string.sub(/_id$/, "").to_sym
29
47
  when /_ids$/
30
- name_as_string.sub(/_ids$/, "")
48
+ loads_name_as_string.sub(/_ids$/, "")
31
49
  .sub(/([^s])$/, "\\1s")
32
50
  .to_sym
33
51
  else
34
- name
52
+ loads_name
35
53
  end
36
54
 
37
55
  kwargs[:as] ||= inferred_arg_name
38
56
  end
39
- arg_defn = self.argument_class.new(*args, **kwargs, &block)
57
+ kwargs[:owner] = self
58
+ arg_defn = self.argument_class.new(
59
+ arg_name, type_expr, desc,
60
+ **kwargs,
61
+ &definition_block
62
+ )
40
63
  add_argument(arg_defn)
41
64
  arg_defn
42
65
  end
@@ -359,7 +382,8 @@ module GraphQL
359
382
  if application_object.nil?
360
383
  nil
361
384
  else
362
- maybe_lazy_resolve_type = context.schema.resolve_type(argument.loads, application_object, context)
385
+ arg_loads_type = argument.loads
386
+ maybe_lazy_resolve_type = context.schema.resolve_type(arg_loads_type, application_object, context)
363
387
  context.query.after_lazy(maybe_lazy_resolve_type) do |resolve_type_result|
364
388
  if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2
365
389
  application_object_type, application_object = resolve_type_result
@@ -368,10 +392,17 @@ module GraphQL
368
392
  # application_object is already assigned
369
393
  end
370
394
 
371
- if !(
372
- context.types.possible_types(argument.loads).include?(application_object_type) ||
373
- context.types.loadable?(argument.loads, context)
374
- )
395
+ passes_possible_types_check = if context.types.loadable?(arg_loads_type, context)
396
+ if arg_loads_type.kind.abstract?
397
+ # This union/interface is used in `loads:` but not otherwise visible to this query
398
+ context.types.loadable_possible_types(arg_loads_type, context).include?(application_object_type)
399
+ else
400
+ true
401
+ end
402
+ else
403
+ context.types.possible_types(arg_loads_type).include?(application_object_type)
404
+ end
405
+ if !passes_possible_types_check
375
406
  err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object)
376
407
  application_object = load_application_object_failed(err)
377
408
  end
@@ -405,6 +436,12 @@ module GraphQL
405
436
  end
406
437
  end
407
438
 
439
+ # Called when an argument's `loads:` configuration fails to fetch an application object.
440
+ # By default, this method raises the given error, but you can override it to handle failures differently.
441
+ #
442
+ # @param err [GraphQL::LoadApplicationObjectFailedError] The error that occurred
443
+ # @return [Object, nil] If a value is returned, it will be used instead of the failed load
444
+ # @api public
408
445
  def load_application_object_failed(err)
409
446
  raise err
410
447
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Member
5
+ module HasAuthorization
6
+ def self.included(child_class)
7
+ child_class.include(InstanceConfigured)
8
+ end
9
+
10
+ def self.extended(child_class)
11
+ child_class.extend(ClassConfigured)
12
+ child_class.class_exec do
13
+ @authorizes = false
14
+ end
15
+ end
16
+
17
+ def authorized?(object, context)
18
+ true
19
+ end
20
+
21
+ module InstanceConfigured
22
+ def authorizes?(context)
23
+ raise RequiredImplementationMissingError, "#{self.class} must implement #authorizes?(context)"
24
+ end
25
+ end
26
+
27
+ module ClassConfigured
28
+ def authorizes?(context)
29
+ method(:authorized?).owner != GraphQL::Schema::Member::HasAuthorization
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ # @api public
7
+ # Shared methods for working with {Dataloader} inside GraphQL runtime objects.
8
+ module HasDataloader
9
+ # @return [GraphQL::Dataloader] The dataloader for the currently-running query
10
+ def dataloader
11
+ context.dataloader
12
+ end
13
+
14
+ # A shortcut method for loading a key from a source.
15
+ # Identical to `dataloader.with(source_class, *source_args).load(load_key)`
16
+ # @param source_class [Class<GraphQL::Dataloader::Source>]
17
+ # @param source_args [Array<Object>] Any extra parameters defined in `source_class`'s `initialize` method
18
+ # @param load_key [Object] The key to look up using `def fetch`
19
+ def dataload(source_class, *source_args, load_key)
20
+ dataloader.with(source_class, *source_args).load(load_key)
21
+ end
22
+
23
+ # A shortcut method for loading many keys from a source.
24
+ # Identical to `dataloader.with(source_class, *source_args).load_all(load_keys)`
25
+ #
26
+ # @example
27
+ # field :score, Integer, resolve_batch: true
28
+ #
29
+ # def self.score(posts)
30
+ # dataload_all(PostScoreSource, posts.map(&:id))
31
+ # end
32
+ #
33
+ # @param source_class [Class<GraphQL::Dataloader::Source>]
34
+ # @param source_args [Array<Object>] Any extra parameters defined in `source_class`'s `initialize` method
35
+ # @param load_keys [Array<Object>] The keys to look up using `def fetch`
36
+ def dataload_all(source_class, *source_args, load_keys)
37
+ dataloader.with(source_class, *source_args).load_all(load_keys)
38
+ end
39
+
40
+ # Find an object with ActiveRecord via {Dataloader::ActiveRecordSource}.
41
+ # @param model [Class<ActiveRecord::Base>]
42
+ # @param find_by_value [Object] Usually an `id`, might be another value if `find_by:` is also provided
43
+ # @param find_by [Symbol, String] A column name to look the record up by. (Defaults to the model's primary key.)
44
+ # @return [ActiveRecord::Base, nil]
45
+ # @example Finding a record by ID
46
+ # dataload_record(Post, 5) # Like `Post.find(5)`, but dataloaded
47
+ # @example Finding a record by another attribute
48
+ # dataload_record(User, "matz", find_by: :handle) # Like `User.find_by(handle: "matz")`, but dataloaded
49
+ def dataload_record(model, find_by_value, find_by: nil)
50
+ source = if find_by
51
+ dataloader.with(Dataloader::ActiveRecordSource, model, find_by: find_by)
52
+ else
53
+ dataloader.with(Dataloader::ActiveRecordSource, model)
54
+ end
55
+
56
+ source.load(find_by_value)
57
+ end
58
+
59
+ # Look up an associated record using a Rails association (via {Dataloader::ActiveRecordAssociationSource})
60
+ # @param association_name [Symbol] A `belongs_to` or `has_one` association. (If a `has_many` association is named here, it will be selected without pagination.)
61
+ # @param record [ActiveRecord::Base] The object that the association belongs to.
62
+ # @param scope [ActiveRecord::Relation] A scope to look up the associated record in
63
+ # @return [ActiveRecord::Base, nil] The associated record, if there is one
64
+ # @example Looking up a belongs_to on the current object
65
+ # dataload_association(:parent) # Equivalent to `object.parent`, but dataloaded
66
+ # @example Looking up an associated record on some other object
67
+ # dataload_association(comment, :post) # Equivalent to `comment.post`, but dataloaded
68
+ def dataload_association(record = object, association_name, scope: nil)
69
+ source = if scope
70
+ dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name, scope)
71
+ else
72
+ dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name)
73
+ end
74
+ source.load(record)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -18,6 +18,21 @@ module GraphQL
18
18
  directive(GraphQL::Schema::Directive::Deprecated, reason: text)
19
19
  end
20
20
  end
21
+
22
+ def self.extended(child_class)
23
+ super
24
+ child_class.extend(ClassMethods)
25
+ end
26
+
27
+ module ClassMethods
28
+ def deprecation_reason(new_reason = NOT_CONFIGURED)
29
+ if NOT_CONFIGURED.equal?(new_reason)
30
+ super()
31
+ else
32
+ self.deprecation_reason = new_reason
33
+ end
34
+ end
35
+ end
21
36
  end
22
37
  end
23
38
  end
@@ -6,7 +6,7 @@ module GraphQL
6
6
  module HasDirectives
7
7
  def self.extended(child_cls)
8
8
  super
9
- child_cls.module_eval { self.own_directives = nil }
9
+ child_cls.module_exec { self.own_directives = nil }
10
10
  end
11
11
 
12
12
  def inherited(child_cls)
@@ -5,11 +5,87 @@ module GraphQL
5
5
  class Member
6
6
  # Shared code for Objects, Interfaces, Mutations, Subscriptions
7
7
  module HasFields
8
+ include EmptyObjects
8
9
  # Add a field to this object or interface with the given definition
9
- # @see {GraphQL::Schema::Field#initialize} for method signature
10
+ # @param name_positional [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API); `name:` keyword is also accepted
11
+ # @param type_positional [Class, GraphQL::BaseType, Array] The return type of this field; `type:` keyword is also accepted
12
+ # @param desc_positional [String] Field description; `description:` keyword is also accepted
13
+ # @option kwargs [Symbol] :name The underscore-cased version of this field name (will be camelized for the GraphQL API); positional argument also accepted
14
+ # @option kwargs [Class, GraphQL::BaseType, Array] :type The return type of this field; positional argument is also accepted
15
+ # @option kwargs [Boolean] :null (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null`
16
+ # @option kwargs [String] :description Field description; positional argument also accepted
17
+ # @option kwargs [String] :comment Field comment
18
+ # @option kwargs [String] :deprecation_reason If present, the field is marked "deprecated" with this message
19
+ # @option kwargs [Symbol] :method The method to call on the underlying object to resolve this field (defaults to `name`)
20
+ # @option kwargs [String, Symbol] :hash_key The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`)
21
+ # @option kwargs [Array<String, Symbol>] :dig The nested hash keys to lookup on the underlying hash to resolve this field using dig
22
+ # @option kwargs [Symbol, true] :resolver_method The method on the type to call to resolve this field (defaults to `name`)
23
+ # @option kwargs [Symbol, true] :resolve_static Used by {Schema.execute_next} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments`
24
+ # @option kwargs [Symbol, true] :resolve_batch Used by {Schema.execute_next} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`.
25
+ # @option kwargs [Symbol, true] :resolve_each Used by {Schema.execute_next} to get a value value for each item. Called on the owner type class with `object, context, **arguments`.
26
+ # @option kwargs [Symbol, true] :resolve_legacy_instance_method Used by {Schema.execute_next} to get a value value for each item. Calls an instance method on the object type class.
27
+ # @option kwargs [Boolean] :connection `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
28
+ # @option kwargs [Class] :connection_extension The extension to add, to implement connections. If `nil`, no extension is added.
29
+ # @option kwargs [Integer, nil] :max_page_size For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
30
+ # @option kwargs [Integer, nil] :default_page_size For connections, the default number of items to return from this field, or `nil` to return unlimited results.
31
+ # @option kwargs [Boolean] :introspection If true, this field will be marked as `#introspection?` and the name may begin with `__`
32
+ # @option kwargs [{String=>GraphQL::Schema::Argument, Hash}] :arguments Arguments for this field (may be added in the block, also)
33
+ # @option kwargs [Boolean] :camelize If true, the field name will be camelized when building the schema
34
+ # @option kwargs [Numeric] :complexity When provided, set the complexity for this field
35
+ # @option kwargs [Boolean] :scope If true, the return type's `.scope_items` method will be called on the return value
36
+ # @option kwargs [Symbol, String] :subscription_scope A key in `context` which will be used to scope subscription payloads
37
+ # @option kwargs [Array<Class, Hash<Class => Object>>] :extensions Named extensions to apply to this field (see also {#extension})
38
+ # @option kwargs [Hash{Class => Hash}] :directives Directives to apply to this field
39
+ # @option kwargs [Boolean] :trace If true, a {GraphQL::Tracing} tracer will measure this scalar field
40
+ # @option kwargs [Boolean] :broadcastable Whether or not this field can be distributed in subscription broadcasts
41
+ # @option kwargs [Language::Nodes::FieldDefinition, nil] :ast_node If this schema was parsed from definition, this AST node defined the field
42
+ # @option kwargs [Boolean] :method_conflict_warning If false, skip the warning if this field's method conflicts with a built-in method
43
+ # @option kwargs [Array<Hash>] :validates Configurations for validating this field
44
+ # @option kwargs [Object] :fallback_value A fallback value if the method is not defined
45
+ # @option kwargs [Class<GraphQL::Schema::Mutation>] :mutation
46
+ # @option kwargs [Class<GraphQL::Schema::Resolver>] :resolver
47
+ # @option kwargs [Class<GraphQL::Schema::Subscription>] :subscription
48
+ # @option kwargs [Boolean] :dynamic_introspection (Private, used by GraphQL-Ruby)
49
+ # @option kwargs [Boolean] :relay_node_field (Private, used by GraphQL-Ruby)
50
+ # @option kwargs [Boolean] :relay_nodes_field (Private, used by GraphQL-Ruby)
51
+ # @option kwargs [Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>] :extras Extra arguments to be injected into the resolver for this field
52
+ # @param kwargs [Hash] Keywords for defining the field. Any not documented here will be passed to your base field class where they must be handled.
53
+ # @param definition_block [Proc] an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new {Field}.
54
+ # @yieldparam field [GraphQL::Schema::Field] The newly-created field instance
55
+ # @yieldreturn [void]
10
56
  # @return [GraphQL::Schema::Field]
11
- def field(*args, **kwargs, &block)
12
- field_defn = field_class.from_options(*args, owner: self, **kwargs, &block)
57
+ def field(name_positional = nil, type_positional = nil, desc_positional = nil, **kwargs, &definition_block)
58
+ resolver = kwargs.delete(:resolver)
59
+ mutation = kwargs.delete(:mutation)
60
+ subscription = kwargs.delete(:subscription)
61
+ if (resolver_class = resolver || mutation || subscription)
62
+ # Add a reference to that parent class
63
+ kwargs[:resolver_class] = resolver_class
64
+ end
65
+
66
+ kwargs[:name] ||= name_positional
67
+ if !type_positional.nil?
68
+ if desc_positional
69
+ if kwargs[:description]
70
+ raise ArgumentError, "Provide description as a positional argument or `description:` keyword, but not both (#{desc_positional.inspect}, #{kwargs[:description].inspect})"
71
+ end
72
+
73
+ kwargs[:description] = desc_positional
74
+ kwargs[:type] = type_positional
75
+ elsif (resolver || mutation) && type_positional.is_a?(String)
76
+ # The return type should be copied from the resolver, and the second positional argument is the description
77
+ kwargs[:description] = type_positional
78
+ else
79
+ kwargs[:type] = type_positional
80
+ end
81
+
82
+ if type_positional.is_a?(Class) && type_positional < GraphQL::Schema::Mutation
83
+ raise ArgumentError, "Use `field #{name_positional.inspect}, mutation: Mutation, ...` to provide a mutation to this field instead"
84
+ end
85
+ end
86
+
87
+ kwargs[:owner] = self
88
+ field_defn = field_class.new(**kwargs, &definition_block)
13
89
  add_field(field_defn)
14
90
  field_defn
15
91
  end
@@ -202,7 +278,7 @@ module GraphQL
202
278
 
203
279
  def inherited(subclass)
204
280
  super
205
- subclass.class_eval do
281
+ subclass.class_exec do
206
282
  @own_fields ||= nil
207
283
  @field_class ||= nil
208
284
  @has_no_fields ||= false
@@ -232,7 +308,7 @@ module GraphQL
232
308
  end
233
309
  end
234
310
 
235
- # @param [GraphQL::Schema::Field]
311
+ # @param field_defn [GraphQL::Schema::Field]
236
312
  # @return [String] A warning to give when this field definition might conflict with a built-in method
237
313
  def conflict_field_name_warning(field_defn)
238
314
  "#{self.graphql_name}'s `field :#{field_defn.original_name}` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_#{field_defn.resolver_method}` and `def resolve_#{field_defn.resolver_method}`). Or use `method_conflict_warning: false` to suppress this warning."
@@ -8,8 +8,8 @@ module GraphQL
8
8
  new_memberships = []
9
9
  new_interfaces.each do |int|
10
10
  if int.is_a?(Module)
11
- unless int.include?(GraphQL::Schema::Interface)
12
- raise "#{int} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules."
11
+ unless int.include?(GraphQL::Schema::Interface) && !int.is_a?(Class)
12
+ raise "#{int.respond_to?(:graphql_name) ? "#{int.graphql_name} (#{int})" : int.inspect} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules."
13
13
  end
14
14
 
15
15
  new_memberships << int.type_membership_class.new(int, self, **options)
@@ -133,7 +133,7 @@ module GraphQL
133
133
 
134
134
  def inherited(subclass)
135
135
  super
136
- subclass.class_eval do
136
+ subclass.class_exec do
137
137
  @own_interface_type_memberships ||= nil
138
138
  end
139
139
  end
@@ -30,7 +30,7 @@ module GraphQL
30
30
 
31
31
  def inherited(subclass)
32
32
  super
33
- subclass.class_eval do
33
+ subclass.class_exec do
34
34
  @reauthorize_scoped_objects = nil
35
35
  end
36
36
  end
@@ -12,12 +12,26 @@ module GraphQL
12
12
 
13
13
  # @return [Schema::NonNull] Make a non-null-type representation of this type
14
14
  def to_non_null_type
15
- @to_non_null_type ||= GraphQL::Schema::NonNull.new(self)
15
+ @to_non_null_type || begin
16
+ t = GraphQL::Schema::NonNull.new(self)
17
+ if frozen?
18
+ t
19
+ else
20
+ @to_non_null_type = t
21
+ end
22
+ end
16
23
  end
17
24
 
18
25
  # @return [Schema::List] Make a list-type representation of this type
19
26
  def to_list_type
20
- @to_list_type ||= GraphQL::Schema::List.new(self)
27
+ @to_list_type || begin
28
+ t = GraphQL::Schema::List.new(self)
29
+ if frozen?
30
+ t
31
+ else
32
+ @to_list_type = t
33
+ end
34
+ end
21
35
  end
22
36
 
23
37
  # @return [Boolean] true if this is a non-nullable type. A nullable list of non-nullables is considered nullable.
@@ -42,7 +56,7 @@ module GraphQL
42
56
  private
43
57
 
44
58
  def inherited(subclass)
45
- subclass.class_eval do
59
+ subclass.class_exec do
46
60
  @to_non_null_type ||= nil
47
61
  @to_list_type ||= nil
48
62
  end
@@ -2,6 +2,8 @@
2
2
  require 'graphql/schema/member/base_dsl_methods'
3
3
  require 'graphql/schema/member/graphql_type_names'
4
4
  require 'graphql/schema/member/has_ast_node'
5
+ require 'graphql/schema/member/has_authorization'
6
+ require 'graphql/schema/member/has_dataloader'
5
7
  require 'graphql/schema/member/has_directives'
6
8
  require 'graphql/schema/member/has_deprecation_reason'
7
9
  require 'graphql/schema/member/has_interfaces'
@@ -30,6 +32,10 @@ module GraphQL
30
32
  extend HasPath
31
33
  extend HasAstNode
32
34
  extend HasDirectives
35
+
36
+ def self.authorizes?(_ctx)
37
+ false
38
+ end
33
39
  end
34
40
  end
35
41
  end