graphql 2.0.31 → 2.5.18

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 (287) 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/templates/base_mutation.erb +2 -0
  4. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  5. data/lib/generators/graphql/install_generator.rb +49 -0
  6. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  7. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  8. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  9. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  10. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  11. data/lib/generators/graphql/templates/base_field.erb +2 -0
  12. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  13. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  14. data/lib/generators/graphql/templates/base_object.erb +2 -0
  15. data/lib/generators/graphql/templates/base_resolver.erb +8 -0
  16. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  17. data/lib/generators/graphql/templates/base_union.erb +2 -0
  18. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  19. data/lib/generators/graphql/templates/loader.erb +2 -0
  20. data/lib/generators/graphql/templates/mutation.erb +2 -0
  21. data/lib/generators/graphql/templates/node_type.erb +2 -0
  22. data/lib/generators/graphql/templates/query_type.erb +2 -0
  23. data/lib/generators/graphql/templates/schema.erb +5 -0
  24. data/lib/generators/graphql/type_generator.rb +1 -1
  25. data/lib/graphql/analysis/analyzer.rb +90 -0
  26. data/lib/graphql/analysis/field_usage.rb +82 -0
  27. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  28. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  29. data/lib/graphql/analysis/query_complexity.rb +263 -0
  30. data/lib/graphql/analysis/query_depth.rb +58 -0
  31. data/lib/graphql/analysis/visitor.rb +280 -0
  32. data/lib/graphql/analysis.rb +95 -1
  33. data/lib/graphql/autoload.rb +38 -0
  34. data/lib/graphql/backtrace/table.rb +118 -55
  35. data/lib/graphql/backtrace.rb +1 -19
  36. data/lib/graphql/coercion_error.rb +1 -9
  37. data/lib/graphql/current.rb +57 -0
  38. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  39. data/lib/graphql/dashboard/installable.rb +22 -0
  40. data/lib/graphql/dashboard/limiters.rb +93 -0
  41. data/lib/graphql/dashboard/operation_store.rb +199 -0
  42. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  43. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  44. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  45. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  46. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  47. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  48. data/lib/graphql/dashboard/statics/icon.png +0 -0
  49. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  50. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  51. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  52. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  53. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  54. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +24 -0
  55. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  56. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  57. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  58. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  59. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  60. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  61. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  62. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  63. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  64. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  65. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  66. data/lib/graphql/dashboard.rb +161 -0
  67. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  68. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  69. data/lib/graphql/dataloader/async_dataloader.rb +112 -0
  70. data/lib/graphql/dataloader/null_dataloader.rb +51 -10
  71. data/lib/graphql/dataloader/request.rb +5 -0
  72. data/lib/graphql/dataloader/source.rb +35 -12
  73. data/lib/graphql/dataloader.rb +224 -149
  74. data/lib/graphql/date_encoding_error.rb +1 -1
  75. data/lib/graphql/dig.rb +2 -1
  76. data/lib/graphql/duration_encoding_error.rb +16 -0
  77. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  78. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  79. data/lib/graphql/execution/interpreter/resolve.rb +23 -25
  80. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +228 -0
  81. data/lib/graphql/execution/interpreter/runtime.rb +363 -434
  82. data/lib/graphql/execution/interpreter.rb +91 -164
  83. data/lib/graphql/execution/lookahead.rb +105 -31
  84. data/lib/graphql/execution/multiplex.rb +6 -5
  85. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  86. data/lib/graphql/introspection/directive_type.rb +1 -1
  87. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  88. data/lib/graphql/introspection/entry_points.rb +11 -5
  89. data/lib/graphql/introspection/field_type.rb +1 -1
  90. data/lib/graphql/introspection/schema_type.rb +8 -11
  91. data/lib/graphql/introspection/type_type.rb +5 -5
  92. data/lib/graphql/invalid_name_error.rb +1 -1
  93. data/lib/graphql/invalid_null_error.rb +20 -17
  94. data/lib/graphql/language/block_string.rb +34 -18
  95. data/lib/graphql/language/cache.rb +13 -0
  96. data/lib/graphql/language/comment.rb +18 -0
  97. data/lib/graphql/language/definition_slice.rb +1 -1
  98. data/lib/graphql/language/document_from_schema_definition.rb +90 -61
  99. data/lib/graphql/language/lexer.rb +319 -193
  100. data/lib/graphql/language/nodes.rb +139 -77
  101. data/lib/graphql/language/parser.rb +807 -1985
  102. data/lib/graphql/language/printer.rb +324 -151
  103. data/lib/graphql/language/sanitized_printer.rb +21 -23
  104. data/lib/graphql/language/static_visitor.rb +171 -0
  105. data/lib/graphql/language/visitor.rb +62 -119
  106. data/lib/graphql/language.rb +71 -1
  107. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  108. data/lib/graphql/pagination/array_connection.rb +6 -6
  109. data/lib/graphql/pagination/connection.rb +28 -1
  110. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  111. data/lib/graphql/query/context/scoped_context.rb +101 -0
  112. data/lib/graphql/query/context.rb +81 -144
  113. data/lib/graphql/query/null_context.rb +7 -16
  114. data/lib/graphql/query/partial.rb +179 -0
  115. data/lib/graphql/query/validation_pipeline.rb +4 -4
  116. data/lib/graphql/query/variable_validation_error.rb +1 -1
  117. data/lib/graphql/query/variables.rb +3 -3
  118. data/lib/graphql/query.rb +126 -81
  119. data/lib/graphql/railtie.rb +16 -6
  120. data/lib/graphql/rake_task.rb +3 -12
  121. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  122. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  123. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  124. data/lib/graphql/rubocop.rb +2 -0
  125. data/lib/graphql/schema/addition.rb +26 -13
  126. data/lib/graphql/schema/always_visible.rb +7 -2
  127. data/lib/graphql/schema/argument.rb +73 -14
  128. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  129. data/lib/graphql/schema/build_from_definition.rb +123 -66
  130. data/lib/graphql/schema/directive/flagged.rb +4 -2
  131. data/lib/graphql/schema/directive/one_of.rb +12 -0
  132. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  133. data/lib/graphql/schema/directive.rb +55 -3
  134. data/lib/graphql/schema/enum.rb +110 -27
  135. data/lib/graphql/schema/enum_value.rb +11 -3
  136. data/lib/graphql/schema/field/connection_extension.rb +2 -16
  137. data/lib/graphql/schema/field/scope_extension.rb +8 -1
  138. data/lib/graphql/schema/field.rb +173 -119
  139. data/lib/graphql/schema/field_extension.rb +1 -1
  140. data/lib/graphql/schema/has_single_input_argument.rb +160 -0
  141. data/lib/graphql/schema/input_object.rb +123 -65
  142. data/lib/graphql/schema/interface.rb +34 -16
  143. data/lib/graphql/schema/introspection_system.rb +8 -17
  144. data/lib/graphql/schema/late_bound_type.rb +4 -0
  145. data/lib/graphql/schema/list.rb +3 -3
  146. data/lib/graphql/schema/loader.rb +3 -4
  147. data/lib/graphql/schema/member/base_dsl_methods.rb +17 -1
  148. data/lib/graphql/schema/member/has_arguments.rb +132 -100
  149. data/lib/graphql/schema/member/has_dataloader.rb +62 -0
  150. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  151. data/lib/graphql/schema/member/has_directives.rb +5 -5
  152. data/lib/graphql/schema/member/has_fields.rb +111 -16
  153. data/lib/graphql/schema/member/has_interfaces.rb +27 -13
  154. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  155. data/lib/graphql/schema/member/has_validators.rb +1 -1
  156. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  157. data/lib/graphql/schema/member/scoped.rb +19 -0
  158. data/lib/graphql/schema/member/type_system_helpers.rb +18 -5
  159. data/lib/graphql/schema/member/validates_input.rb +3 -3
  160. data/lib/graphql/schema/member.rb +1 -0
  161. data/lib/graphql/schema/mutation.rb +7 -0
  162. data/lib/graphql/schema/object.rb +33 -8
  163. data/lib/graphql/schema/printer.rb +9 -7
  164. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  165. data/lib/graphql/schema/relay_classic_mutation.rb +6 -129
  166. data/lib/graphql/schema/resolver.rb +45 -31
  167. data/lib/graphql/schema/scalar.rb +4 -9
  168. data/lib/graphql/schema/subscription.rb +63 -10
  169. data/lib/graphql/schema/timeout.rb +19 -2
  170. data/lib/graphql/schema/type_expression.rb +2 -2
  171. data/lib/graphql/schema/union.rb +2 -2
  172. data/lib/graphql/schema/unique_within_type.rb +1 -1
  173. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  174. data/lib/graphql/schema/validator/required_validator.rb +92 -11
  175. data/lib/graphql/schema/validator.rb +3 -1
  176. data/lib/graphql/schema/visibility/migration.rb +188 -0
  177. data/lib/graphql/schema/visibility/profile.rb +445 -0
  178. data/lib/graphql/schema/visibility/visit.rb +190 -0
  179. data/lib/graphql/schema/visibility.rb +311 -0
  180. data/lib/graphql/schema/warden.rb +275 -103
  181. data/lib/graphql/schema.rb +924 -210
  182. data/lib/graphql/static_validation/all_rules.rb +3 -3
  183. data/lib/graphql/static_validation/base_visitor.rb +7 -6
  184. data/lib/graphql/static_validation/literal_validator.rb +6 -7
  185. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  186. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  187. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  188. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  189. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  190. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  191. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  192. data/lib/graphql/static_validation/rules/fields_will_merge.rb +88 -25
  193. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  194. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  195. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  196. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  197. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  198. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  199. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  200. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  201. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +5 -5
  202. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  203. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  204. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +7 -3
  205. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  206. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  207. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  208. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  209. data/lib/graphql/static_validation/validation_context.rb +21 -5
  210. data/lib/graphql/static_validation/validator.rb +9 -1
  211. data/lib/graphql/static_validation.rb +0 -1
  212. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -5
  213. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  214. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  215. data/lib/graphql/subscriptions/event.rb +21 -4
  216. data/lib/graphql/subscriptions/serialize.rb +3 -1
  217. data/lib/graphql/subscriptions.rb +21 -17
  218. data/lib/graphql/testing/helpers.rb +161 -0
  219. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  220. data/lib/graphql/testing.rb +3 -0
  221. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  222. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  223. data/lib/graphql/tracing/appoptics_trace.rb +11 -3
  224. data/lib/graphql/tracing/appoptics_tracing.rb +9 -2
  225. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  226. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  227. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  228. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  229. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  230. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  231. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  232. data/lib/graphql/tracing/detailed_trace.rb +141 -0
  233. data/lib/graphql/tracing/legacy_hooks_trace.rb +75 -0
  234. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  235. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  236. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  237. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  238. data/lib/graphql/tracing/notifications_trace.rb +184 -34
  239. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  240. data/lib/graphql/tracing/null_trace.rb +9 -0
  241. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  242. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  243. data/lib/graphql/tracing/perfetto_trace.rb +818 -0
  244. data/lib/graphql/tracing/platform_trace.rb +5 -0
  245. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  246. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +5 -1
  247. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  248. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  249. data/lib/graphql/tracing/scout_trace.rb +32 -55
  250. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  251. data/lib/graphql/tracing/sentry_trace.rb +82 -0
  252. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  253. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  254. data/lib/graphql/tracing/trace.rb +112 -1
  255. data/lib/graphql/tracing.rb +31 -28
  256. data/lib/graphql/type_kinds.rb +2 -1
  257. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  258. data/lib/graphql/types/relay/connection_behaviors.rb +43 -3
  259. data/lib/graphql/types/relay/edge_behaviors.rb +18 -1
  260. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  261. data/lib/graphql/types.rb +18 -10
  262. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  263. data/lib/graphql/version.rb +1 -1
  264. data/lib/graphql.rb +68 -54
  265. data/readme.md +12 -2
  266. metadata +220 -40
  267. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  268. data/lib/graphql/analysis/ast/field_usage.rb +0 -57
  269. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  270. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  271. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  272. data/lib/graphql/analysis/ast/query_depth.rb +0 -55
  273. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  274. data/lib/graphql/analysis/ast.rb +0 -81
  275. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  276. data/lib/graphql/backtrace/trace.rb +0 -96
  277. data/lib/graphql/backtrace/tracer.rb +0 -80
  278. data/lib/graphql/deprecation.rb +0 -9
  279. data/lib/graphql/filter.rb +0 -59
  280. data/lib/graphql/language/parser.y +0 -560
  281. data/lib/graphql/language/token.rb +0 -34
  282. data/lib/graphql/schema/base_64_bp.rb +0 -26
  283. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  284. data/lib/graphql/schema/null_mask.rb +0 -11
  285. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
  286. data/lib/graphql/static_validation/type_stack.rb +0 -216
  287. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -19,7 +19,15 @@ module GraphQL
19
19
  attr_reader :items
20
20
 
21
21
  # @return [GraphQL::Query::Context]
22
- attr_accessor :context
22
+ attr_reader :context
23
+
24
+ def context=(new_ctx)
25
+ @context = new_ctx
26
+ if @was_authorized_by_scope_items.nil?
27
+ @was_authorized_by_scope_items = detect_was_authorized_by_scope_items
28
+ end
29
+ @context
30
+ end
23
31
 
24
32
  # @return [Object] the object this collection belongs to
25
33
  attr_accessor :parent
@@ -83,6 +91,11 @@ module GraphQL
83
91
  else
84
92
  default_page_size
85
93
  end
94
+ @was_authorized_by_scope_items = detect_was_authorized_by_scope_items
95
+ end
96
+
97
+ def was_authorized_by_scope_items?
98
+ @was_authorized_by_scope_items
86
99
  end
87
100
 
88
101
  def max_page_size=(new_value)
@@ -208,6 +221,16 @@ module GraphQL
208
221
 
209
222
  private
210
223
 
224
+ def detect_was_authorized_by_scope_items
225
+ if @context &&
226
+ (current_runtime_state = Fiber[:__graphql_runtime_info]) &&
227
+ (query_runtime_state = current_runtime_state[@context.query])
228
+ query_runtime_state.was_authorized_by_scope_items
229
+ else
230
+ nil
231
+ end
232
+ end
233
+
211
234
  # @param argument [nil, Integer] `first` or `last`, as provided by the client
212
235
  # @param max_page_size [nil, Integer]
213
236
  # @return [nil, Integer] `nil` if the input was `nil`, otherwise a value between `0` and `max_page_size`
@@ -247,6 +270,10 @@ module GraphQL
247
270
  def cursor
248
271
  @cursor ||= @connection.cursor_for(@node)
249
272
  end
273
+
274
+ def was_authorized_by_scope_items?
275
+ @connection.was_authorized_by_scope_items?
276
+ end
250
277
  end
251
278
  end
252
279
  end
@@ -13,8 +13,7 @@ module GraphQL
13
13
  end
14
14
 
15
15
  def relation_count(relation)
16
- # Mongo's `.count` doesn't apply limit or skip, which we need. So we have to load _everything_!
17
- relation.to_a.count
16
+ relation.all.count(relation.options.slice(:limit, :skip))
18
17
  end
19
18
 
20
19
  def null_relation(relation)
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Query
4
+ class Context
5
+ class ScopedContext
6
+ def initialize(query_context)
7
+ @query_context = query_context
8
+ @scoped_contexts = nil
9
+ @all_keys = nil
10
+ end
11
+
12
+ def merged_context
13
+ if @scoped_contexts.nil?
14
+ GraphQL::EmptyObjects::EMPTY_HASH
15
+ else
16
+ merged_ctx = {}
17
+ each_present_path_ctx do |path_ctx|
18
+ merged_ctx = path_ctx.merge(merged_ctx)
19
+ end
20
+ merged_ctx
21
+ end
22
+ end
23
+
24
+ def merge!(hash, at: current_path)
25
+ @all_keys ||= Set.new
26
+ @all_keys.merge(hash.keys)
27
+ ctx = @scoped_contexts ||= {}
28
+ at.each do |path_part|
29
+ ctx = ctx[path_part] ||= { parent: ctx }
30
+ end
31
+ this_scoped_ctx = ctx[:scoped_context] ||= {}
32
+ this_scoped_ctx.merge!(hash)
33
+ end
34
+
35
+ def key?(key)
36
+ if @all_keys && @all_keys.include?(key)
37
+ each_present_path_ctx do |path_ctx|
38
+ if path_ctx.key?(key)
39
+ return true
40
+ end
41
+ end
42
+ end
43
+ false
44
+ end
45
+
46
+ def [](key)
47
+ each_present_path_ctx do |path_ctx|
48
+ if path_ctx.key?(key)
49
+ return path_ctx[key]
50
+ end
51
+ end
52
+ nil
53
+ end
54
+
55
+ def current_path
56
+ @query_context.current_path || GraphQL::EmptyObjects::EMPTY_ARRAY
57
+ end
58
+
59
+ def dig(key, *other_keys)
60
+ each_present_path_ctx do |path_ctx|
61
+ if path_ctx.key?(key)
62
+ found_value = path_ctx[key]
63
+ if !other_keys.empty?
64
+ return found_value.dig(*other_keys)
65
+ else
66
+ return found_value
67
+ end
68
+ end
69
+ end
70
+ nil
71
+ end
72
+
73
+ private
74
+
75
+ # Start at the current location,
76
+ # but look up the tree for previously-assigned scoped values
77
+ def each_present_path_ctx
78
+ ctx = @scoped_contexts
79
+ if ctx.nil?
80
+ # no-op
81
+ else
82
+ current_path.each do |path_part|
83
+ if ctx.key?(path_part)
84
+ ctx = ctx[path_part]
85
+ else
86
+ break
87
+ end
88
+ end
89
+
90
+ while ctx
91
+ if (scoped_ctx = ctx[:scoped_context])
92
+ yield(scoped_ctx)
93
+ end
94
+ ctx = ctx[:parent]
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -1,39 +1,10 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GraphQL
3
4
  class Query
4
5
  # Expose some query-specific info to field resolve functions.
5
6
  # It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`.
6
7
  class Context
7
- module SharedMethods
8
- # Return this value to tell the runtime
9
- # to exclude this field from the response altogether
10
- def skip
11
- GraphQL::Execution::SKIP
12
- end
13
-
14
- # Add error at query-level.
15
- # @param error [GraphQL::ExecutionError] an execution error
16
- # @return [void]
17
- def add_error(error)
18
- if !error.is_a?(ExecutionError)
19
- raise TypeError, "expected error to be a ExecutionError, but was #{error.class}"
20
- end
21
- errors << error
22
- nil
23
- end
24
-
25
- # @example Print the GraphQL backtrace during field resolution
26
- # puts ctx.backtrace
27
- #
28
- # @return [GraphQL::Backtrace] The backtrace for this point in query execution
29
- def backtrace
30
- GraphQL::Backtrace.new(self)
31
- end
32
-
33
- def execution_errors
34
- @execution_errors ||= ExecutionErrors.new(self)
35
- end
36
- end
37
8
 
38
9
  class ExecutionErrors
39
10
  def initialize(ctx)
@@ -57,7 +28,6 @@ module GraphQL
57
28
  alias :push :add
58
29
  end
59
30
 
60
- include SharedMethods
61
31
  extend Forwardable
62
32
 
63
33
  # @return [Array<GraphQL::ExecutionError>] errors returned during execution
@@ -69,125 +39,21 @@ module GraphQL
69
39
  # @return [GraphQL::Schema]
70
40
  attr_reader :schema
71
41
 
72
- # @return [Array<String, Integer>] The current position in the result
73
- attr_reader :path
74
-
75
42
  # Make a new context which delegates key lookup to `values`
76
43
  # @param query [GraphQL::Query] the query who owns this context
77
44
  # @param values [Hash] A hash of arbitrary values which will be accessible at query-time
78
- def initialize(query:, schema: query.schema, values:, object:)
45
+ def initialize(query:, schema: query.schema, values:)
79
46
  @query = query
80
47
  @schema = schema
81
48
  @provided_values = values || {}
82
- @object = object
83
49
  # Namespaced storage, where user-provided values are in `nil` namespace:
84
50
  @storage = Hash.new { |h, k| h[k] = {} }
85
51
  @storage[nil] = @provided_values
86
52
  @errors = []
87
- @path = []
88
- @value = nil
89
- @context = self # for SharedMethods TODO delete sharedmethods
90
53
  @scoped_context = ScopedContext.new(self)
91
54
  end
92
55
 
93
- class ScopedContext
94
- NO_PATH = GraphQL::EmptyObjects::EMPTY_ARRAY
95
- NO_CONTEXT = GraphQL::EmptyObjects::EMPTY_HASH
96
-
97
- def initialize(query_context)
98
- @query_context = query_context
99
- @scoped_contexts = nil
100
- @all_keys = nil
101
- end
102
-
103
- def merged_context
104
- if @scoped_contexts.nil?
105
- NO_CONTEXT
106
- else
107
- merged_ctx = {}
108
- each_present_path_ctx do |path_ctx|
109
- merged_ctx = path_ctx.merge(merged_ctx)
110
- end
111
- merged_ctx
112
- end
113
- end
114
-
115
- def merge!(hash)
116
- @all_keys ||= Set.new
117
- @all_keys.merge(hash.keys)
118
- ctx = @scoped_contexts ||= {}
119
- current_path.each do |path_part|
120
- ctx = ctx[path_part] ||= { parent: ctx }
121
- end
122
- this_scoped_ctx = ctx[:scoped_context] ||= {}
123
- this_scoped_ctx.merge!(hash)
124
- end
125
-
126
- def key?(key)
127
- if @all_keys && @all_keys.include?(key)
128
- each_present_path_ctx do |path_ctx|
129
- if path_ctx.key?(key)
130
- return true
131
- end
132
- end
133
- end
134
- false
135
- end
136
-
137
- def [](key)
138
- each_present_path_ctx do |path_ctx|
139
- if path_ctx.key?(key)
140
- return path_ctx[key]
141
- end
142
- end
143
- nil
144
- end
145
-
146
- def current_path
147
- @query_context.current_path || NO_PATH
148
- end
149
-
150
- def dig(key, *other_keys)
151
- each_present_path_ctx do |path_ctx|
152
- if path_ctx.key?(key)
153
- found_value = path_ctx[key]
154
- if other_keys.any?
155
- return found_value.dig(*other_keys)
156
- else
157
- return found_value
158
- end
159
- end
160
- end
161
- nil
162
- end
163
-
164
- private
165
-
166
- # Start at the current location,
167
- # but look up the tree for previously-assigned scoped values
168
- def each_present_path_ctx
169
- ctx = @scoped_contexts
170
- if ctx.nil?
171
- # no-op
172
- else
173
- current_path.each do |path_part|
174
- if ctx.key?(path_part)
175
- ctx = ctx[path_part]
176
- else
177
- break
178
- end
179
- end
180
-
181
- while ctx
182
- if (scoped_ctx = ctx[:scoped_context])
183
- yield(scoped_ctx)
184
- end
185
- ctx = ctx[:parent]
186
- end
187
- end
188
- end
189
- end
190
-
56
+ # Modify this hash to return extensions to client.
191
57
  # @return [Hash] A hash that will be added verbatim to the result hash, as `"extensions" => { ... }`
192
58
  def response_extensions
193
59
  namespace(:__query_result_extensions__)
@@ -210,9 +76,15 @@ module GraphQL
210
76
  @provided_values[key] = value
211
77
  end
212
78
 
213
- def_delegators :@query, :trace, :interpreter?
79
+ def_delegators :@query, :trace
80
+
81
+ def types
82
+ @types ||= @query.types
83
+ end
214
84
 
215
- RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path])
85
+ attr_writer :types
86
+
87
+ RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path]).freeze
216
88
  # @!method []=(key, value)
217
89
  # Reassign `key` to the hash passed to {Schema#execute} as `context:`
218
90
 
@@ -226,7 +98,7 @@ module GraphQL
226
98
  if key == :current_path
227
99
  current_path
228
100
  else
229
- (current_runtime_state = Thread.current[:__graphql_runtime_info]) &&
101
+ (current_runtime_state = Fiber[:__graphql_runtime_info]) &&
230
102
  (query_runtime_state = current_runtime_state[@query]) &&
231
103
  (query_runtime_state.public_send(key))
232
104
  end
@@ -236,8 +108,37 @@ module GraphQL
236
108
  end
237
109
  end
238
110
 
111
+ # Return this value to tell the runtime
112
+ # to exclude this field from the response altogether
113
+ def skip
114
+ GraphQL::Execution::SKIP
115
+ end
116
+
117
+ # Add error at query-level.
118
+ # @param error [GraphQL::ExecutionError] an execution error
119
+ # @return [void]
120
+ def add_error(error)
121
+ if !error.is_a?(ExecutionError)
122
+ raise TypeError, "expected error to be a ExecutionError, but was #{error.class}"
123
+ end
124
+ errors << error
125
+ nil
126
+ end
127
+
128
+ # @example Print the GraphQL backtrace during field resolution
129
+ # puts ctx.backtrace
130
+ #
131
+ # @return [GraphQL::Backtrace] The backtrace for this point in query execution
132
+ def backtrace
133
+ GraphQL::Backtrace.new(self)
134
+ end
135
+
136
+ def execution_errors
137
+ @execution_errors ||= ExecutionErrors.new(self)
138
+ end
139
+
239
140
  def current_path
240
- current_runtime_state = Thread.current[:__graphql_runtime_info]
141
+ current_runtime_state = Fiber[:__graphql_runtime_info]
241
142
  query_runtime_state = current_runtime_state && current_runtime_state[@query]
242
143
 
243
144
  path = query_runtime_state &&
@@ -262,7 +163,7 @@ module GraphQL
262
163
 
263
164
  def fetch(key, default = UNSPECIFIED_FETCH_DEFAULT)
264
165
  if RUNTIME_METADATA_KEYS.include?(key)
265
- (runtime = Thread.current[:__graphql_runtime_info]) &&
166
+ (runtime = Fiber[:__graphql_runtime_info]) &&
266
167
  (query_runtime_state = runtime[@query]) &&
267
168
  (query_runtime_state.public_send(key))
268
169
  elsif @scoped_context.key?(key)
@@ -280,7 +181,7 @@ module GraphQL
280
181
 
281
182
  def dig(key, *other_keys)
282
183
  if RUNTIME_METADATA_KEYS.include?(key)
283
- (current_runtime_state = Thread.current[:__graphql_runtime_info]) &&
184
+ (current_runtime_state = Fiber[:__graphql_runtime_info]) &&
284
185
  (query_runtime_state = current_runtime_state[@query]) &&
285
186
  (obj = query_runtime_state.public_send(key)) &&
286
187
  if other_keys.empty?
@@ -333,8 +234,12 @@ module GraphQL
333
234
  @storage.key?(ns)
334
235
  end
335
236
 
237
+ def logger
238
+ @query && @query.logger
239
+ end
240
+
336
241
  def inspect
337
- "#<Query::Context ...>"
242
+ "#<#{self.class} ...>"
338
243
  end
339
244
 
340
245
  def scoped_merge!(hash)
@@ -345,6 +250,38 @@ module GraphQL
345
250
  scoped_merge!(key => value)
346
251
  nil
347
252
  end
253
+
254
+ # Use this when you need to do a scoped set _inside_ a lazy-loaded (or batch-loaded)
255
+ # block of code.
256
+ #
257
+ # @example using scoped context inside a promise
258
+ # scoped_ctx = context.scoped
259
+ # SomeBatchLoader.load(...).then do |thing|
260
+ # # use a scoped_ctx which was created _before_ dataloading:
261
+ # scoped_ctx.set!(:thing, thing)
262
+ # end
263
+ # @return [Context::Scoped]
264
+ def scoped
265
+ Scoped.new(@scoped_context, current_path)
266
+ end
267
+
268
+ class Scoped
269
+ def initialize(scoped_context, path)
270
+ @path = path
271
+ @scoped_context = scoped_context
272
+ end
273
+
274
+ def merge!(hash)
275
+ @scoped_context.merge!(hash, at: @path)
276
+ end
277
+
278
+ def set!(key, value)
279
+ @scoped_context.merge!({ key => value }, at: @path)
280
+ nil
281
+ end
282
+ end
348
283
  end
349
284
  end
350
285
  end
286
+
287
+ require "graphql/query/context/scoped_context"
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
+ require "graphql/query/context"
2
3
  module GraphQL
3
4
  class Query
4
5
  # This object can be `ctx` in places where there is no query
5
- class NullContext
6
+ class NullContext < Context
7
+ include Singleton
8
+
6
9
  class NullQuery
7
10
  def after_lazy(value)
8
11
  yield(value)
@@ -15,27 +18,15 @@ module GraphQL
15
18
  extend Forwardable
16
19
 
17
20
  attr_reader :schema, :query, :warden, :dataloader
18
- def_delegators GraphQL::EmptyObjects::EMPTY_HASH, :[], :fetch, :dig, :key?
21
+ def_delegators GraphQL::EmptyObjects::EMPTY_HASH, :[], :fetch, :dig, :key?, :to_h
19
22
 
20
23
  def initialize
21
24
  @query = NullQuery.new
22
25
  @dataloader = GraphQL::Dataloader::NullDataloader.new
23
26
  @schema = NullSchema
24
27
  @warden = Schema::Warden::NullWarden.new(context: self, schema: @schema)
25
- end
26
-
27
- def interpreter?
28
- true
29
- end
30
-
31
- class << self
32
- extend Forwardable
33
-
34
- def instance
35
- @instance ||= self.new
36
- end
37
-
38
- def_delegators :instance, :query, :warden, :schema, :interpreter?, :dataloader, :[], :fetch, :dig, :key?
28
+ @types = @warden.visibility_profile
29
+ freeze
39
30
  end
40
31
  end
41
32
  end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Query
4
+ # This class is _like_ a {GraphQL::Query}, except it can run on an arbitrary path within a query string.
5
+ #
6
+ # It depends on a "parent" {Query}.
7
+ #
8
+ # During execution, it calls query-related tracing hooks but passes itself as `query:`.
9
+ #
10
+ # The {Partial} will use your {Schema.resolve_type} hook to find the right GraphQL type to use for
11
+ # `object` in some cases.
12
+ #
13
+ # @see Query#run_partials Run via {Query#run_partials}
14
+ class Partial
15
+ include Query::Runnable
16
+
17
+ # @param path [Array<String, Integer>] A path in `query.query_string` to start executing from
18
+ # @param object [Object] A starting object for execution
19
+ # @param query [GraphQL::Query] A full query instance that this partial is based on. Caches are shared.
20
+ # @param context [Hash] Extra context values to merge into `query.context`, if provided
21
+ # @param fragment_node [GraphQL::Language::Nodes::InlineFragment, GraphQL::Language::Nodes::FragmentDefinition]
22
+ def initialize(path: nil, object:, query:, context: nil, fragment_node: nil, type: nil)
23
+ @path = path
24
+ @object = object
25
+ @query = query
26
+ @schema = query.schema
27
+ context_vals = @query.context.to_h
28
+ if context
29
+ context_vals = context_vals.merge(context)
30
+ end
31
+ @context = GraphQL::Query::Context.new(query: self, schema: @query.schema, values: context_vals)
32
+ @multiplex = nil
33
+ @result_values = nil
34
+ @result = nil
35
+
36
+ if fragment_node
37
+ @ast_nodes = [fragment_node]
38
+ @root_type = type || raise(ArgumentError, "Pass `type:` when using `node:`")
39
+ # This is only used when `@leaf`
40
+ @field_definition = nil
41
+ elsif path.nil?
42
+ raise ArgumentError, "`path:` is required if `node:` is not given; add `path:`"
43
+ else
44
+ set_type_info_from_path
45
+ end
46
+
47
+ @leaf = @root_type.unwrap.kind.leaf?
48
+ end
49
+
50
+ def leaf?
51
+ @leaf
52
+ end
53
+
54
+ attr_reader :context, :query, :ast_nodes, :root_type, :object, :field_definition, :path, :schema
55
+
56
+ attr_accessor :multiplex, :result_values
57
+
58
+ class Result < GraphQL::Query::Result
59
+ def path
60
+ @query.path
61
+ end
62
+
63
+ # @return [GraphQL::Query::Partial]
64
+ def partial
65
+ @query
66
+ end
67
+ end
68
+
69
+ def result
70
+ @result ||= Result.new(query: self, values: result_values)
71
+ end
72
+
73
+ def current_trace
74
+ @query.current_trace
75
+ end
76
+
77
+ def types
78
+ @query.types
79
+ end
80
+
81
+ def resolve_type(...)
82
+ @query.resolve_type(...)
83
+ end
84
+
85
+ def variables
86
+ @query.variables
87
+ end
88
+
89
+ def fragments
90
+ @query.fragments
91
+ end
92
+
93
+ def valid?
94
+ @query.valid?
95
+ end
96
+
97
+ def analyzers
98
+ EmptyObjects::EMPTY_ARRAY
99
+ end
100
+
101
+ def analysis_errors=(_ignored)
102
+ # pass
103
+ end
104
+
105
+ def subscription?
106
+ @query.subscription?
107
+ end
108
+
109
+ def selected_operation
110
+ ast_nodes.first
111
+ end
112
+
113
+ def static_errors
114
+ @query.static_errors
115
+ end
116
+
117
+ def selected_operation_name
118
+ @query.selected_operation_name
119
+ end
120
+
121
+ private
122
+
123
+ def set_type_info_from_path
124
+ selections = [@query.selected_operation]
125
+ type = @query.root_type
126
+ parent_type = nil
127
+ field_defn = nil
128
+
129
+ @path.each do |name_in_doc|
130
+ if name_in_doc.is_a?(Integer)
131
+ if type.list?
132
+ type = type.unwrap
133
+ next
134
+ else
135
+ raise ArgumentError, "Received path with index `#{name_in_doc}`, but type wasn't a list. Type: #{type.to_type_signature}, path: #{@path}"
136
+ end
137
+ end
138
+
139
+ next_selections = []
140
+ selections.each do |selection|
141
+ selections_to_check = []
142
+ selections_to_check.concat(selection.selections)
143
+ while (sel = selections_to_check.shift)
144
+ case sel
145
+ when GraphQL::Language::Nodes::InlineFragment
146
+ selections_to_check.concat(sel.selections)
147
+ when GraphQL::Language::Nodes::FragmentSpread
148
+ fragment = @query.fragments[sel.name]
149
+ selections_to_check.concat(fragment.selections)
150
+ when GraphQL::Language::Nodes::Field
151
+ if sel.alias == name_in_doc || sel.name == name_in_doc
152
+ next_selections << sel
153
+ end
154
+ else
155
+ raise "Unexpected selection in partial path: #{sel.class}, #{sel.inspect}"
156
+ end
157
+ end
158
+ end
159
+
160
+ if next_selections.empty?
161
+ raise ArgumentError, "Path `#{@path.inspect}` is not present in this query. `#{name_in_doc.inspect}` was not found. Try a different path or rewrite the query to include it."
162
+ end
163
+ field_name = next_selections.first.name
164
+ field_defn = @schema.get_field(type, field_name, @query.context) || raise("Invariant: no field called #{field_name} on #{type.graphql_name}")
165
+ parent_type = type
166
+ type = field_defn.type
167
+ if type.non_null?
168
+ type = type.of_type
169
+ end
170
+ selections = next_selections
171
+ end
172
+
173
+ @ast_nodes = selections
174
+ @root_type = type
175
+ @field_definition = field_defn
176
+ end
177
+ end
178
+ end
179
+ end