graphql 2.2.17 → 2.5.16

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 (240) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  3. data/lib/generators/graphql/install_generator.rb +46 -0
  4. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  5. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  6. data/lib/generators/graphql/templates/schema.erb +3 -0
  7. data/lib/generators/graphql/type_generator.rb +1 -1
  8. data/lib/graphql/analysis/analyzer.rb +90 -0
  9. data/lib/graphql/analysis/field_usage.rb +82 -0
  10. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  11. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  12. data/lib/graphql/analysis/query_complexity.rb +263 -0
  13. data/lib/graphql/analysis/{ast/query_depth.rb → query_depth.rb} +23 -25
  14. data/lib/graphql/analysis/visitor.rb +280 -0
  15. data/lib/graphql/analysis.rb +95 -1
  16. data/lib/graphql/autoload.rb +38 -0
  17. data/lib/graphql/backtrace/table.rb +118 -55
  18. data/lib/graphql/backtrace.rb +1 -19
  19. data/lib/graphql/current.rb +57 -0
  20. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  21. data/lib/graphql/dashboard/installable.rb +22 -0
  22. data/lib/graphql/dashboard/limiters.rb +93 -0
  23. data/lib/graphql/dashboard/operation_store.rb +199 -0
  24. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  25. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  26. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  27. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  28. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  29. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  30. data/lib/graphql/dashboard/statics/icon.png +0 -0
  31. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  37. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  38. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  39. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  40. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  41. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  42. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  43. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  44. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  45. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  46. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  47. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  48. data/lib/graphql/dashboard.rb +158 -0
  49. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  50. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  51. data/lib/graphql/dataloader/async_dataloader.rb +46 -19
  52. data/lib/graphql/dataloader/null_dataloader.rb +51 -10
  53. data/lib/graphql/dataloader/source.rb +20 -9
  54. data/lib/graphql/dataloader.rb +153 -45
  55. data/lib/graphql/date_encoding_error.rb +1 -1
  56. data/lib/graphql/dig.rb +2 -1
  57. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  58. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  59. data/lib/graphql/execution/interpreter/resolve.rb +23 -25
  60. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +63 -5
  61. data/lib/graphql/execution/interpreter/runtime.rb +321 -222
  62. data/lib/graphql/execution/interpreter.rb +23 -30
  63. data/lib/graphql/execution/lookahead.rb +18 -11
  64. data/lib/graphql/execution/multiplex.rb +6 -5
  65. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  66. data/lib/graphql/introspection/directive_type.rb +1 -1
  67. data/lib/graphql/introspection/entry_points.rb +2 -2
  68. data/lib/graphql/introspection/field_type.rb +1 -1
  69. data/lib/graphql/introspection/schema_type.rb +6 -11
  70. data/lib/graphql/introspection/type_type.rb +5 -5
  71. data/lib/graphql/invalid_name_error.rb +1 -1
  72. data/lib/graphql/invalid_null_error.rb +20 -17
  73. data/lib/graphql/language/cache.rb +13 -0
  74. data/lib/graphql/language/comment.rb +18 -0
  75. data/lib/graphql/language/document_from_schema_definition.rb +64 -35
  76. data/lib/graphql/language/lexer.rb +72 -42
  77. data/lib/graphql/language/nodes.rb +93 -52
  78. data/lib/graphql/language/parser.rb +168 -61
  79. data/lib/graphql/language/printer.rb +31 -15
  80. data/lib/graphql/language/sanitized_printer.rb +1 -1
  81. data/lib/graphql/language.rb +61 -1
  82. data/lib/graphql/pagination/connection.rb +1 -1
  83. data/lib/graphql/query/context/scoped_context.rb +1 -1
  84. data/lib/graphql/query/context.rb +46 -47
  85. data/lib/graphql/query/null_context.rb +3 -5
  86. data/lib/graphql/query/partial.rb +179 -0
  87. data/lib/graphql/query/validation_pipeline.rb +2 -2
  88. data/lib/graphql/query/variable_validation_error.rb +1 -1
  89. data/lib/graphql/query.rb +123 -69
  90. data/lib/graphql/railtie.rb +7 -0
  91. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  92. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  93. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  94. data/lib/graphql/rubocop.rb +2 -0
  95. data/lib/graphql/schema/addition.rb +26 -13
  96. data/lib/graphql/schema/always_visible.rb +7 -2
  97. data/lib/graphql/schema/argument.rb +57 -8
  98. data/lib/graphql/schema/build_from_definition.rb +116 -49
  99. data/lib/graphql/schema/directive/flagged.rb +4 -2
  100. data/lib/graphql/schema/directive.rb +54 -2
  101. data/lib/graphql/schema/enum.rb +107 -24
  102. data/lib/graphql/schema/enum_value.rb +10 -2
  103. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  104. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  105. data/lib/graphql/schema/field.rb +134 -45
  106. data/lib/graphql/schema/field_extension.rb +1 -1
  107. data/lib/graphql/schema/has_single_input_argument.rb +6 -2
  108. data/lib/graphql/schema/input_object.rb +122 -64
  109. data/lib/graphql/schema/interface.rb +23 -5
  110. data/lib/graphql/schema/introspection_system.rb +6 -17
  111. data/lib/graphql/schema/late_bound_type.rb +4 -0
  112. data/lib/graphql/schema/list.rb +3 -3
  113. data/lib/graphql/schema/loader.rb +3 -2
  114. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  115. data/lib/graphql/schema/member/has_arguments.rb +44 -58
  116. data/lib/graphql/schema/member/has_dataloader.rb +62 -0
  117. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  118. data/lib/graphql/schema/member/has_directives.rb +4 -4
  119. data/lib/graphql/schema/member/has_fields.rb +26 -6
  120. data/lib/graphql/schema/member/has_interfaces.rb +6 -6
  121. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  122. data/lib/graphql/schema/member/has_validators.rb +1 -1
  123. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  124. data/lib/graphql/schema/member/type_system_helpers.rb +17 -4
  125. data/lib/graphql/schema/member.rb +1 -0
  126. data/lib/graphql/schema/mutation.rb +7 -0
  127. data/lib/graphql/schema/object.rb +25 -8
  128. data/lib/graphql/schema/printer.rb +1 -0
  129. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  130. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  131. data/lib/graphql/schema/resolver.rb +29 -23
  132. data/lib/graphql/schema/scalar.rb +1 -6
  133. data/lib/graphql/schema/subscription.rb +52 -6
  134. data/lib/graphql/schema/timeout.rb +19 -2
  135. data/lib/graphql/schema/type_expression.rb +2 -2
  136. data/lib/graphql/schema/union.rb +1 -1
  137. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  138. data/lib/graphql/schema/validator/required_validator.rb +92 -11
  139. data/lib/graphql/schema/validator.rb +3 -1
  140. data/lib/graphql/schema/visibility/migration.rb +188 -0
  141. data/lib/graphql/schema/visibility/profile.rb +445 -0
  142. data/lib/graphql/schema/visibility/visit.rb +190 -0
  143. data/lib/graphql/schema/visibility.rb +311 -0
  144. data/lib/graphql/schema/warden.rb +190 -20
  145. data/lib/graphql/schema.rb +695 -167
  146. data/lib/graphql/static_validation/all_rules.rb +2 -2
  147. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  148. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  149. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  150. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  151. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  152. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  153. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  154. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  155. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  156. data/lib/graphql/static_validation/rules/fields_will_merge.rb +88 -25
  157. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  158. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  159. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  160. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  161. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  162. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  163. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  164. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  165. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
  166. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  167. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  168. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +7 -3
  169. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  170. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  171. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  172. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  173. data/lib/graphql/static_validation/validation_context.rb +18 -2
  174. data/lib/graphql/static_validation/validator.rb +6 -1
  175. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +5 -3
  176. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  177. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  178. data/lib/graphql/subscriptions/event.rb +13 -2
  179. data/lib/graphql/subscriptions/serialize.rb +1 -1
  180. data/lib/graphql/subscriptions.rb +7 -5
  181. data/lib/graphql/testing/helpers.rb +48 -16
  182. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  183. data/lib/graphql/testing.rb +1 -0
  184. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  185. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  186. data/lib/graphql/tracing/appoptics_trace.rb +5 -1
  187. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  188. data/lib/graphql/tracing/appsignal_trace.rb +32 -59
  189. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  190. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  191. data/lib/graphql/tracing/data_dog_trace.rb +46 -162
  192. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  193. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  194. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  195. data/lib/graphql/tracing/detailed_trace.rb +141 -0
  196. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  197. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  198. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  199. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  200. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  201. data/lib/graphql/tracing/notifications_trace.rb +183 -37
  202. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  203. data/lib/graphql/tracing/null_trace.rb +9 -0
  204. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  205. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  206. data/lib/graphql/tracing/perfetto_trace.rb +818 -0
  207. data/lib/graphql/tracing/platform_tracing.rb +1 -1
  208. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  209. data/lib/graphql/tracing/prometheus_trace.rb +73 -73
  210. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  211. data/lib/graphql/tracing/scout_trace.rb +32 -58
  212. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  213. data/lib/graphql/tracing/sentry_trace.rb +64 -98
  214. data/lib/graphql/tracing/statsd_trace.rb +33 -45
  215. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  216. data/lib/graphql/tracing/trace.rb +111 -1
  217. data/lib/graphql/tracing.rb +31 -30
  218. data/lib/graphql/type_kinds.rb +2 -1
  219. data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
  220. data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
  221. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  222. data/lib/graphql/types.rb +18 -11
  223. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  224. data/lib/graphql/version.rb +1 -1
  225. data/lib/graphql.rb +64 -54
  226. metadata +197 -22
  227. data/lib/graphql/analysis/ast/analyzer.rb +0 -91
  228. data/lib/graphql/analysis/ast/field_usage.rb +0 -82
  229. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  230. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  231. data/lib/graphql/analysis/ast/query_complexity.rb +0 -182
  232. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  233. data/lib/graphql/analysis/ast.rb +0 -94
  234. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  235. data/lib/graphql/backtrace/trace.rb +0 -93
  236. data/lib/graphql/backtrace/tracer.rb +0 -80
  237. data/lib/graphql/language/token.rb +0 -34
  238. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  239. data/lib/graphql/schema/null_mask.rb +0 -11
  240. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -0,0 +1,62 @@
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
+ # Find an object with ActiveRecord via {Dataloader::ActiveRecordSource}.
24
+ # @param model [Class<ActiveRecord::Base>]
25
+ # @param find_by_value [Object] Usually an `id`, might be another value if `find_by:` is also provided
26
+ # @param find_by [Symbol, String] A column name to look the record up by. (Defaults to the model's primary key.)
27
+ # @return [ActiveRecord::Base, nil]
28
+ # @example Finding a record by ID
29
+ # dataload_record(Post, 5) # Like `Post.find(5)`, but dataloaded
30
+ # @example Finding a record by another attribute
31
+ # dataload_record(User, "matz", find_by: :handle) # Like `User.find_by(handle: "matz")`, but dataloaded
32
+ def dataload_record(model, find_by_value, find_by: nil)
33
+ source = if find_by
34
+ dataloader.with(Dataloader::ActiveRecordSource, model, find_by: find_by)
35
+ else
36
+ dataloader.with(Dataloader::ActiveRecordSource, model)
37
+ end
38
+
39
+ source.load(find_by_value)
40
+ end
41
+
42
+ # Look up an associated record using a Rails association (via {Dataloader::ActiveRecordAssociationSource})
43
+ # @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.)
44
+ # @param record [ActiveRecord::Base] The object that the association belongs to.
45
+ # @param scope [ActiveRecord::Relation] A scope to look up the associated record in
46
+ # @return [ActiveRecord::Base, nil] The associated record, if there is one
47
+ # @example Looking up a belongs_to on the current object
48
+ # dataload_association(:parent) # Equivalent to `object.parent`, but dataloaded
49
+ # @example Looking up an associated record on some other object
50
+ # dataload_association(comment, :post) # Equivalent to `comment.post`, but dataloaded
51
+ def dataload_association(record = object, association_name, scope: nil)
52
+ source = if scope
53
+ dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name, scope)
54
+ else
55
+ dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name)
56
+ end
57
+ source.load(record)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ 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
@@ -55,14 +55,14 @@ module GraphQL
55
55
  else
56
56
  GraphQL::EmptyObjects::EMPTY_ARRAY
57
57
  end
58
- if inherited_directives.any? && directives
58
+ if !inherited_directives.empty? && directives
59
59
  dirs = []
60
60
  merge_directives(dirs, inherited_directives)
61
61
  merge_directives(dirs, directives)
62
62
  dirs
63
63
  elsif directives
64
64
  directives
65
- elsif inherited_directives.any?
65
+ elsif !inherited_directives.empty?
66
66
  inherited_directives
67
67
  else
68
68
  GraphQL::EmptyObjects::EMPTY_ARRAY
@@ -71,7 +71,7 @@ module GraphQL
71
71
  dirs = nil
72
72
  schema_member.ancestors.reverse_each do |ancestor|
73
73
  if ancestor.respond_to?(:own_directives) &&
74
- (anc_dirs = ancestor.own_directives).any?
74
+ !(anc_dirs = ancestor.own_directives).empty?
75
75
  dirs ||= []
76
76
  merge_directives(dirs, anc_dirs)
77
77
  end
@@ -91,7 +91,7 @@ module GraphQL
91
91
  private
92
92
 
93
93
  # Modify `target` by adding items from `dirs` such that:
94
- # - Any name conflict is overriden by the incoming member of `dirs`
94
+ # - Any name conflict is overridden by the incoming member of `dirs`
95
95
  # - Any other member of `dirs` is appended
96
96
  # @param target [Array<GraphQL::Schema::Directive>]
97
97
  # @param dirs [Array<GraphQL::Schema::Directive>]
@@ -79,6 +79,18 @@ module GraphQL
79
79
  end
80
80
  end
81
81
 
82
+ # @param new_has_no_fields [Boolean] Call with `true` to make this Object type ignore the requirement to have any defined fields.
83
+ # @return [void]
84
+ def has_no_fields(new_has_no_fields)
85
+ @has_no_fields = new_has_no_fields
86
+ nil
87
+ end
88
+
89
+ # @return [Boolean] `true` if `has_no_fields(true)` was configued
90
+ def has_no_fields?
91
+ @has_no_fields
92
+ end
93
+
82
94
  # @return [Hash<String => GraphQL::Schema::Field, Array<GraphQL::Schema::Field>>] Fields defined on this class _specifically_, not parent classes
83
95
  def own_fields
84
96
  @own_fields ||= {}
@@ -99,11 +111,12 @@ module GraphQL
99
111
  module InterfaceMethods
100
112
  def get_field(field_name, context = GraphQL::Query::NullContext.instance)
101
113
  warden = Warden.from_context(context)
114
+ skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)
102
115
  for ancestor in ancestors
103
116
  if ancestor.respond_to?(:own_fields) &&
104
117
  (f_entry = ancestor.own_fields[field_name]) &&
105
- (f = Warden.visible_entry?(:visible_field?, f_entry, context, warden))
106
- return f
118
+ (skip_visible || (f_entry = Warden.visible_entry?(:visible_field?, f_entry, context, warden)))
119
+ return f_entry
107
120
  end
108
121
  end
109
122
  nil
@@ -120,7 +133,7 @@ module GraphQL
120
133
  # Choose the most local definition that passes `.visible?` --
121
134
  # stop checking for fields by name once one has been found.
122
135
  if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden))
123
- visible_fields[field_name] = f
136
+ visible_fields[field_name] = f.ensure_loaded
124
137
  end
125
138
  end
126
139
  end
@@ -134,13 +147,14 @@ module GraphQL
134
147
  # Objects need to check that the interface implementation is visible, too
135
148
  warden = Warden.from_context(context)
136
149
  ancs = ancestors
150
+ skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)
137
151
  i = 0
138
152
  while (ancestor = ancs[i])
139
153
  if ancestor.respond_to?(:own_fields) &&
140
154
  visible_interface_implementation?(ancestor, context, warden) &&
141
155
  (f_entry = ancestor.own_fields[field_name]) &&
142
- (f = Warden.visible_entry?(:visible_field?, f_entry, context, warden))
143
- return f
156
+ (skip_visible || (f_entry = Warden.visible_entry?(:visible_field?, f_entry, context, warden)))
157
+ return (skip_visible ? f_entry : f_entry.ensure_loaded)
144
158
  end
145
159
  i += 1
146
160
  end
@@ -153,17 +167,22 @@ module GraphQL
153
167
  warden = Warden.from_context(context)
154
168
  # Local overrides take precedence over inherited fields
155
169
  visible_fields = {}
170
+ had_any_fields_at_all = false
156
171
  for ancestor in ancestors
157
172
  if ancestor.respond_to?(:own_fields) && visible_interface_implementation?(ancestor, context, warden)
158
173
  ancestor.own_fields.each do |field_name, fields_entry|
174
+ had_any_fields_at_all = true
159
175
  # Choose the most local definition that passes `.visible?` --
160
176
  # stop checking for fields by name once one has been found.
161
177
  if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden))
162
- visible_fields[field_name] = f
178
+ visible_fields[field_name] = f.ensure_loaded
163
179
  end
164
180
  end
165
181
  end
166
182
  end
183
+ if !had_any_fields_at_all && !has_no_fields?
184
+ warn(GraphQL::Schema::Object::FieldsAreRequiredError.new(self).message + "\n\nThis will raise an error in a future GraphQL-Ruby version.")
185
+ end
167
186
  visible_fields
168
187
  end
169
188
  end
@@ -186,6 +205,7 @@ module GraphQL
186
205
  subclass.class_exec do
187
206
  @own_fields ||= nil
188
207
  @field_class ||= nil
208
+ @has_no_fields ||= false
189
209
  end
190
210
  end
191
211
 
@@ -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)
@@ -24,7 +24,7 @@ module GraphQL
24
24
  implements(next_interface)
25
25
  end
26
26
  elsif int.is_a?(String) || int.is_a?(GraphQL::Schema::LateBoundType)
27
- if options.any?
27
+ if !options.empty?
28
28
  raise ArgumentError, "`implements(...)` doesn't support options with late-loaded types yet. Remove #{options} and open an issue to request this feature."
29
29
  end
30
30
  new_memberships << int
@@ -73,13 +73,13 @@ module GraphQL
73
73
  def interfaces(context = GraphQL::Query::NullContext.instance)
74
74
  visible_interfaces = super
75
75
  inherited_interfaces = superclass.interfaces(context)
76
- if visible_interfaces.any?
77
- if inherited_interfaces.any?
76
+ if !visible_interfaces.empty?
77
+ if !inherited_interfaces.empty?
78
78
  visible_interfaces.concat(inherited_interfaces)
79
79
  visible_interfaces.uniq!
80
80
  end
81
81
  visible_interfaces
82
- elsif inherited_interfaces.any?
82
+ elsif !inherited_interfaces.empty?
83
83
  inherited_interfaces
84
84
  else
85
85
  EmptyObjects::EMPTY_ARRAY
@@ -7,7 +7,11 @@ module GraphQL
7
7
  module HasUnresolvedTypeError
8
8
  private
9
9
  def add_unresolved_type_error(child_class)
10
- child_class.const_set(:UnresolvedTypeError, Class.new(GraphQL::UnresolvedTypeError))
10
+ if child_class.name # Don't set this for anonymous classes
11
+ child_class.const_set(:UnresolvedTypeError, Class.new(GraphQL::UnresolvedTypeError))
12
+ else
13
+ child_class.const_set(:UnresolvedTypeError, UnresolvedTypeError)
14
+ end
11
15
  end
12
16
  end
13
17
  end
@@ -32,7 +32,7 @@ module GraphQL
32
32
 
33
33
  def validators
34
34
  inherited_validators = superclass.validators
35
- if inherited_validators.any?
35
+ if !inherited_validators.empty?
36
36
  if @own_validators.nil?
37
37
  inherited_validators
38
38
  else
@@ -76,7 +76,7 @@ module GraphQL
76
76
 
77
77
  private
78
78
 
79
- # If one of thse values is accessed, initialize all the instance variables to retain
79
+ # If one of these values is accessed, initialize all the instance variables to retain
80
80
  # a consistent object shape.
81
81
  def initialize_relay_metadata
82
82
  if !defined?(@connection_type)
@@ -4,21 +4,34 @@ module GraphQL
4
4
  class Schema
5
5
  class Member
6
6
  module TypeSystemHelpers
7
- def initialize(*args, &block)
7
+ def initialize(...)
8
8
  super
9
9
  @to_non_null_type ||= nil
10
10
  @to_list_type ||= nil
11
11
  end
12
- ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
13
12
 
14
13
  # @return [Schema::NonNull] Make a non-null-type representation of this type
15
14
  def to_non_null_type
16
- @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
17
23
  end
18
24
 
19
25
  # @return [Schema::List] Make a list-type representation of this type
20
26
  def to_list_type
21
- @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
22
35
  end
23
36
 
24
37
  # @return [Boolean] true if this is a non-nullable type. A nullable list of non-nullables is considered nullable.
@@ -2,6 +2,7 @@
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_dataloader'
5
6
  require 'graphql/schema/member/has_directives'
6
7
  require 'graphql/schema/member/has_deprecation_reason'
7
8
  require 'graphql/schema/member/has_interfaces'
@@ -62,6 +62,13 @@ module GraphQL
62
62
  extend GraphQL::Schema::Member::HasFields
63
63
  extend GraphQL::Schema::Resolver::HasPayloadType
64
64
 
65
+ # @api private
66
+ def call_resolve(_args_hash)
67
+ # Clear any cached values from `loads` or authorization:
68
+ dataloader.clear_cache
69
+ super
70
+ end
71
+
65
72
  class << self
66
73
  def visible?(context)
67
74
  true
@@ -7,6 +7,15 @@ module GraphQL
7
7
  class Object < GraphQL::Schema::Member
8
8
  extend GraphQL::Schema::Member::HasFields
9
9
  extend GraphQL::Schema::Member::HasInterfaces
10
+ include Member::HasDataloader
11
+
12
+ # Raised when an Object doesn't have any field defined and hasn't explicitly opted out of this requirement
13
+ class FieldsAreRequiredError < GraphQL::Error
14
+ def initialize(object_type)
15
+ message = "Object types must have fields, but #{object_type.graphql_name} doesn't have any. Define a field for this type, remove it from your schema, or add `has_no_fields(true)` to its definition."
16
+ super(message)
17
+ end
18
+ end
10
19
 
11
20
  # @return [Object] the application object this type is wrapping
12
21
  attr_reader :object
@@ -57,20 +66,28 @@ module GraphQL
57
66
  # @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
58
67
  # @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
59
68
  def authorized_new(object, context)
60
- maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do
61
- begin
62
- authorized?(object, context)
63
- rescue GraphQL::UnauthorizedError => err
64
- context.schema.unauthorized_object(err)
65
- rescue StandardError => err
66
- context.query.handle_or_reraise(err)
69
+ context.query.current_trace.begin_authorized(self, object, context)
70
+ begin
71
+ maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do
72
+ begin
73
+ authorized?(object, context)
74
+ rescue GraphQL::UnauthorizedError => err
75
+ context.schema.unauthorized_object(err)
76
+ rescue StandardError => err
77
+ context.query.handle_or_reraise(err)
78
+ end
67
79
  end
80
+ ensure
81
+ context.query.current_trace.end_authorized(self, object, context, maybe_lazy_auth_val)
68
82
  end
69
83
 
70
84
  auth_val = if context.schema.lazy?(maybe_lazy_auth_val)
71
85
  GraphQL::Execution::Lazy.new do
86
+ context.query.current_trace.begin_authorized(self, object, context)
72
87
  context.query.current_trace.authorized_lazy(query: context.query, type: self, object: object) do
73
- context.schema.sync_lazy(maybe_lazy_auth_val)
88
+ res = context.schema.sync_lazy(maybe_lazy_auth_val)
89
+ context.query.current_trace.end_authorized(self, object, context, res)
90
+ res
74
91
  end
75
92
  end
76
93
  else
@@ -58,6 +58,7 @@ module GraphQL
58
58
  end
59
59
  end
60
60
  schema = Class.new(GraphQL::Schema) {
61
+ use GraphQL::Schema::Visibility
61
62
  query(query_root)
62
63
  def self.visible?(member, _ctx)
63
64
  member.graphql_name != "Root"
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ module RactorShareable
5
+ def self.extended(schema_class)
6
+ schema_class.extend(SchemaExtension)
7
+ schema_class.freeze_schema
8
+ end
9
+
10
+ module SchemaExtension
11
+
12
+ def freeze_error_handlers(handlers)
13
+ handlers[:subclass_handlers].default_proc = nil
14
+ handlers[:subclass_handlers].each do |_class, subclass_handlers|
15
+ freeze_error_handlers(subclass_handlers)
16
+ end
17
+ Ractor.make_shareable(handlers)
18
+ end
19
+
20
+ def freeze_schema
21
+ # warm some ivars:
22
+ default_analysis_engine
23
+ default_execution_strategy
24
+ GraphQL.default_parser
25
+ default_logger
26
+ freeze_error_handlers(error_handlers)
27
+ # TODO: this freezes errors of parent classes which could cause trouble
28
+ parent_class = superclass
29
+ while parent_class.respond_to?(:error_handlers)
30
+ freeze_error_handlers(parent_class.error_handlers)
31
+ parent_class = parent_class.superclass
32
+ end
33
+
34
+ own_tracers.freeze
35
+ @frozen_tracers = tracers.freeze
36
+ own_trace_modes.each do |m|
37
+ trace_options_for(m)
38
+ build_trace_mode(m)
39
+ end
40
+ build_trace_mode(:default)
41
+ Ractor.make_shareable(@trace_options_for_mode)
42
+ Ractor.make_shareable(own_trace_modes)
43
+ Ractor.make_shareable(own_multiplex_analyzers)
44
+ @frozen_multiplex_analyzers = Ractor.make_shareable(multiplex_analyzers)
45
+ Ractor.make_shareable(own_query_analyzers)
46
+ @frozen_query_analyzers = Ractor.make_shareable(query_analyzers)
47
+ Ractor.make_shareable(own_plugins)
48
+ own_plugins.each do |(plugin, options)|
49
+ Ractor.make_shareable(plugin)
50
+ Ractor.make_shareable(options)
51
+ end
52
+ @frozen_plugins = Ractor.make_shareable(plugins)
53
+ Ractor.make_shareable(own_references_to)
54
+ @frozen_directives = Ractor.make_shareable(directives)
55
+
56
+ Ractor.make_shareable(visibility)
57
+ Ractor.make_shareable(introspection_system)
58
+ extend(FrozenMethods)
59
+
60
+ Ractor.make_shareable(self)
61
+ superclass.respond_to?(:freeze_schema) && superclass.freeze_schema
62
+ end
63
+
64
+ module FrozenMethods
65
+ def tracers; @frozen_tracers; end
66
+ def multiplex_analyzers; @frozen_multiplex_analyzers; end
67
+ def query_analyzers; @frozen_query_analyzers; end
68
+ def plugins; @frozen_plugins; end
69
+ def directives; @frozen_directives; end
70
+
71
+ # This actually accumulates info during execution...
72
+ # How to support it?
73
+ def lazy?(_obj); false; end
74
+ def sync_lazy(obj); obj; end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- require "graphql/types/string"
3
2
 
4
3
  module GraphQL
5
4
  class Schema
@@ -8,6 +8,7 @@ module GraphQL
8
8
  # - Arguments, via `.argument(...)` helper, which will be applied to the field.
9
9
  # - Return type, via `.type(..., null: ...)`, which will be applied to the field.
10
10
  # - Description, via `.description(...)`, which will be applied to the field
11
+ # - Comment, via `.comment(...)`, which will be applied to the field
11
12
  # - Resolution, via `#resolve(**args)` method, which will be called to resolve the field.
12
13
  # - `#object` and `#context` accessors for use during `#resolve`.
13
14
  #
@@ -19,12 +20,15 @@ module GraphQL
19
20
  # @see {GraphQL::Function} `Resolver` is a replacement for `GraphQL::Function`
20
21
  class Resolver
21
22
  include Schema::Member::GraphQLTypeNames
22
- # Really we only need description from here, but:
23
+ # Really we only need description & comment from here, but:
23
24
  extend Schema::Member::BaseDSLMethods
24
25
  extend GraphQL::Schema::Member::HasArguments
25
26
  extend GraphQL::Schema::Member::HasValidators
26
27
  include Schema::Member::HasPath
27
28
  extend Schema::Member::HasPath
29
+ extend Schema::Member::HasDirectives
30
+ include Schema::Member::HasDataloader
31
+ extend Schema::Member::HasDeprecationReason
28
32
 
29
33
  # @param object [Object] The application object that this field is being resolved on
30
34
  # @param context [GraphQL::Query::Context]
@@ -35,7 +39,7 @@ module GraphQL
35
39
  @field = field
36
40
  # Since this hash is constantly rebuilt, cache it for this call
37
41
  @arguments_by_keyword = {}
38
- self.class.arguments(context).each do |name, arg|
42
+ context.types.arguments(self.class).each do |arg|
39
43
  @arguments_by_keyword[arg.keyword] = arg
40
44
  end
41
45
  @prepared_arguments = nil
@@ -47,11 +51,6 @@ module GraphQL
47
51
  # @return [GraphQL::Query::Context]
48
52
  attr_reader :context
49
53
 
50
- # @return [GraphQL::Dataloader]
51
- def dataloader
52
- context.dataloader
53
- end
54
-
55
54
  # @return [GraphQL::Schema::Field]
56
55
  attr_reader :field
57
56
 
@@ -65,7 +64,7 @@ module GraphQL
65
64
  # @api private
66
65
  def resolve_with_support(**args)
67
66
  # First call the ready? hook which may raise
68
- raw_ready_val = if args.any?
67
+ raw_ready_val = if !args.empty?
69
68
  ready?(**args)
70
69
  else
71
70
  ready?
@@ -86,7 +85,7 @@ module GraphQL
86
85
  @prepared_arguments = loaded_args
87
86
  Schema::Validator.validate!(self.class.validators, object, context, loaded_args, as: @field)
88
87
  # Then call `authorized?`, which may raise or may return a lazy object
89
- raw_authorized_val = if loaded_args.any?
88
+ raw_authorized_val = if !loaded_args.empty?
90
89
  authorized?(**loaded_args)
91
90
  else
92
91
  authorized?
@@ -103,11 +102,7 @@ module GraphQL
103
102
  end
104
103
  elsif authorized_val
105
104
  # Finally, all the hooks have passed, so resolve it
106
- if loaded_args.any?
107
- public_send(self.class.resolve_method, **loaded_args)
108
- else
109
- public_send(self.class.resolve_method)
110
- end
105
+ call_resolve(loaded_args)
111
106
  else
112
107
  raise GraphQL::UnauthorizedFieldError.new(context: context, object: object, type: field.owner, field: field)
113
108
  end
@@ -117,6 +112,15 @@ module GraphQL
117
112
  end
118
113
  end
119
114
 
115
+ # @api private {GraphQL::Schema::Mutation} uses this to clear the dataloader cache
116
+ def call_resolve(args_hash)
117
+ if !args_hash.empty?
118
+ public_send(self.class.resolve_method, **args_hash)
119
+ else
120
+ public_send(self.class.resolve_method)
121
+ end
122
+ end
123
+
120
124
  # Do the work. Everything happens here.
121
125
  # @return [Object] An object corresponding to the return type
122
126
  def resolve(**args)
@@ -146,7 +150,7 @@ module GraphQL
146
150
  # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
147
151
  def authorized?(**inputs)
148
152
  arg_owner = @field # || self.class
149
- args = arg_owner.arguments(context)
153
+ args = context.types.arguments(arg_owner)
150
154
  authorize_arguments(args, inputs)
151
155
  end
152
156
 
@@ -163,7 +167,7 @@ module GraphQL
163
167
  private
164
168
 
165
169
  def authorize_arguments(args, inputs)
166
- args.each_value do |argument|
170
+ args.each do |argument|
167
171
  arg_keyword = argument.keyword
168
172
  if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
169
173
  auth_result = argument.authorized?(self, arg_value, context)
@@ -176,10 +180,9 @@ module GraphQL
176
180
  elsif auth_result == false
177
181
  return auth_result
178
182
  end
179
- else
180
- true
181
183
  end
182
184
  end
185
+ true
183
186
  end
184
187
 
185
188
  def load_arguments(args)
@@ -202,7 +205,7 @@ module GraphQL
202
205
  end
203
206
 
204
207
  # Avoid returning a lazy if none are needed
205
- if prepare_lazies.any?
208
+ if !prepare_lazies.empty?
206
209
  GraphQL::Execution::Lazy.all(prepare_lazies).then { prepared_args }
207
210
  else
208
211
  prepared_args
@@ -388,7 +391,7 @@ module GraphQL
388
391
  if superclass.respond_to?(:extensions)
389
392
  s_exts = superclass.extensions
390
393
  if own_exts
391
- if s_exts.any?
394
+ if !s_exts.empty?
392
395
  own_exts + s_exts
393
396
  else
394
397
  own_exts
@@ -401,11 +404,14 @@ module GraphQL
401
404
  end
402
405
  end
403
406
 
407
+ def inherited(child_class)
408
+ child_class.description(description)
409
+ super
410
+ end
411
+
404
412
  private
405
413
 
406
- def own_extensions
407
- @own_extensions
408
- end
414
+ attr_reader :own_extensions
409
415
  end
410
416
  end
411
417
  end
@@ -50,12 +50,7 @@ module GraphQL
50
50
  end
51
51
 
52
52
  if coerced_result.nil?
53
- str_value = if value == Float::INFINITY
54
- ""
55
- else
56
- " #{GraphQL::Language.serialize(value)}"
57
- end
58
- Query::InputValidationResult.from_problem("Could not coerce value#{str_value} to #{graphql_name}")
53
+ Query::InputValidationResult.from_problem("Could not coerce value #{GraphQL::Language.serialize(value)} to #{graphql_name}")
59
54
  elsif coerced_result.is_a?(GraphQL::CoercionError)
60
55
  Query::InputValidationResult.from_problem(coerced_result.message, message: coerced_result.message, extensions: coerced_result.extensions)
61
56
  else