graphql 1.9.17 → 1.11.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (230) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +18 -2
  3. data/lib/generators/graphql/install_generator.rb +27 -0
  4. data/lib/generators/graphql/object_generator.rb +52 -8
  5. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  6. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  7. data/lib/generators/graphql/templates/base_field.erb +2 -0
  8. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  9. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  10. data/lib/generators/graphql/templates/base_mutation.erb +2 -0
  11. data/lib/generators/graphql/templates/base_object.erb +2 -0
  12. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  13. data/lib/generators/graphql/templates/base_union.erb +2 -0
  14. data/lib/generators/graphql/templates/enum.erb +2 -0
  15. data/lib/generators/graphql/templates/graphql_controller.erb +14 -10
  16. data/lib/generators/graphql/templates/interface.erb +2 -0
  17. data/lib/generators/graphql/templates/loader.erb +2 -0
  18. data/lib/generators/graphql/templates/mutation.erb +2 -0
  19. data/lib/generators/graphql/templates/mutation_type.erb +2 -0
  20. data/lib/generators/graphql/templates/object.erb +2 -0
  21. data/lib/generators/graphql/templates/query_type.erb +2 -0
  22. data/lib/generators/graphql/templates/scalar.erb +2 -0
  23. data/lib/generators/graphql/templates/schema.erb +10 -0
  24. data/lib/generators/graphql/templates/union.erb +3 -1
  25. data/lib/graphql/analysis/ast/field_usage.rb +1 -1
  26. data/lib/graphql/analysis/ast/query_complexity.rb +178 -67
  27. data/lib/graphql/analysis/ast/visitor.rb +3 -3
  28. data/lib/graphql/analysis/ast.rb +12 -11
  29. data/lib/graphql/argument.rb +10 -38
  30. data/lib/graphql/backtrace/table.rb +10 -2
  31. data/lib/graphql/backtrace/tracer.rb +2 -1
  32. data/lib/graphql/base_type.rb +4 -0
  33. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +2 -2
  34. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +5 -9
  35. data/lib/graphql/define/assign_enum_value.rb +1 -1
  36. data/lib/graphql/define/assign_global_id_field.rb +2 -2
  37. data/lib/graphql/define/assign_object_field.rb +3 -3
  38. data/lib/graphql/define/defined_object_proxy.rb +3 -0
  39. data/lib/graphql/define/instance_definable.rb +18 -108
  40. data/lib/graphql/directive/deprecated_directive.rb +1 -12
  41. data/lib/graphql/directive.rb +8 -1
  42. data/lib/graphql/enum_type.rb +5 -71
  43. data/lib/graphql/execution/directive_checks.rb +2 -2
  44. data/lib/graphql/execution/errors.rb +2 -3
  45. data/lib/graphql/execution/execute.rb +1 -1
  46. data/lib/graphql/execution/instrumentation.rb +1 -1
  47. data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
  48. data/lib/graphql/execution/interpreter/arguments.rb +51 -0
  49. data/lib/graphql/execution/interpreter/arguments_cache.rb +79 -0
  50. data/lib/graphql/execution/interpreter/handles_raw_value.rb +25 -0
  51. data/lib/graphql/execution/interpreter/runtime.rb +227 -254
  52. data/lib/graphql/execution/interpreter.rb +34 -11
  53. data/lib/graphql/execution/lazy/lazy_method_map.rb +4 -0
  54. data/lib/graphql/execution/lookahead.rb +39 -114
  55. data/lib/graphql/execution/multiplex.rb +14 -5
  56. data/lib/graphql/field.rb +14 -118
  57. data/lib/graphql/filter.rb +1 -1
  58. data/lib/graphql/function.rb +1 -30
  59. data/lib/graphql/input_object_type.rb +6 -24
  60. data/lib/graphql/integer_decoding_error.rb +17 -0
  61. data/lib/graphql/interface_type.rb +7 -23
  62. data/lib/graphql/internal_representation/scope.rb +2 -2
  63. data/lib/graphql/internal_representation/visit.rb +2 -2
  64. data/lib/graphql/introspection/base_object.rb +2 -5
  65. data/lib/graphql/introspection/directive_type.rb +1 -1
  66. data/lib/graphql/introspection/entry_points.rb +7 -7
  67. data/lib/graphql/introspection/field_type.rb +7 -3
  68. data/lib/graphql/introspection/input_value_type.rb +33 -9
  69. data/lib/graphql/introspection/introspection_query.rb +6 -92
  70. data/lib/graphql/introspection/schema_type.rb +4 -9
  71. data/lib/graphql/introspection/type_type.rb +11 -7
  72. data/lib/graphql/introspection.rb +96 -0
  73. data/lib/graphql/invalid_null_error.rb +18 -0
  74. data/lib/graphql/language/block_string.rb +24 -5
  75. data/lib/graphql/language/definition_slice.rb +21 -10
  76. data/lib/graphql/language/document_from_schema_definition.rb +89 -64
  77. data/lib/graphql/language/lexer.rb +7 -3
  78. data/lib/graphql/language/lexer.rl +7 -3
  79. data/lib/graphql/language/nodes.rb +52 -91
  80. data/lib/graphql/language/parser.rb +719 -717
  81. data/lib/graphql/language/parser.y +104 -98
  82. data/lib/graphql/language/printer.rb +1 -1
  83. data/lib/graphql/language/sanitized_printer.rb +222 -0
  84. data/lib/graphql/language/visitor.rb +2 -2
  85. data/lib/graphql/language.rb +2 -1
  86. data/lib/graphql/name_validator.rb +6 -7
  87. data/lib/graphql/non_null_type.rb +0 -10
  88. data/lib/graphql/object_type.rb +45 -56
  89. data/lib/graphql/pagination/active_record_relation_connection.rb +41 -0
  90. data/lib/graphql/pagination/array_connection.rb +77 -0
  91. data/lib/graphql/pagination/connection.rb +208 -0
  92. data/lib/graphql/pagination/connections.rb +145 -0
  93. data/lib/graphql/pagination/mongoid_relation_connection.rb +25 -0
  94. data/lib/graphql/pagination/relation_connection.rb +185 -0
  95. data/lib/graphql/pagination/sequel_dataset_connection.rb +28 -0
  96. data/lib/graphql/pagination.rb +6 -0
  97. data/lib/graphql/query/arguments.rb +4 -2
  98. data/lib/graphql/query/context.rb +36 -9
  99. data/lib/graphql/query/fingerprint.rb +26 -0
  100. data/lib/graphql/query/input_validation_result.rb +23 -6
  101. data/lib/graphql/query/literal_input.rb +30 -10
  102. data/lib/graphql/query/null_context.rb +5 -1
  103. data/lib/graphql/query/validation_pipeline.rb +4 -1
  104. data/lib/graphql/query/variable_validation_error.rb +1 -1
  105. data/lib/graphql/query/variables.rb +16 -7
  106. data/lib/graphql/query.rb +64 -15
  107. data/lib/graphql/rake_task/validate.rb +3 -0
  108. data/lib/graphql/rake_task.rb +9 -9
  109. data/lib/graphql/relay/array_connection.rb +10 -12
  110. data/lib/graphql/relay/base_connection.rb +23 -13
  111. data/lib/graphql/relay/connection_type.rb +2 -1
  112. data/lib/graphql/relay/edge_type.rb +1 -0
  113. data/lib/graphql/relay/edges_instrumentation.rb +1 -1
  114. data/lib/graphql/relay/mutation.rb +1 -86
  115. data/lib/graphql/relay/node.rb +2 -2
  116. data/lib/graphql/relay/range_add.rb +14 -5
  117. data/lib/graphql/relay/relation_connection.rb +8 -10
  118. data/lib/graphql/scalar_type.rb +15 -59
  119. data/lib/graphql/schema/argument.rb +113 -11
  120. data/lib/graphql/schema/base_64_encoder.rb +2 -0
  121. data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +1 -1
  122. data/lib/graphql/schema/build_from_definition/resolve_map.rb +13 -5
  123. data/lib/graphql/schema/build_from_definition.rb +212 -190
  124. data/lib/graphql/schema/built_in_types.rb +5 -5
  125. data/lib/graphql/schema/default_type_error.rb +2 -0
  126. data/lib/graphql/schema/directive/deprecated.rb +18 -0
  127. data/lib/graphql/schema/directive/include.rb +1 -1
  128. data/lib/graphql/schema/directive/skip.rb +1 -1
  129. data/lib/graphql/schema/directive.rb +34 -3
  130. data/lib/graphql/schema/enum.rb +52 -4
  131. data/lib/graphql/schema/enum_value.rb +6 -1
  132. data/lib/graphql/schema/field/connection_extension.rb +44 -20
  133. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  134. data/lib/graphql/schema/field.rb +200 -129
  135. data/lib/graphql/schema/find_inherited_value.rb +13 -0
  136. data/lib/graphql/schema/finder.rb +13 -11
  137. data/lib/graphql/schema/input_object.rb +131 -22
  138. data/lib/graphql/schema/interface.rb +26 -8
  139. data/lib/graphql/schema/introspection_system.rb +108 -37
  140. data/lib/graphql/schema/late_bound_type.rb +3 -2
  141. data/lib/graphql/schema/list.rb +47 -0
  142. data/lib/graphql/schema/loader.rb +134 -96
  143. data/lib/graphql/schema/member/base_dsl_methods.rb +29 -12
  144. data/lib/graphql/schema/member/build_type.rb +19 -5
  145. data/lib/graphql/schema/member/cached_graphql_definition.rb +5 -0
  146. data/lib/graphql/schema/member/has_arguments.rb +105 -5
  147. data/lib/graphql/schema/member/has_ast_node.rb +20 -0
  148. data/lib/graphql/schema/member/has_fields.rb +20 -10
  149. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  150. data/lib/graphql/schema/member/type_system_helpers.rb +2 -2
  151. data/lib/graphql/schema/member/validates_input.rb +33 -0
  152. data/lib/graphql/schema/member.rb +6 -0
  153. data/lib/graphql/schema/mutation.rb +5 -1
  154. data/lib/graphql/schema/non_null.rb +30 -0
  155. data/lib/graphql/schema/object.rb +65 -12
  156. data/lib/graphql/schema/possible_types.rb +9 -4
  157. data/lib/graphql/schema/printer.rb +0 -15
  158. data/lib/graphql/schema/relay_classic_mutation.rb +5 -3
  159. data/lib/graphql/schema/resolver/has_payload_type.rb +5 -2
  160. data/lib/graphql/schema/resolver.rb +26 -18
  161. data/lib/graphql/schema/scalar.rb +27 -3
  162. data/lib/graphql/schema/subscription.rb +8 -18
  163. data/lib/graphql/schema/timeout.rb +29 -15
  164. data/lib/graphql/schema/traversal.rb +1 -1
  165. data/lib/graphql/schema/type_expression.rb +21 -13
  166. data/lib/graphql/schema/type_membership.rb +2 -2
  167. data/lib/graphql/schema/union.rb +37 -3
  168. data/lib/graphql/schema/unique_within_type.rb +1 -2
  169. data/lib/graphql/schema/validation.rb +10 -2
  170. data/lib/graphql/schema/warden.rb +115 -29
  171. data/lib/graphql/schema.rb +903 -195
  172. data/lib/graphql/static_validation/all_rules.rb +1 -0
  173. data/lib/graphql/static_validation/base_visitor.rb +10 -6
  174. data/lib/graphql/static_validation/literal_validator.rb +52 -27
  175. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +43 -83
  176. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +17 -5
  177. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +33 -25
  178. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +1 -1
  179. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
  180. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
  181. data/lib/graphql/static_validation/rules/fields_will_merge.rb +29 -21
  182. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  183. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  184. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  185. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -2
  186. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -5
  187. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +12 -13
  188. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +5 -6
  189. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  190. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +5 -3
  191. data/lib/graphql/static_validation/type_stack.rb +2 -2
  192. data/lib/graphql/static_validation/validation_context.rb +1 -1
  193. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  194. data/lib/graphql/static_validation/validator.rb +30 -8
  195. data/lib/graphql/static_validation.rb +1 -0
  196. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +89 -19
  197. data/lib/graphql/subscriptions/broadcast_analyzer.rb +84 -0
  198. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +21 -0
  199. data/lib/graphql/subscriptions/event.rb +23 -5
  200. data/lib/graphql/subscriptions/instrumentation.rb +10 -5
  201. data/lib/graphql/subscriptions/serialize.rb +22 -4
  202. data/lib/graphql/subscriptions/subscription_root.rb +15 -5
  203. data/lib/graphql/subscriptions.rb +108 -35
  204. data/lib/graphql/tracing/active_support_notifications_tracing.rb +14 -10
  205. data/lib/graphql/tracing/appoptics_tracing.rb +171 -0
  206. data/lib/graphql/tracing/appsignal_tracing.rb +8 -0
  207. data/lib/graphql/tracing/data_dog_tracing.rb +8 -0
  208. data/lib/graphql/tracing/new_relic_tracing.rb +9 -12
  209. data/lib/graphql/tracing/platform_tracing.rb +53 -9
  210. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  211. data/lib/graphql/tracing/prometheus_tracing.rb +8 -0
  212. data/lib/graphql/tracing/scout_tracing.rb +19 -0
  213. data/lib/graphql/tracing/skylight_tracing.rb +8 -0
  214. data/lib/graphql/tracing/statsd_tracing.rb +42 -0
  215. data/lib/graphql/tracing.rb +14 -34
  216. data/lib/graphql/types/big_int.rb +1 -1
  217. data/lib/graphql/types/int.rb +9 -2
  218. data/lib/graphql/types/iso_8601_date.rb +3 -3
  219. data/lib/graphql/types/iso_8601_date_time.rb +25 -10
  220. data/lib/graphql/types/relay/base_connection.rb +11 -7
  221. data/lib/graphql/types/relay/base_edge.rb +2 -1
  222. data/lib/graphql/types/string.rb +7 -1
  223. data/lib/graphql/unauthorized_error.rb +1 -1
  224. data/lib/graphql/union_type.rb +13 -28
  225. data/lib/graphql/unresolved_type_error.rb +2 -2
  226. data/lib/graphql/version.rb +1 -1
  227. data/lib/graphql.rb +31 -6
  228. data/readme.md +1 -1
  229. metadata +34 -9
  230. data/lib/graphql/literal_validation_error.rb +0 -6
@@ -36,6 +36,7 @@ require "graphql/schema/scalar"
36
36
  require "graphql/schema/object"
37
37
  require "graphql/schema/union"
38
38
  require "graphql/schema/directive"
39
+ require "graphql/schema/directive/deprecated"
39
40
  require "graphql/schema/directive/include"
40
41
  require "graphql/schema/directive/skip"
41
42
  require "graphql/schema/directive/feature"
@@ -55,7 +56,6 @@ module GraphQL
55
56
  # - types for exposing your application
56
57
  # - query analyzers for assessing incoming queries (including max depth & max complexity restrictions)
57
58
  # - execution strategies for running incoming queries
58
- # - middleware for interacting with execution
59
59
  #
60
60
  # Schemas start with root types, {Schema#query}, {Schema#mutation} and {Schema#subscription}.
61
61
  # The schema will traverse the tree of fields & types, using those as starting points.
@@ -67,14 +67,10 @@ module GraphQL
67
67
  # Schemas can specify how queries should be executed against them.
68
68
  # `query_execution_strategy`, `mutation_execution_strategy` and `subscription_execution_strategy`
69
69
  # each apply to corresponding root types.
70
- #
71
- # A schema accepts a `Relay::GlobalNodeIdentification` instance for use with Relay IDs.
72
- #
70
+ # #
73
71
  # @example defining a schema
74
- # MySchema = GraphQL::Schema.define do
72
+ # class MySchema < GraphQL::Schema
75
73
  # query QueryType
76
- # middleware PermissionMiddleware
77
- # rescue_from(ActiveRecord::RecordNotFound) { "Not found" }
78
74
  # # If types are only connected by way of interfaces, they must be added here
79
75
  # orphan_types ImageType, AudioType
80
76
  # end
@@ -82,12 +78,86 @@ module GraphQL
82
78
  class Schema
83
79
  extend Forwardable
84
80
  extend GraphQL::Schema::Member::AcceptsDefinition
81
+ extend GraphQL::Schema::Member::HasAstNode
85
82
  include GraphQL::Define::InstanceDefinable
86
83
  extend GraphQL::Schema::FindInheritedValue
87
84
 
85
+ class DuplicateTypeNamesError < GraphQL::Error
86
+ def initialize(type_name:, first_definition:, second_definition:, path:)
87
+ super("Multiple definitions for `#{type_name}`. Previously found #{first_definition.inspect} (#{first_definition.class}), then found #{second_definition.inspect} (#{second_definition.class}) at #{path.join(".")}")
88
+ end
89
+ end
90
+
91
+ class UnresolvedLateBoundTypeError < GraphQL::Error
92
+ attr_reader :type
93
+ def initialize(type:)
94
+ @type = type
95
+ super("Late bound type was never found: #{type.inspect}")
96
+ end
97
+ end
98
+
99
+ module LazyHandlingMethods
100
+ # Call the given block at the right time, either:
101
+ # - Right away, if `value` is not registered with `lazy_resolve`
102
+ # - After resolving `value`, if it's registered with `lazy_resolve` (eg, `Promise`)
103
+ # @api private
104
+ def after_lazy(value, &block)
105
+ if lazy?(value)
106
+ GraphQL::Execution::Lazy.new do
107
+ result = sync_lazy(value)
108
+ # The returned result might also be lazy, so check it, too
109
+ after_lazy(result, &block)
110
+ end
111
+ else
112
+ yield(value) if block_given?
113
+ end
114
+ end
115
+
116
+ # Override this method to handle lazy objects in a custom way.
117
+ # @param value [Object] an instance of a class registered with {.lazy_resolve}
118
+ # @return [Object] A GraphQL-ready (non-lazy) object
119
+ # @api private
120
+ def sync_lazy(value)
121
+ lazy_method = lazy_method_name(value)
122
+ if lazy_method
123
+ synced_value = value.public_send(lazy_method)
124
+ sync_lazy(synced_value)
125
+ else
126
+ value
127
+ end
128
+ end
129
+
130
+ # @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered with {#lazy_resolve}.
131
+ def lazy_method_name(obj)
132
+ lazy_methods.get(obj)
133
+ end
134
+
135
+ # @return [Boolean] True if this object should be lazily resolved
136
+ def lazy?(obj)
137
+ !!lazy_method_name(obj)
138
+ end
139
+
140
+ # Return a lazy if any of `maybe_lazies` are lazy,
141
+ # otherwise, call the block eagerly and return the result.
142
+ # @param maybe_lazies [Array]
143
+ # @api private
144
+ def after_any_lazies(maybe_lazies)
145
+ if maybe_lazies.any? { |l| lazy?(l) }
146
+ GraphQL::Execution::Lazy.all(maybe_lazies).then do |result|
147
+ yield result
148
+ end
149
+ else
150
+ yield maybe_lazies
151
+ end
152
+ end
153
+ end
154
+
155
+ include LazyHandlingMethods
156
+ extend LazyHandlingMethods
157
+
88
158
  accepts_definitions \
89
159
  :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
90
- :max_depth, :max_complexity, :default_max_page_size,
160
+ :validate_timeout, :max_depth, :max_complexity, :default_max_page_size,
91
161
  :orphan_types, :resolve_type, :type_error, :parse_error,
92
162
  :error_bubbling,
93
163
  :raise_definition_error,
@@ -99,7 +169,9 @@ module GraphQL
99
169
  mutation: ->(schema, t) { schema.mutation = t.respond_to?(:graphql_definition) ? t.graphql_definition : t },
100
170
  subscription: ->(schema, t) { schema.subscription = t.respond_to?(:graphql_definition) ? t.graphql_definition : t },
101
171
  disable_introspection_entry_points: ->(schema) { schema.disable_introspection_entry_points = true },
102
- directives: ->(schema, directives) { schema.directives = directives.reduce({}) { |m, d| m[d.name] = d; m } },
172
+ disable_schema_introspection_entry_point: ->(schema) { schema.disable_schema_introspection_entry_point = true },
173
+ disable_type_introspection_entry_point: ->(schema) { schema.disable_type_introspection_entry_point = true },
174
+ directives: ->(schema, directives) { schema.directives = directives.reduce({}) { |m, d| m[d.graphql_name] = d; m } },
103
175
  directive: ->(schema, directive) { schema.directives[directive.graphql_name] = directive },
104
176
  instrument: ->(schema, type, instrumenter, after_built_ins: false) {
105
177
  if type == :field && after_built_ins
@@ -124,7 +196,7 @@ module GraphQL
124
196
  attr_accessor \
125
197
  :query, :mutation, :subscription,
126
198
  :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
127
- :max_depth, :max_complexity, :default_max_page_size,
199
+ :validate_timeout, :max_depth, :max_complexity, :default_max_page_size,
128
200
  :orphan_types, :directives,
129
201
  :query_analyzers, :multiplex_analyzers, :instrumenters, :lazy_methods,
130
202
  :cursor_encoder,
@@ -154,6 +226,24 @@ module GraphQL
154
226
  # [Boolean] True if this object disables the introspection entry point fields
155
227
  attr_accessor :disable_introspection_entry_points
156
228
 
229
+ def disable_introspection_entry_points?
230
+ !!@disable_introspection_entry_points
231
+ end
232
+
233
+ # [Boolean] True if this object disables the __schema introspection entry point field
234
+ attr_accessor :disable_schema_introspection_entry_point
235
+
236
+ def disable_schema_introspection_entry_point?
237
+ !!@disable_schema_introspection_entry_point
238
+ end
239
+
240
+ # [Boolean] True if this object disables the __type introspection entry point field
241
+ attr_accessor :disable_type_introspection_entry_point
242
+
243
+ def disable_type_introspection_entry_point?
244
+ !!@disable_type_introspection_entry_point
245
+ end
246
+
157
247
  class << self
158
248
  attr_writer :default_execution_strategy
159
249
  end
@@ -167,8 +257,6 @@ module GraphQL
167
257
  attr_reader :tracers
168
258
 
169
259
  DYNAMIC_FIELDS = ["__type", "__typename", "__schema"].freeze
170
- EMPTY_ARRAY = [].freeze
171
- EMPTY_HASH = {}.freeze
172
260
 
173
261
  attr_reader :static_validator, :object_from_id_proc, :id_from_object_proc, :resolve_type_proc
174
262
 
@@ -176,7 +264,10 @@ module GraphQL
176
264
  @tracers = []
177
265
  @definition_error = nil
178
266
  @orphan_types = []
179
- @directives = self.class.default_directives
267
+ @directives = {}
268
+ self.class.default_directives.each do |name, dir|
269
+ @directives[name] = dir.graphql_definition
270
+ end
180
271
  @static_validator = GraphQL::StaticValidation::Validator.new(schema: self)
181
272
  @middleware = MiddlewareChain.new(final_step: GraphQL::Execution::Execute::FieldResolveStep)
182
273
  @query_analyzers = []
@@ -203,6 +294,8 @@ module GraphQL
203
294
  @interpreter = false
204
295
  @error_bubbling = false
205
296
  @disable_introspection_entry_points = false
297
+ @disable_schema_introspection_entry_point = false
298
+ @disable_type_introspection_entry_point = false
206
299
  end
207
300
 
208
301
  # @return [Boolean] True if using the new {GraphQL::Execution::Interpreter}
@@ -272,8 +365,8 @@ module GraphQL
272
365
  query = GraphQL::Query.new(self, document: doc, context: context)
273
366
  validator_opts = { schema: self }
274
367
  rules && (validator_opts[:rules] = rules)
275
- validator = GraphQL::StaticValidation::Validator.new(validator_opts)
276
- res = validator.validate(query)
368
+ validator = GraphQL::StaticValidation::Validator.new(**validator_opts)
369
+ res = validator.validate(query, timeout: validate_timeout)
277
370
  res[:errors]
278
371
  end
279
372
 
@@ -325,6 +418,10 @@ module GraphQL
325
418
  end
326
419
  end
327
420
 
421
+ def get_type(type_name)
422
+ @types[type_name]
423
+ end
424
+
328
425
  # @api private
329
426
  def introspection_system
330
427
  @introspection_system ||= begin
@@ -336,9 +433,13 @@ module GraphQL
336
433
  # Returns a list of Arguments and Fields referencing a certain type
337
434
  # @param type_name [String]
338
435
  # @return [Hash]
339
- def references_to(type_name)
436
+ def references_to(type_name = nil)
340
437
  rebuild_artifacts unless defined?(@type_reference_map)
341
- @type_reference_map.fetch(type_name, [])
438
+ if type_name
439
+ @type_reference_map.fetch(type_name, [])
440
+ else
441
+ @type_reference_map
442
+ end
342
443
  end
343
444
 
344
445
  # Returns a list of Union types in which a type is a member
@@ -416,8 +517,8 @@ module GraphQL
416
517
  def get_field(parent_type, field_name)
417
518
  with_definition_error_check do
418
519
  parent_type_name = case parent_type
419
- when GraphQL::BaseType
420
- parent_type.name
520
+ when GraphQL::BaseType, Class, Module
521
+ parent_type.graphql_name
421
522
  when String
422
523
  parent_type
423
524
  else
@@ -443,8 +544,8 @@ module GraphQL
443
544
  @instrumented_field_map[type.graphql_name]
444
545
  end
445
546
 
446
- def type_from_ast(ast_node)
447
- GraphQL::Schema::TypeExpression.build_type(self.types, ast_node)
547
+ def type_from_ast(ast_node, context:)
548
+ GraphQL::Schema::TypeExpression.build_type(self, ast_node)
448
549
  end
449
550
 
450
551
  # @see [GraphQL::Schema::Warden] Restricted access to members of a schema
@@ -452,8 +553,17 @@ module GraphQL
452
553
  # @param context [GraphQL::Query::Context] The context for the current query
453
554
  # @return [Array<GraphQL::ObjectType>] types which belong to `type_defn` in this schema
454
555
  def possible_types(type_defn, context = GraphQL::Query::NullContext)
455
- @possible_types ||= GraphQL::Schema::PossibleTypes.new(self)
456
- @possible_types.possible_types(type_defn, context)
556
+ if context == GraphQL::Query::NullContext
557
+ @possible_types ||= GraphQL::Schema::PossibleTypes.new(self)
558
+ @possible_types.possible_types(type_defn, context)
559
+ else
560
+ # Use the incoming context to cache this instance --
561
+ # if it were cached on the schema, we'd have a memory leak
562
+ # https://github.com/rmosolgo/graphql-ruby/issues/2878
563
+ ns = context.namespace(:possible_types)
564
+ per_query_possible_types = ns[:possible_types] ||= GraphQL::Schema::PossibleTypes.new(self)
565
+ per_query_possible_types.possible_types(type_defn, context)
566
+ end
457
567
  end
458
568
 
459
569
  # @see [GraphQL::Schema::Warden] Resticted access to root types
@@ -597,10 +707,41 @@ module GraphQL
597
707
 
598
708
  # Can't delegate to `class`
599
709
  alias :_schema_class :class
600
- def_delegators :_schema_class, :visible?, :accessible?, :authorized?, :unauthorized_object, :unauthorized_field, :inaccessible_fields
710
+ def_delegators :_schema_class, :unauthorized_object, :unauthorized_field, :inaccessible_fields
601
711
  def_delegators :_schema_class, :directive
602
712
  def_delegators :_schema_class, :error_handler
603
713
 
714
+
715
+ # Given this schema member, find the class-based definition object
716
+ # whose `method_name` should be treated as an application hook
717
+ # @see {.visible?}
718
+ # @see {.accessible?}
719
+ def call_on_type_class(member, method_name, context, default:)
720
+ member = if member.respond_to?(:type_class)
721
+ member.type_class
722
+ else
723
+ member
724
+ end
725
+
726
+ if member.respond_to?(:relay_node_type) && (t = member.relay_node_type)
727
+ member = t
728
+ end
729
+
730
+ if member.respond_to?(method_name)
731
+ member.public_send(method_name, context)
732
+ else
733
+ default
734
+ end
735
+ end
736
+
737
+ def visible?(member, context)
738
+ call_on_type_class(member, :visible?, context, default: true)
739
+ end
740
+
741
+ def accessible?(member, context)
742
+ call_on_type_class(member, :accessible?, context, default: true)
743
+ end
744
+
604
745
  # A function to call when {#execute} receives an invalid query string
605
746
  #
606
747
  # @see {DefaultParseError} is the default behavior.
@@ -645,30 +786,33 @@ module GraphQL
645
786
  # @param definition_or_path [String] A schema definition string, or a path to a file containing the definition
646
787
  # @param default_resolve [<#call(type, field, obj, args, ctx)>] A callable for handling field resolution
647
788
  # @param parser [Object] An object for handling definition string parsing (must respond to `parse`)
648
- # @return [GraphQL::Schema] the schema described by `document`
649
- def self.from_definition(definition_or_path, default_resolve: BuildFromDefinition::DefaultResolve, parser: BuildFromDefinition::DefaultParser)
789
+ # @param using [Hash] Plugins to attach to the created schema with `use(key, value)`
790
+ # @param interpreter [Boolean] If false, the legacy {Execution::Execute} runtime will be used
791
+ # @return [Class] the schema described by `document`
792
+ def self.from_definition(definition_or_path, default_resolve: nil, interpreter: true, parser: GraphQL.default_parser, using: {})
650
793
  # If the file ends in `.graphql`, treat it like a filepath
651
- definition = if definition_or_path.end_with?(".graphql")
652
- File.read(definition_or_path)
794
+ if definition_or_path.end_with?(".graphql")
795
+ GraphQL::Schema::BuildFromDefinition.from_definition_path(
796
+ definition_or_path,
797
+ default_resolve: default_resolve,
798
+ parser: parser,
799
+ using: using,
800
+ interpreter: interpreter,
801
+ )
653
802
  else
654
- definition_or_path
803
+ GraphQL::Schema::BuildFromDefinition.from_definition(
804
+ definition_or_path,
805
+ default_resolve: default_resolve,
806
+ parser: parser,
807
+ using: using,
808
+ interpreter: interpreter,
809
+ )
655
810
  end
656
- GraphQL::Schema::BuildFromDefinition.from_definition(definition, default_resolve: default_resolve, parser: parser)
657
811
  end
658
812
 
659
813
  # Error that is raised when [#Schema#from_definition] is passed an invalid schema definition string.
660
814
  class InvalidDocumentError < Error; end;
661
815
 
662
- # @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered wtih {#lazy_resolve}.
663
- def lazy_method_name(obj)
664
- @lazy_methods.get(obj)
665
- end
666
-
667
- # @return [Boolean] True if this object should be lazily resolved
668
- def lazy?(obj)
669
- !!lazy_method_name(obj)
670
- end
671
-
672
816
  # Return the GraphQL IDL for the schema
673
817
  # @param context [Hash]
674
818
  # @param only [<#call(member, ctx)>]
@@ -679,9 +823,12 @@ module GraphQL
679
823
  end
680
824
 
681
825
  # Return the GraphQL::Language::Document IDL AST for the schema
826
+ # @param context [Hash]
827
+ # @param only [<#call(member, ctx)>]
828
+ # @param except [<#call(member, ctx)>]
682
829
  # @return [GraphQL::Language::Document]
683
- def to_document
684
- GraphQL::Language::DocumentFromSchemaDefinition.new(self).document
830
+ def to_document(only: nil, except: nil, context: {})
831
+ GraphQL::Language::DocumentFromSchemaDefinition.new(self, only: only, except: except, context: context).document
685
832
  end
686
833
 
687
834
  # Return the Hash response of {Introspection::INTROSPECTION_QUERY}.
@@ -690,7 +837,7 @@ module GraphQL
690
837
  # @param except [<#call(member, ctx)>]
691
838
  # @return [Hash] GraphQL result
692
839
  def as_json(only: nil, except: nil, context: {})
693
- execute(Introspection::INTROSPECTION_QUERY, only: only, except: except, context: context).to_h
840
+ execute(Introspection.query(include_deprecated_args: true), only: only, except: except, context: context).to_h
694
841
  end
695
842
 
696
843
  # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}.
@@ -700,6 +847,12 @@ module GraphQL
700
847
  JSON.pretty_generate(as_json(*args))
701
848
  end
702
849
 
850
+ def new_connections?
851
+ !!connections
852
+ end
853
+
854
+ attr_accessor :connections
855
+
703
856
  class << self
704
857
  extend Forwardable
705
858
  # For compatibility, these methods all:
@@ -707,39 +860,84 @@ module GraphQL
707
860
  # - Delegate to that instance
708
861
  # Eventually, the methods will be moved into this class, removing the need for the singleton.
709
862
  def_delegators :graphql_definition,
710
- # Schema structure
711
- :as_json, :to_json, :to_document, :to_definition, :ast_node,
712
863
  # Execution
713
- :execute, :multiplex,
714
- :static_validator, :introspection_system,
715
- :query_analyzers, :tracers, :instrumenters,
716
864
  :execution_strategy_for_operation,
717
- :validate, :multiplex_analyzers, :lazy?, :lazy_method_name, :after_lazy, :sync_lazy,
865
+ :validate,
718
866
  # Configuration
719
- :analysis_engine, :analysis_engine=, :using_ast_analysis?, :interpreter?,
720
- :max_complexity=, :max_depth=,
721
- :error_bubbling=,
722
- :metadata,
723
- :default_mask,
724
- :default_filter, :redefine,
867
+ :metadata, :redefine,
725
868
  :id_from_object_proc, :object_from_id_proc,
726
869
  :id_from_object=, :object_from_id=,
727
- :remove_handler,
728
- # Members
729
- :types, :get_fields, :find,
730
- :root_type_for_operation,
731
- :subscriptions,
732
- :union_memberships,
733
- :get_field, :root_types, :references_to, :type_from_ast,
734
- :possible_types,
735
- :disable_introspection_entry_points=
870
+ :remove_handler
871
+
872
+ # @return [GraphQL::Subscriptions]
873
+ attr_accessor :subscriptions
874
+
875
+ # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}.
876
+ # @see {#as_json}
877
+ # @return [String]
878
+ def to_json(**args)
879
+ JSON.pretty_generate(as_json(**args))
880
+ end
881
+
882
+ # Return the Hash response of {Introspection::INTROSPECTION_QUERY}.
883
+ # @param context [Hash]
884
+ # @param only [<#call(member, ctx)>]
885
+ # @param except [<#call(member, ctx)>]
886
+ # @return [Hash] GraphQL result
887
+ def as_json(only: nil, except: nil, context: {})
888
+ execute(Introspection.query(include_deprecated_args: true), only: only, except: except, context: context).to_h
889
+ end
890
+
891
+ # Return the GraphQL IDL for the schema
892
+ # @param context [Hash]
893
+ # @param only [<#call(member, ctx)>]
894
+ # @param except [<#call(member, ctx)>]
895
+ # @return [String]
896
+ def to_definition(only: nil, except: nil, context: {})
897
+ GraphQL::Schema::Printer.print_schema(self, only: only, except: except, context: context)
898
+ end
899
+
900
+ # Return the GraphQL::Language::Document IDL AST for the schema
901
+ # @return [GraphQL::Language::Document]
902
+ def to_document
903
+ GraphQL::Language::DocumentFromSchemaDefinition.new(self).document
904
+ end
905
+
906
+ def find(path)
907
+ if !@finder
908
+ @find_cache = {}
909
+ @finder ||= GraphQL::Schema::Finder.new(self)
910
+ end
911
+ @find_cache[path] ||= @finder.find(path)
912
+ end
736
913
 
737
914
  def graphql_definition
738
915
  @graphql_definition ||= to_graphql
739
916
  end
740
917
 
741
- def use(plugin, options = {})
742
- own_plugins << [plugin, options]
918
+ def default_filter
919
+ GraphQL::Filter.new(except: default_mask)
920
+ end
921
+
922
+ def default_mask(new_mask = nil)
923
+ if new_mask
924
+ @own_default_mask = new_mask
925
+ else
926
+ @own_default_mask || find_inherited_value(:default_mask, Schema::NullMask)
927
+ end
928
+ end
929
+
930
+ def static_validator
931
+ GraphQL::StaticValidation::Validator.new(schema: self)
932
+ end
933
+
934
+ def use(plugin, **kwargs)
935
+ if kwargs.any?
936
+ plugin.use(self, **kwargs)
937
+ else
938
+ plugin.use(self)
939
+ end
940
+ own_plugins << [plugin, kwargs]
743
941
  end
744
942
 
745
943
  def plugins
@@ -749,15 +947,18 @@ module GraphQL
749
947
  def to_graphql
750
948
  schema_defn = self.new
751
949
  schema_defn.raise_definition_error = true
752
- schema_defn.query = query
753
- schema_defn.mutation = mutation
754
- schema_defn.subscription = subscription
950
+ schema_defn.query = query && query.graphql_definition
951
+ schema_defn.mutation = mutation && mutation.graphql_definition
952
+ schema_defn.subscription = subscription && subscription.graphql_definition
953
+ schema_defn.validate_timeout = validate_timeout
755
954
  schema_defn.max_complexity = max_complexity
756
955
  schema_defn.error_bubbling = error_bubbling
757
956
  schema_defn.max_depth = max_depth
758
957
  schema_defn.default_max_page_size = default_max_page_size
759
- schema_defn.orphan_types = orphan_types
958
+ schema_defn.orphan_types = orphan_types.map(&:graphql_definition)
760
959
  schema_defn.disable_introspection_entry_points = disable_introspection_entry_points?
960
+ schema_defn.disable_schema_introspection_entry_point = disable_schema_introspection_entry_point?
961
+ schema_defn.disable_type_introspection_entry_point = disable_type_introspection_entry_point?
761
962
 
762
963
  prepped_dirs = {}
763
964
  directives.each { |k, v| prepped_dirs[k] = v.graphql_definition}
@@ -771,81 +972,268 @@ module GraphQL
771
972
  schema_defn.cursor_encoder = cursor_encoder
772
973
  schema_defn.tracers.concat(tracers)
773
974
  schema_defn.query_analyzers.concat(query_analyzers)
975
+ schema_defn.analysis_engine = analysis_engine
774
976
 
775
977
  schema_defn.middleware.concat(all_middleware)
776
978
  schema_defn.multiplex_analyzers.concat(multiplex_analyzers)
777
979
  schema_defn.query_execution_strategy = query_execution_strategy
778
980
  schema_defn.mutation_execution_strategy = mutation_execution_strategy
779
981
  schema_defn.subscription_execution_strategy = subscription_execution_strategy
780
- all_instrumenters.each do |step, insts|
982
+ schema_defn.default_mask = default_mask
983
+ instrumenters.each do |step, insts|
781
984
  insts.each do |inst|
782
985
  schema_defn.instrumenters[step] << inst
783
986
  end
784
987
  end
785
- lazy_classes.each do |lazy_class, value_method|
988
+
989
+ lazy_methods.each do |lazy_class, value_method|
786
990
  schema_defn.lazy_methods.set(lazy_class, value_method)
787
991
  end
992
+
788
993
  rescues.each do |err_class, handler|
789
994
  schema_defn.rescue_from(err_class, &handler)
790
995
  end
791
996
 
792
- if plugins.any?
793
- schema_plugins = plugins
794
- # TODO don't depend on .define
795
- schema_defn = schema_defn.redefine do
796
- schema_plugins.each do |plugin, options|
797
- if options.any?
798
- use(plugin, **options)
799
- else
800
- use(plugin)
801
- end
802
- end
803
- end
804
- end
805
- # Do this after `plugins` since Interpreter is a plugin
806
- if schema_defn.query_execution_strategy != GraphQL::Execution::Interpreter
997
+ schema_defn.subscriptions ||= self.subscriptions
998
+
999
+ if !schema_defn.interpreter?
807
1000
  schema_defn.instrumenters[:query] << GraphQL::Schema::Member::Instrumentation
808
1001
  end
1002
+
1003
+ if new_connections?
1004
+ schema_defn.connections = self.connections
1005
+ end
1006
+
809
1007
  schema_defn.send(:rebuild_artifacts)
810
1008
 
811
1009
  schema_defn
812
1010
  end
813
1011
 
1012
+ # Build a map of `{ name => type }` and return it
1013
+ # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
1014
+ # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
1015
+ def types
1016
+ non_introspection_types.merge(introspection_system.types)
1017
+ end
1018
+
1019
+ # @param type_name [String]
1020
+ # @return [Module, nil] A type, or nil if there's no type called `type_name`
1021
+ def get_type(type_name)
1022
+ own_types[type_name] ||
1023
+ introspection_system.types[type_name] ||
1024
+ find_inherited_value(:types, EMPTY_HASH)[type_name]
1025
+ end
1026
+
1027
+ # @api private
1028
+ attr_writer :connections
1029
+
1030
+ # @return [GraphQL::Pagination::Connections] if installed
1031
+ def connections
1032
+ if defined?(@connections)
1033
+ @connections
1034
+ else
1035
+ inherited_connections = find_inherited_value(:connections, nil)
1036
+ # This schema is part of an inheritance chain which is using new connections,
1037
+ # make a new instance, so we don't pollute the upstream one.
1038
+ if inherited_connections
1039
+ @connections = Pagination::Connections.new(schema: self)
1040
+ else
1041
+ nil
1042
+ end
1043
+ end
1044
+ end
1045
+
1046
+ def new_connections?
1047
+ !!connections
1048
+ end
1049
+
814
1050
  def query(new_query_object = nil)
815
1051
  if new_query_object
816
- @query_object = new_query_object
1052
+ if @query_object
1053
+ raise GraphQL::Error, "Second definition of `query(...)` (#{new_query_object.inspect}) is invalid, already configured with #{@query_object.inspect}"
1054
+ else
1055
+ @query_object = new_query_object
1056
+ add_type_and_traverse(new_query_object, root: true)
1057
+ nil
1058
+ end
817
1059
  else
818
- query_object = @query_object || find_inherited_value(:query)
819
- query_object.respond_to?(:graphql_definition) ? query_object.graphql_definition : query_object
1060
+ @query_object || find_inherited_value(:query)
820
1061
  end
821
1062
  end
822
1063
 
823
1064
  def mutation(new_mutation_object = nil)
824
1065
  if new_mutation_object
825
- @mutation_object = new_mutation_object
1066
+ if @mutation_object
1067
+ raise GraphQL::Error, "Second definition of `mutation(...)` (#{new_mutation_object.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
1068
+ else
1069
+ @mutation_object = new_mutation_object
1070
+ add_type_and_traverse(new_mutation_object, root: true)
1071
+ nil
1072
+ end
826
1073
  else
827
- mutation_object = @mutation_object || find_inherited_value(:mutation)
828
- mutation_object.respond_to?(:graphql_definition) ? mutation_object.graphql_definition : mutation_object
1074
+ @mutation_object || find_inherited_value(:mutation)
829
1075
  end
830
1076
  end
831
1077
 
832
1078
  def subscription(new_subscription_object = nil)
833
1079
  if new_subscription_object
834
- @subscription_object = new_subscription_object
1080
+ if @subscription_object
1081
+ raise GraphQL::Error, "Second definition of `subscription(...)` (#{new_subscription_object.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
1082
+ else
1083
+ @subscription_object = new_subscription_object
1084
+ add_subscription_extension_if_necessary
1085
+ add_type_and_traverse(new_subscription_object, root: true)
1086
+ nil
1087
+ end
1088
+ else
1089
+ @subscription_object || find_inherited_value(:subscription)
1090
+ end
1091
+ end
1092
+
1093
+ # @see [GraphQL::Schema::Warden] Resticted access to root types
1094
+ # @return [GraphQL::ObjectType, nil]
1095
+ def root_type_for_operation(operation)
1096
+ case operation
1097
+ when "query"
1098
+ query
1099
+ when "mutation"
1100
+ mutation
1101
+ when "subscription"
1102
+ subscription
1103
+ else
1104
+ raise ArgumentError, "unknown operation type: #{operation}"
1105
+ end
1106
+ end
1107
+
1108
+ def root_types
1109
+ @root_types
1110
+ end
1111
+
1112
+ # @param type [Module] The type definition whose possible types you want to see
1113
+ # @return [Hash<String, Module>] All possible types, if no `type` is given.
1114
+ # @return [Array<Module>] Possible types for `type`, if it's given.
1115
+ def possible_types(type = nil, context = GraphQL::Query::NullContext)
1116
+ if type
1117
+ # TODO duck-typing `.possible_types` would probably be nicer here
1118
+ if type.kind.union?
1119
+ type.possible_types(context: context)
1120
+ else
1121
+ stored_possible_types = own_possible_types[type.graphql_name]
1122
+ visible_possible_types = stored_possible_types.select do |possible_type|
1123
+ next true unless type.kind.interface?
1124
+ next true unless possible_type.kind.object?
1125
+
1126
+ # Use `.graphql_name` comparison to match legacy vs class-based types.
1127
+ # When we don't need to support legacy `.define` types, use `.include?(type)` instead.
1128
+ possible_type.interfaces(context).any? { |interface| interface.graphql_name == type.graphql_name }
1129
+ end if stored_possible_types
1130
+ visible_possible_types ||
1131
+ introspection_system.possible_types[type.graphql_name] ||
1132
+ (
1133
+ superclass.respond_to?(:possible_types) ?
1134
+ superclass.possible_types(type, context) :
1135
+ EMPTY_ARRAY
1136
+ )
1137
+ end
1138
+ else
1139
+ find_inherited_value(:possible_types, EMPTY_HASH)
1140
+ .merge(own_possible_types)
1141
+ .merge(introspection_system.possible_types)
1142
+ end
1143
+ end
1144
+
1145
+ def union_memberships(type = nil)
1146
+ if type
1147
+ own_um = own_union_memberships.fetch(type.graphql_name, EMPTY_ARRAY)
1148
+ inherited_um = find_inherited_value(:union_memberships, EMPTY_HASH).fetch(type.graphql_name, EMPTY_ARRAY)
1149
+ own_um + inherited_um
1150
+ else
1151
+ joined_um = own_union_memberships.dup
1152
+ find_inherited_value(:union_memberhips, EMPTY_HASH).each do |k, v|
1153
+ um = joined_um[k] ||= []
1154
+ um.concat(v)
1155
+ end
1156
+ joined_um
1157
+ end
1158
+ end
1159
+
1160
+ def references_to(to_type = nil, from: nil)
1161
+ @own_references_to ||= Hash.new { |h, k| h[k] = [] }
1162
+ if to_type
1163
+ if !to_type.is_a?(String)
1164
+ to_type = to_type.graphql_name
1165
+ end
1166
+
1167
+ if from
1168
+ @own_references_to[to_type] << from
1169
+ else
1170
+ own_refs = @own_references_to[to_type]
1171
+ inherited_refs = find_inherited_value(:references_to, EMPTY_HASH)[to_type] || EMPTY_ARRAY
1172
+ own_refs + inherited_refs
1173
+ end
1174
+ else
1175
+ # `@own_references_to` can be quite large for big schemas,
1176
+ # and generally speaking, we won't inherit any values.
1177
+ # So optimize the most common case -- don't create a duplicate Hash.
1178
+ inherited_value = find_inherited_value(:references_to, EMPTY_HASH)
1179
+ if inherited_value.any?
1180
+ inherited_value.merge(@own_references_to)
1181
+ else
1182
+ @own_references_to
1183
+ end
1184
+ end
1185
+ end
1186
+
1187
+ def type_from_ast(ast_node, context: nil)
1188
+ type_owner = context ? context.warden : self
1189
+ GraphQL::Schema::TypeExpression.build_type(type_owner, ast_node)
1190
+ end
1191
+
1192
+ def get_field(type_or_name, field_name)
1193
+ parent_type = case type_or_name
1194
+ when LateBoundType
1195
+ get_type(type_or_name.name)
1196
+ when String
1197
+ get_type(type_or_name)
1198
+ when Module
1199
+ type_or_name
1200
+ else
1201
+ raise ArgumentError, "unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})"
1202
+ end
1203
+
1204
+ if parent_type.kind.fields? && (field = parent_type.get_field(field_name))
1205
+ field
1206
+ elsif parent_type == query && (entry_point_field = introspection_system.entry_point(name: field_name))
1207
+ entry_point_field
1208
+ elsif (dynamic_field = introspection_system.dynamic_field(name: field_name))
1209
+ dynamic_field
835
1210
  else
836
- subscription_object = @subscription_object || find_inherited_value(:subscription)
837
- subscription_object.respond_to?(:graphql_definition) ? subscription_object.graphql_definition : subscription_object
1211
+ nil
838
1212
  end
839
1213
  end
840
1214
 
1215
+ def get_fields(type)
1216
+ type.fields
1217
+ end
1218
+
841
1219
  def introspection(new_introspection_namespace = nil)
842
1220
  if new_introspection_namespace
843
1221
  @introspection = new_introspection_namespace
1222
+ # reset this cached value:
1223
+ @introspection_system = nil
844
1224
  else
845
1225
  @introspection || find_inherited_value(:introspection)
846
1226
  end
847
1227
  end
848
1228
 
1229
+ def introspection_system
1230
+ if !@introspection_system
1231
+ @introspection_system = Schema::IntrospectionSystem.new(self)
1232
+ @introspection_system.resolve_late_bindings
1233
+ end
1234
+ @introspection_system
1235
+ end
1236
+
849
1237
  def cursor_encoder(new_encoder = nil)
850
1238
  if new_encoder
851
1239
  @cursor_encoder = new_encoder
@@ -885,14 +1273,50 @@ module GraphQL
885
1273
  end
886
1274
  end
887
1275
 
1276
+ attr_writer :validate_timeout
1277
+
1278
+ def validate_timeout(new_validate_timeout = nil)
1279
+ if new_validate_timeout
1280
+ @validate_timeout = new_validate_timeout
1281
+ elsif defined?(@validate_timeout)
1282
+ @validate_timeout
1283
+ else
1284
+ find_inherited_value(:validate_timeout)
1285
+ end
1286
+ end
1287
+
1288
+ attr_writer :max_complexity
1289
+
888
1290
  def max_complexity(max_complexity = nil)
889
1291
  if max_complexity
890
1292
  @max_complexity = max_complexity
1293
+ elsif defined?(@max_complexity)
1294
+ @max_complexity
891
1295
  else
892
- @max_complexity || find_inherited_value(:max_complexity)
1296
+ find_inherited_value(:max_complexity)
893
1297
  end
894
1298
  end
895
1299
 
1300
+ attr_writer :analysis_engine
1301
+
1302
+ def analysis_engine
1303
+ @analysis_engine || find_inherited_value(:analysis_engine, GraphQL::Analysis)
1304
+ end
1305
+
1306
+ def using_ast_analysis?
1307
+ analysis_engine == GraphQL::Analysis::AST
1308
+ end
1309
+
1310
+ def interpreter?
1311
+ if defined?(@interpreter)
1312
+ @interpreter
1313
+ else
1314
+ find_inherited_value(:interpreter?, false)
1315
+ end
1316
+ end
1317
+
1318
+ attr_writer :interpreter
1319
+
896
1320
  def error_bubbling(new_error_bubbling = nil)
897
1321
  if !new_error_bubbling.nil?
898
1322
  @error_bubbling = new_error_bubbling
@@ -901,16 +1325,36 @@ module GraphQL
901
1325
  end
902
1326
  end
903
1327
 
1328
+ attr_writer :error_bubbling
1329
+
1330
+ attr_writer :max_depth
1331
+
904
1332
  def max_depth(new_max_depth = nil)
905
1333
  if new_max_depth
906
1334
  @max_depth = new_max_depth
1335
+ elsif defined?(@max_depth)
1336
+ @max_depth
907
1337
  else
908
- @max_depth || find_inherited_value(:max_depth)
1338
+ find_inherited_value(:max_depth)
909
1339
  end
910
1340
  end
911
1341
 
912
1342
  def disable_introspection_entry_points
913
1343
  @disable_introspection_entry_points = true
1344
+ # TODO: this clears the cache made in `def types`. But this is not a great solution.
1345
+ @introspection_system = nil
1346
+ end
1347
+
1348
+ def disable_schema_introspection_entry_point
1349
+ @disable_schema_introspection_entry_point = true
1350
+ # TODO: this clears the cache made in `def types`. But this is not a great solution.
1351
+ @introspection_system = nil
1352
+ end
1353
+
1354
+ def disable_type_introspection_entry_point
1355
+ @disable_type_introspection_entry_point = true
1356
+ # TODO: this clears the cache made in `def types`. But this is not a great solution.
1357
+ @introspection_system = nil
914
1358
  end
915
1359
 
916
1360
  def disable_introspection_entry_points?
@@ -921,8 +1365,27 @@ module GraphQL
921
1365
  end
922
1366
  end
923
1367
 
1368
+ def disable_schema_introspection_entry_point?
1369
+ if instance_variable_defined?(:@disable_schema_introspection_entry_point)
1370
+ @disable_schema_introspection_entry_point
1371
+ else
1372
+ find_inherited_value(:disable_schema_introspection_entry_point?, false)
1373
+ end
1374
+ end
1375
+
1376
+ def disable_type_introspection_entry_point?
1377
+ if instance_variable_defined?(:@disable_type_introspection_entry_point)
1378
+ @disable_type_introspection_entry_point
1379
+ else
1380
+ find_inherited_value(:disable_type_introspection_entry_point?, false)
1381
+ end
1382
+ end
1383
+
924
1384
  def orphan_types(*new_orphan_types)
925
1385
  if new_orphan_types.any?
1386
+ new_orphan_types = new_orphan_types.flatten
1387
+ add_type_and_traverse(new_orphan_types, root: false)
1388
+ @orphan_types = new_orphan_types
926
1389
  own_orphan_types.concat(new_orphan_types.flatten)
927
1390
  end
928
1391
 
@@ -951,8 +1414,27 @@ module GraphQL
951
1414
  end
952
1415
  end
953
1416
 
954
- def rescues
955
- find_inherited_value(:rescues, EMPTY_HASH).merge(own_rescues)
1417
+ # rubocop:disable Lint/DuplicateMethods
1418
+ module ResolveTypeWithType
1419
+ def resolve_type(type, obj, ctx)
1420
+ first_resolved_type, resolved_value = if type.is_a?(Module) && type.respond_to?(:resolve_type)
1421
+ type.resolve_type(obj, ctx)
1422
+ else
1423
+ super
1424
+ end
1425
+
1426
+ after_lazy(first_resolved_type) do |resolved_type|
1427
+ if resolved_type.nil? || (resolved_type.is_a?(Module) && resolved_type.respond_to?(:kind)) || resolved_type.is_a?(GraphQL::BaseType)
1428
+ if resolved_value
1429
+ [resolved_type, resolved_value]
1430
+ else
1431
+ resolved_type
1432
+ end
1433
+ else
1434
+ raise ".resolve_type should return a type definition, but got #{resolved_type.inspect} (#{resolved_type.class}) from `resolve_type(#{type}, #{obj}, #{ctx})`"
1435
+ end
1436
+ end
1437
+ end
956
1438
  end
957
1439
 
958
1440
  def resolve_type(type, obj, ctx)
@@ -962,6 +1444,19 @@ module GraphQL
962
1444
  raise GraphQL::RequiredImplementationMissingError, "#{self.name}.resolve_type(type, obj, ctx) must be implemented to use Union types or Interface types (tried to resolve: #{type.name})"
963
1445
  end
964
1446
  end
1447
+ # rubocop:enable Lint/DuplicateMethods
1448
+
1449
+ def inherited(child_class)
1450
+ if self == GraphQL::Schema
1451
+ child_class.directives(default_directives.values)
1452
+ end
1453
+ child_class.singleton_class.prepend(ResolveTypeWithType)
1454
+ super
1455
+ end
1456
+
1457
+ def rescues
1458
+ find_inherited_value(:rescues, EMPTY_HASH).merge(own_rescues)
1459
+ end
965
1460
 
966
1461
  def object_from_id(node_id, ctx)
967
1462
  raise GraphQL::RequiredImplementationMissingError, "#{self.name}.object_from_id(node_id, ctx) must be implemented to load by ID (tried to load from id `#{node_id}`)"
@@ -971,12 +1466,12 @@ module GraphQL
971
1466
  raise GraphQL::RequiredImplementationMissingError, "#{self.name}.id_from_object(object, type, ctx) must be implemented to create global ids (tried to create an id for `#{object.inspect}`)"
972
1467
  end
973
1468
 
974
- def visible?(member, context)
975
- call_on_type_class(member, :visible?, context, default: true)
1469
+ def visible?(member, ctx)
1470
+ member.type_class.visible?(ctx)
976
1471
  end
977
1472
 
978
- def accessible?(member, context)
979
- call_on_type_class(member, :accessible?, context, default: true)
1473
+ def accessible?(member, ctx)
1474
+ member.type_class.accessible?(ctx)
980
1475
  end
981
1476
 
982
1477
  # This hook is called when a client tries to access one or more
@@ -1030,15 +1525,28 @@ module GraphQL
1030
1525
  DefaultTypeError.call(type_err, ctx)
1031
1526
  end
1032
1527
 
1528
+ # A function to call when {#execute} receives an invalid query string
1529
+ #
1530
+ # The default is to add the error to `context.errors`
1531
+ # @param err [GraphQL::ParseError] The error encountered during parsing
1532
+ # @param ctx [GraphQL::Query::Context] The context for the query where the error occurred
1533
+ # @return void
1534
+ def parse_error(parse_err, ctx)
1535
+ ctx.errors.push(parse_err)
1536
+ end
1033
1537
  attr_writer :error_handler
1034
1538
 
1035
1539
  # @return [GraphQL::Execution::Errors, Class<GraphQL::Execution::Errors::NullErrorHandler>]
1036
1540
  def error_handler
1037
- @error_handler ||= GraphQL::Execution::Errors::NullErrorHandler
1541
+ if defined?(@error_handler)
1542
+ @error_handler
1543
+ else
1544
+ find_inherited_value(:error_handler, GraphQL::Execution::Errors::NullErrorHandler)
1545
+ end
1038
1546
  end
1039
1547
 
1040
1548
  def lazy_resolve(lazy_class, value_method)
1041
- lazy_classes[lazy_class] = value_method
1549
+ lazy_methods.set(lazy_class, value_method)
1042
1550
  end
1043
1551
 
1044
1552
  def instrument(instrument_step, instrumenter, options = {})
@@ -1051,24 +1559,29 @@ module GraphQL
1051
1559
  own_instrumenters[step] << instrumenter
1052
1560
  end
1053
1561
 
1054
- def directives(new_directives = nil)
1055
- if new_directives
1056
- new_directives.each {|d| directive(d) }
1562
+ # Add several directives at once
1563
+ # @param new_directives [Class]
1564
+ def directives(*new_directives)
1565
+ if new_directives.any?
1566
+ new_directives.flatten.each { |d| directive(d) }
1057
1567
  end
1058
1568
 
1059
1569
  find_inherited_value(:directives, default_directives).merge(own_directives)
1060
1570
  end
1061
1571
 
1572
+ # Attach a single directive to this schema
1573
+ # @param new_directive [Class]
1062
1574
  def directive(new_directive)
1575
+ add_type_and_traverse(new_directive, root: false)
1063
1576
  own_directives[new_directive.graphql_name] = new_directive
1064
1577
  end
1065
1578
 
1066
1579
  def default_directives
1067
- {
1068
- "include" => GraphQL::Directive::IncludeDirective,
1069
- "skip" => GraphQL::Directive::SkipDirective,
1070
- "deprecated" => GraphQL::Directive::DeprecatedDirective,
1071
- }
1580
+ @default_directives ||= {
1581
+ "include" => GraphQL::Schema::Directive::Include,
1582
+ "skip" => GraphQL::Schema::Directive::Skip,
1583
+ "deprecated" => GraphQL::Schema::Directive::Deprecated,
1584
+ }.freeze
1072
1585
  end
1073
1586
 
1074
1587
  def tracer(new_tracer)
@@ -1094,7 +1607,8 @@ module GraphQL
1094
1607
  if new_middleware
1095
1608
  own_middleware << new_middleware
1096
1609
  else
1097
- graphql_definition.middleware
1610
+ # TODO make sure this is cached when running a query
1611
+ MiddlewareChain.new(steps: all_middleware, final_step: GraphQL::Execution::Execute::FieldResolveStep)
1098
1612
  end
1099
1613
  end
1100
1614
 
@@ -1106,10 +1620,101 @@ module GraphQL
1106
1620
  find_inherited_value(:multiplex_analyzers, EMPTY_ARRAY) + own_multiplex_analyzers
1107
1621
  end
1108
1622
 
1623
+ # Execute a query on itself.
1624
+ # @see {Query#initialize} for arguments.
1625
+ # @return [Hash] query result, ready to be serialized as JSON
1626
+ def execute(query_str = nil, **kwargs)
1627
+ if query_str
1628
+ kwargs[:query] = query_str
1629
+ end
1630
+ # Some of the query context _should_ be passed to the multiplex, too
1631
+ multiplex_context = if (ctx = kwargs[:context])
1632
+ {
1633
+ backtrace: ctx[:backtrace],
1634
+ tracers: ctx[:tracers],
1635
+ }
1636
+ else
1637
+ {}
1638
+ end
1639
+ # Since we're running one query, don't run a multiplex-level complexity analyzer
1640
+ all_results = multiplex([kwargs], max_complexity: nil, context: multiplex_context)
1641
+ all_results[0]
1642
+ end
1643
+
1644
+ # Execute several queries on itself, concurrently.
1645
+ #
1646
+ # @example Run several queries at once
1647
+ # context = { ... }
1648
+ # queries = [
1649
+ # { query: params[:query_1], variables: params[:variables_1], context: context },
1650
+ # { query: params[:query_2], variables: params[:variables_2], context: context },
1651
+ # ]
1652
+ # results = MySchema.multiplex(queries)
1653
+ # render json: {
1654
+ # result_1: results[0],
1655
+ # result_2: results[1],
1656
+ # }
1657
+ #
1658
+ # @see {Query#initialize} for query keyword arguments
1659
+ # @see {Execution::Multiplex#run_queries} for multiplex keyword arguments
1660
+ # @param queries [Array<Hash>] Keyword arguments for each query
1661
+ # @param context [Hash] Multiplex-level context
1662
+ # @return [Array<Hash>] One result for each query in the input
1663
+ def multiplex(queries, **kwargs)
1664
+ schema = if interpreter?
1665
+ self
1666
+ else
1667
+ graphql_definition
1668
+ end
1669
+ GraphQL::Execution::Multiplex.run_all(schema, queries, **kwargs)
1670
+ end
1671
+
1672
+ def instrumenters
1673
+ inherited_instrumenters = find_inherited_value(:instrumenters) || Hash.new { |h,k| h[k] = [] }
1674
+ inherited_instrumenters.merge(own_instrumenters) do |_step, inherited, own|
1675
+ inherited + own
1676
+ end
1677
+ end
1678
+
1679
+ # @api private
1680
+ def add_subscription_extension_if_necessary
1681
+ if interpreter? && !defined?(@subscription_extension_added) && subscription && self.subscriptions
1682
+ @subscription_extension_added = true
1683
+ if subscription.singleton_class.ancestors.include?(Subscriptions::SubscriptionRoot)
1684
+ warn("`extend Subscriptions::SubscriptionRoot` is no longer required; you may remove it from #{self}'s `subscription` root type (#{subscription}).")
1685
+ else
1686
+ subscription.fields.each do |name, field|
1687
+ field.extension(Subscriptions::DefaultSubscriptionResolveExtension)
1688
+ end
1689
+ end
1690
+ end
1691
+ end
1692
+
1693
+ def query_stack_error(query, err)
1694
+ query.context.errors.push(GraphQL::ExecutionError.new("This query is too large to execute."))
1695
+ end
1696
+
1109
1697
  private
1110
1698
 
1111
- def lazy_classes
1112
- @lazy_classes ||= {}
1699
+ def lazy_methods
1700
+ if !defined?(@lazy_methods)
1701
+ if inherited_map = find_inherited_value(:lazy_methods)
1702
+ # this isn't _completely_ inherited :S (Things added after `dup` won't work)
1703
+ @lazy_methods = inherited_map.dup
1704
+ else
1705
+ @lazy_methods = GraphQL::Execution::Lazy::LazyMethodMap.new
1706
+ @lazy_methods.set(GraphQL::Execution::Lazy, :value)
1707
+ end
1708
+ end
1709
+ @lazy_methods
1710
+ end
1711
+
1712
+ def own_types
1713
+ @own_types ||= {}
1714
+ end
1715
+
1716
+ def non_introspection_types
1717
+ find_inherited_value(:non_introspection_types, EMPTY_HASH).merge(own_types)
1113
1718
  end
1114
1719
 
1115
1720
  def own_plugins
@@ -1124,15 +1729,16 @@ module GraphQL
1124
1729
  @own_orphan_types ||= []
1125
1730
  end
1126
1731
 
1127
- def own_directives
1128
- @own_directives ||= {}
1732
+ def own_possible_types
1733
+ @own_possible_types ||= {}
1129
1734
  end
1130
1735
 
1131
- def all_instrumenters
1132
- inherited_instrumenters = find_inherited_value(:all_instrumenters) || Hash.new { |h,k| h[k] = [] }
1133
- inherited_instrumenters.merge(own_instrumenters) do |_step, inherited, own|
1134
- inherited + own
1135
- end
1736
+ def own_union_memberships
1737
+ @own_union_memberships ||= {}
1738
+ end
1739
+
1740
+ def own_directives
1741
+ @own_directives ||= {}
1136
1742
  end
1137
1743
 
1138
1744
  def own_instrumenters
@@ -1159,91 +1765,193 @@ module GraphQL
1159
1765
  @own_multiplex_analyzers ||= []
1160
1766
  end
1161
1767
 
1162
- # Given this schema member, find the class-based definition object
1163
- # whose `method_name` should be treated as an application hook
1164
- # @see {.visible?}
1165
- # @see {.accessible?}
1166
- # @see {.authorized?}
1167
- def call_on_type_class(member, method_name, *args, default:)
1168
- member = if member.respond_to?(:metadata) && member.metadata
1169
- member.metadata[:type_class] || member
1170
- else
1171
- member
1172
- end
1173
-
1174
- if member.respond_to?(:relay_node_type) && (t = member.relay_node_type)
1175
- member = t
1768
+ # @param t [Module, Array<Module>]
1769
+ # @return [void]
1770
+ def add_type_and_traverse(t, root:)
1771
+ if root
1772
+ @root_types ||= []
1773
+ @root_types << t
1176
1774
  end
1177
-
1178
- if member.respond_to?(method_name)
1179
- member.public_send(method_name, *args)
1180
- else
1181
- default
1775
+ late_types = []
1776
+ new_types = Array(t)
1777
+ new_types.each { |t| add_type(t, owner: nil, late_types: late_types, path: [t.graphql_name]) }
1778
+ missed_late_types = 0
1779
+ while (late_type_vals = late_types.shift)
1780
+ type_owner, lt = late_type_vals
1781
+ if lt.is_a?(String)
1782
+ type = Member::BuildType.constantize(lt)
1783
+ # Reset the counter, since we might succeed next go-round
1784
+ missed_late_types = 0
1785
+ update_type_owner(type_owner, type)
1786
+ add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name])
1787
+ elsif lt.is_a?(LateBoundType)
1788
+ if (type = get_type(lt.graphql_name))
1789
+ # Reset the counter, since we might succeed next go-round
1790
+ missed_late_types = 0
1791
+ update_type_owner(type_owner, type)
1792
+ add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name])
1793
+ else
1794
+ missed_late_types += 1
1795
+ # Add it back to the list, maybe we'll be able to resolve it later.
1796
+ late_types << [type_owner, lt]
1797
+ if missed_late_types == late_types.size
1798
+ # We've looked at all of them and haven't resolved one.
1799
+ raise UnresolvedLateBoundTypeError.new(type: lt)
1800
+ else
1801
+ # Try the next one
1802
+ end
1803
+ end
1804
+ else
1805
+ raise ArgumentError, "Unexpected late type: #{lt.inspect}"
1806
+ end
1182
1807
  end
1808
+ nil
1183
1809
  end
1184
- end
1185
-
1186
1810
 
1187
- def self.inherited(child_class)
1188
- child_class.singleton_class.class_eval do
1189
- prepend(MethodWrappers)
1190
- end
1191
- end
1811
+ def update_type_owner(owner, type)
1812
+ case owner
1813
+ when Class
1814
+ if owner.kind.union?
1815
+ # It's a union with possible_types
1816
+ # Replace the item by class name
1817
+ owner.assign_type_membership_object_type(type)
1818
+ own_possible_types[owner.graphql_name] = owner.possible_types
1819
+ elsif type.kind.interface? && owner.kind.object?
1820
+ new_interfaces = []
1821
+ owner.interfaces.each do |int_t|
1822
+ if int_t.is_a?(String) && int_t == type.graphql_name
1823
+ new_interfaces << type
1824
+ elsif int_t.is_a?(LateBoundType) && int_t.graphql_name == type.graphql_name
1825
+ new_interfaces << type
1826
+ else
1827
+ # Don't re-add proper interface definitions,
1828
+ # they were probably already added, maybe with options.
1829
+ end
1830
+ end
1831
+ owner.implements(*new_interfaces)
1832
+ new_interfaces.each do |int|
1833
+ pt = own_possible_types[int.graphql_name] ||= []
1834
+ if !pt.include?(owner)
1835
+ pt << owner
1836
+ end
1837
+ end
1838
+ end
1192
1839
 
1193
- module MethodWrappers
1194
- # Wrap the user-provided resolve-type in a correctness check
1195
- def resolve_type(type, obj, ctx = :__undefined__)
1196
- graphql_definition.check_resolved_type(type, obj, ctx) do |ok_type, ok_obj, ok_ctx|
1197
- super(ok_type, ok_obj, ok_ctx)
1840
+ when nil
1841
+ # It's a root type
1842
+ own_types[type.graphql_name] = type
1843
+ when GraphQL::Schema::Field, GraphQL::Schema::Argument
1844
+ orig_type = owner.type
1845
+ # Apply list/non-null wrapper as needed
1846
+ if orig_type.respond_to?(:of_type)
1847
+ transforms = []
1848
+ while (orig_type.respond_to?(:of_type))
1849
+ if orig_type.kind.non_null?
1850
+ transforms << :to_non_null_type
1851
+ elsif orig_type.kind.list?
1852
+ transforms << :to_list_type
1853
+ else
1854
+ raise "Invariant: :of_type isn't non-null or list"
1855
+ end
1856
+ orig_type = orig_type.of_type
1857
+ end
1858
+ transforms.reverse_each { |t| type = type.public_send(t) }
1859
+ end
1860
+ owner.type = type
1861
+ else
1862
+ raise "Unexpected update: #{owner.inspect} #{type.inspect}"
1198
1863
  end
1199
1864
  end
1200
- end
1201
1865
 
1202
- # Call the given block at the right time, either:
1203
- # - Right away, if `value` is not registered with `lazy_resolve`
1204
- # - After resolving `value`, if it's registered with `lazy_resolve` (eg, `Promise`)
1205
- # @api private
1206
- def after_lazy(value)
1207
- if lazy?(value)
1208
- GraphQL::Execution::Lazy.new do
1209
- result = sync_lazy(value)
1210
- # The returned result might also be lazy, so check it, too
1211
- after_lazy(result) do |final_result|
1212
- yield(final_result) if block_given?
1866
+ def add_type(type, owner:, late_types:, path:)
1867
+ if type.respond_to?(:metadata) && type.metadata.is_a?(Hash)
1868
+ type_class = type.metadata[:type_class]
1869
+ if type_class.nil?
1870
+ raise ArgumentError, "Can't add legacy type: #{type} (#{type.class})"
1871
+ else
1872
+ type = type_class
1213
1873
  end
1874
+ elsif type.is_a?(String) || type.is_a?(GraphQL::Schema::LateBoundType)
1875
+ late_types << [owner, type]
1876
+ return
1214
1877
  end
1215
- else
1216
- yield(value) if block_given?
1217
- end
1218
- end
1219
1878
 
1220
- # Override this method to handle lazy objects in a custom way.
1221
- # @param value [Object] an instance of a class registered with {.lazy_resolve}
1222
- # @param ctx [GraphQL::Query::Context] the context for this query
1223
- # @return [Object] A GraphQL-ready (non-lazy) object
1224
- def self.sync_lazy(value)
1225
- if block_given?
1226
- # This was already hit by the instance, just give it back
1227
- yield(value)
1228
- else
1229
- # This was called directly on the class, hit the instance
1230
- # which has the lazy method map
1231
- self.graphql_definition.sync_lazy(value)
1232
- end
1233
- end
1879
+ if owner.is_a?(Class) && owner < GraphQL::Schema::Union
1880
+ um = own_union_memberships[type.graphql_name] ||= []
1881
+ um << owner
1882
+ end
1234
1883
 
1235
- # @see Schema.sync_lazy for a hook to override
1236
- # @api private
1237
- def sync_lazy(value)
1238
- self.class.sync_lazy(value) { |v|
1239
- lazy_method = lazy_method_name(v)
1240
- if lazy_method
1241
- synced_value = value.public_send(lazy_method)
1242
- sync_lazy(synced_value)
1884
+ if (prev_type = own_types[type.graphql_name])
1885
+ if prev_type != type
1886
+ raise DuplicateTypeNamesError.new(
1887
+ type_name: type.graphql_name,
1888
+ first_definition: prev_type,
1889
+ second_definition: type,
1890
+ path: path,
1891
+ )
1892
+ else
1893
+ # This type was already added
1894
+ end
1895
+ elsif type.is_a?(Class) && type < GraphQL::Schema::Directive
1896
+ type.arguments.each do |name, arg|
1897
+ arg_type = arg.type.unwrap
1898
+ references_to(arg_type, from: arg)
1899
+ add_type(arg_type, owner: arg, late_types: late_types, path: path + [name])
1900
+ end
1243
1901
  else
1244
- v
1902
+ own_types[type.graphql_name] = type
1903
+ if type.kind.fields?
1904
+ type.fields.each do |name, field|
1905
+ field_type = field.type.unwrap
1906
+ references_to(field_type, from: field)
1907
+ field_path = path + [name]
1908
+ add_type(field_type, owner: field, late_types: late_types, path: field_path)
1909
+ field.arguments.each do |arg_name, arg|
1910
+ arg_type = arg.type.unwrap
1911
+ references_to(arg_type, from: arg)
1912
+ add_type(arg_type, owner: arg, late_types: late_types, path: field_path + [arg_name])
1913
+ end
1914
+ end
1915
+ end
1916
+ if type.kind.input_object?
1917
+ type.arguments.each do |arg_name, arg|
1918
+ arg_type = arg.type.unwrap
1919
+ references_to(arg_type, from: arg)
1920
+ add_type(arg_type, owner: arg, late_types: late_types, path: path + [arg_name])
1921
+ end
1922
+ end
1923
+ if type.kind.union?
1924
+ own_possible_types[type.graphql_name] = type.possible_types
1925
+ type.possible_types.each do |t|
1926
+ add_type(t, owner: type, late_types: late_types, path: path + ["possible_types"])
1927
+ end
1928
+ end
1929
+ if type.kind.interface?
1930
+ type.orphan_types.each do |t|
1931
+ add_type(t, owner: type, late_types: late_types, path: path + ["orphan_types"])
1932
+ end
1933
+ end
1934
+ if type.kind.object?
1935
+ own_possible_types[type.graphql_name] = [type]
1936
+ type.interface_type_memberships.each do |interface_type_membership|
1937
+ case interface_type_membership
1938
+ when Schema::TypeMembership
1939
+ interface_type = interface_type_membership.abstract_type
1940
+ # We can get these now; we'll have to get late-bound types later
1941
+ if interface_type.is_a?(Module)
1942
+ implementers = own_possible_types[interface_type.graphql_name] ||= []
1943
+ implementers << type
1944
+ end
1945
+ when String, Schema::LateBoundType
1946
+ interface_type = interface_type_membership
1947
+ else
1948
+ raise ArgumentError, "Invariant: unexpected type membership for #{type.graphql_name}: #{interface_type_membership.class} (#{interface_type_membership.inspect})"
1949
+ end
1950
+ add_type(interface_type, owner: type, late_types: late_types, path: path + ["implements"])
1951
+ end
1952
+ end
1245
1953
  end
1246
- }
1954
+ end
1247
1955
  end
1248
1956
 
1249
1957
  protected