graphql 1.10.1 → 1.13.0

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 (292) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +18 -2
  3. data/lib/generators/graphql/install_generator.rb +36 -6
  4. data/lib/generators/graphql/loader_generator.rb +1 -0
  5. data/lib/generators/graphql/mutation_generator.rb +2 -1
  6. data/lib/generators/graphql/object_generator.rb +54 -9
  7. data/lib/generators/graphql/relay.rb +63 -0
  8. data/lib/generators/graphql/relay_generator.rb +21 -0
  9. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  10. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  11. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  12. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  13. data/lib/generators/graphql/templates/base_field.erb +2 -0
  14. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  15. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  16. data/lib/generators/graphql/templates/base_mutation.erb +2 -0
  17. data/lib/generators/graphql/templates/base_object.erb +2 -0
  18. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  19. data/lib/generators/graphql/templates/base_union.erb +2 -0
  20. data/lib/generators/graphql/templates/enum.erb +2 -0
  21. data/lib/generators/graphql/templates/graphql_controller.erb +16 -12
  22. data/lib/generators/graphql/templates/interface.erb +2 -0
  23. data/lib/generators/graphql/templates/loader.erb +2 -0
  24. data/lib/generators/graphql/templates/mutation.erb +2 -0
  25. data/lib/generators/graphql/templates/mutation_type.erb +2 -0
  26. data/lib/generators/graphql/templates/node_type.erb +9 -0
  27. data/lib/generators/graphql/templates/object.erb +3 -1
  28. data/lib/generators/graphql/templates/query_type.erb +3 -3
  29. data/lib/generators/graphql/templates/scalar.erb +2 -0
  30. data/lib/generators/graphql/templates/schema.erb +21 -33
  31. data/lib/generators/graphql/templates/union.erb +3 -1
  32. data/lib/generators/graphql/type_generator.rb +1 -1
  33. data/lib/graphql/analysis/analyze_query.rb +7 -0
  34. data/lib/graphql/analysis/ast/field_usage.rb +24 -1
  35. data/lib/graphql/analysis/ast/query_complexity.rb +126 -109
  36. data/lib/graphql/analysis/ast/visitor.rb +13 -5
  37. data/lib/graphql/analysis/ast.rb +11 -2
  38. data/lib/graphql/argument.rb +3 -3
  39. data/lib/graphql/backtrace/inspect_result.rb +0 -1
  40. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  41. data/lib/graphql/backtrace/table.rb +34 -3
  42. data/lib/graphql/backtrace/traced_error.rb +0 -1
  43. data/lib/graphql/backtrace/tracer.rb +40 -9
  44. data/lib/graphql/backtrace.rb +28 -19
  45. data/lib/graphql/backwards_compatibility.rb +2 -1
  46. data/lib/graphql/base_type.rb +1 -1
  47. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +2 -2
  48. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  49. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  50. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  51. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  52. data/lib/graphql/dataloader/null_dataloader.rb +22 -0
  53. data/lib/graphql/dataloader/request.rb +19 -0
  54. data/lib/graphql/dataloader/request_all.rb +19 -0
  55. data/lib/graphql/dataloader/source.rb +155 -0
  56. data/lib/graphql/dataloader.rb +308 -0
  57. data/lib/graphql/define/assign_global_id_field.rb +2 -2
  58. data/lib/graphql/define/defined_object_proxy.rb +1 -1
  59. data/lib/graphql/define/instance_definable.rb +34 -4
  60. data/lib/graphql/define/type_definer.rb +5 -5
  61. data/lib/graphql/deprecated_dsl.rb +18 -5
  62. data/lib/graphql/deprecation.rb +9 -0
  63. data/lib/graphql/directive.rb +4 -4
  64. data/lib/graphql/enum_type.rb +7 -1
  65. data/lib/graphql/execution/errors.rb +110 -7
  66. data/lib/graphql/execution/execute.rb +8 -1
  67. data/lib/graphql/execution/instrumentation.rb +1 -1
  68. data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
  69. data/lib/graphql/execution/interpreter/arguments.rb +88 -0
  70. data/lib/graphql/execution/interpreter/arguments_cache.rb +103 -0
  71. data/lib/graphql/execution/interpreter/handles_raw_value.rb +18 -0
  72. data/lib/graphql/execution/interpreter/resolve.rb +37 -25
  73. data/lib/graphql/execution/interpreter/runtime.rb +685 -421
  74. data/lib/graphql/execution/interpreter.rb +42 -13
  75. data/lib/graphql/execution/lazy.rb +5 -1
  76. data/lib/graphql/execution/lookahead.rb +25 -110
  77. data/lib/graphql/execution/multiplex.rb +37 -25
  78. data/lib/graphql/field.rb +5 -1
  79. data/lib/graphql/function.rb +4 -0
  80. data/lib/graphql/input_object_type.rb +6 -0
  81. data/lib/graphql/integer_decoding_error.rb +17 -0
  82. data/lib/graphql/integer_encoding_error.rb +18 -2
  83. data/lib/graphql/interface_type.rb +7 -0
  84. data/lib/graphql/internal_representation/document.rb +2 -2
  85. data/lib/graphql/internal_representation/rewrite.rb +1 -1
  86. data/lib/graphql/internal_representation/scope.rb +2 -2
  87. data/lib/graphql/internal_representation/visit.rb +2 -2
  88. data/lib/graphql/introspection/directive_type.rb +8 -4
  89. data/lib/graphql/introspection/entry_points.rb +2 -2
  90. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  91. data/lib/graphql/introspection/field_type.rb +9 -5
  92. data/lib/graphql/introspection/input_value_type.rb +15 -3
  93. data/lib/graphql/introspection/introspection_query.rb +6 -92
  94. data/lib/graphql/introspection/schema_type.rb +4 -4
  95. data/lib/graphql/introspection/type_type.rb +16 -12
  96. data/lib/graphql/introspection.rb +96 -0
  97. data/lib/graphql/invalid_null_error.rb +18 -0
  98. data/lib/graphql/language/block_string.rb +20 -5
  99. data/lib/graphql/language/cache.rb +37 -0
  100. data/lib/graphql/language/document_from_schema_definition.rb +73 -25
  101. data/lib/graphql/language/lexer.rb +4 -3
  102. data/lib/graphql/language/lexer.rl +3 -3
  103. data/lib/graphql/language/nodes.rb +51 -89
  104. data/lib/graphql/language/parser.rb +552 -530
  105. data/lib/graphql/language/parser.y +114 -99
  106. data/lib/graphql/language/printer.rb +7 -2
  107. data/lib/graphql/language/sanitized_printer.rb +222 -0
  108. data/lib/graphql/language/token.rb +0 -4
  109. data/lib/graphql/language/visitor.rb +2 -2
  110. data/lib/graphql/language.rb +2 -0
  111. data/lib/graphql/name_validator.rb +2 -7
  112. data/lib/graphql/object_type.rb +44 -35
  113. data/lib/graphql/pagination/active_record_relation_connection.rb +14 -1
  114. data/lib/graphql/pagination/array_connection.rb +2 -2
  115. data/lib/graphql/pagination/connection.rb +75 -20
  116. data/lib/graphql/pagination/connections.rb +83 -31
  117. data/lib/graphql/pagination/relation_connection.rb +34 -14
  118. data/lib/graphql/parse_error.rb +0 -1
  119. data/lib/graphql/query/arguments.rb +4 -3
  120. data/lib/graphql/query/arguments_cache.rb +1 -2
  121. data/lib/graphql/query/context.rb +42 -7
  122. data/lib/graphql/query/executor.rb +0 -1
  123. data/lib/graphql/query/fingerprint.rb +26 -0
  124. data/lib/graphql/query/input_validation_result.rb +23 -6
  125. data/lib/graphql/query/literal_input.rb +1 -1
  126. data/lib/graphql/query/null_context.rb +24 -8
  127. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  128. data/lib/graphql/query/serial_execution.rb +1 -0
  129. data/lib/graphql/query/validation_pipeline.rb +5 -2
  130. data/lib/graphql/query/variable_validation_error.rb +1 -1
  131. data/lib/graphql/query/variables.rb +14 -4
  132. data/lib/graphql/query.rb +68 -13
  133. data/lib/graphql/railtie.rb +9 -1
  134. data/lib/graphql/rake_task.rb +12 -9
  135. data/lib/graphql/relay/array_connection.rb +10 -12
  136. data/lib/graphql/relay/base_connection.rb +26 -13
  137. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  138. data/lib/graphql/relay/connection_type.rb +1 -1
  139. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  140. data/lib/graphql/relay/mutation.rb +1 -0
  141. data/lib/graphql/relay/node.rb +3 -0
  142. data/lib/graphql/relay/range_add.rb +23 -9
  143. data/lib/graphql/relay/relation_connection.rb +8 -10
  144. data/lib/graphql/relay/type_extensions.rb +2 -0
  145. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  146. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  147. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  148. data/lib/graphql/rubocop.rb +4 -0
  149. data/lib/graphql/scalar_type.rb +16 -1
  150. data/lib/graphql/schema/addition.rb +247 -0
  151. data/lib/graphql/schema/argument.rb +210 -12
  152. data/lib/graphql/schema/base_64_encoder.rb +2 -0
  153. data/lib/graphql/schema/build_from_definition/resolve_map.rb +3 -1
  154. data/lib/graphql/schema/build_from_definition.rb +213 -86
  155. data/lib/graphql/schema/default_type_error.rb +2 -0
  156. data/lib/graphql/schema/directive/deprecated.rb +1 -1
  157. data/lib/graphql/schema/directive/feature.rb +1 -1
  158. data/lib/graphql/schema/directive/flagged.rb +57 -0
  159. data/lib/graphql/schema/directive/include.rb +1 -1
  160. data/lib/graphql/schema/directive/skip.rb +1 -1
  161. data/lib/graphql/schema/directive/transform.rb +14 -2
  162. data/lib/graphql/schema/directive.rb +78 -2
  163. data/lib/graphql/schema/enum.rb +80 -9
  164. data/lib/graphql/schema/enum_value.rb +17 -6
  165. data/lib/graphql/schema/field/connection_extension.rb +46 -30
  166. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  167. data/lib/graphql/schema/field.rb +285 -133
  168. data/lib/graphql/schema/find_inherited_value.rb +4 -1
  169. data/lib/graphql/schema/finder.rb +5 -5
  170. data/lib/graphql/schema/input_object.rb +97 -89
  171. data/lib/graphql/schema/interface.rb +24 -19
  172. data/lib/graphql/schema/late_bound_type.rb +2 -2
  173. data/lib/graphql/schema/list.rb +7 -1
  174. data/lib/graphql/schema/loader.rb +137 -103
  175. data/lib/graphql/schema/member/accepts_definition.rb +8 -1
  176. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -19
  177. data/lib/graphql/schema/member/build_type.rb +14 -7
  178. data/lib/graphql/schema/member/has_arguments.rb +205 -12
  179. data/lib/graphql/schema/member/has_ast_node.rb +4 -1
  180. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  181. data/lib/graphql/schema/member/has_directives.rb +98 -0
  182. data/lib/graphql/schema/member/has_fields.rb +95 -30
  183. data/lib/graphql/schema/member/has_interfaces.rb +90 -0
  184. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  185. data/lib/graphql/schema/member/has_validators.rb +31 -0
  186. data/lib/graphql/schema/member/instrumentation.rb +0 -1
  187. data/lib/graphql/schema/member/type_system_helpers.rb +3 -3
  188. data/lib/graphql/schema/member.rb +6 -0
  189. data/lib/graphql/schema/middleware_chain.rb +1 -1
  190. data/lib/graphql/schema/mutation.rb +4 -0
  191. data/lib/graphql/schema/non_null.rb +5 -0
  192. data/lib/graphql/schema/object.rb +47 -46
  193. data/lib/graphql/schema/possible_types.rb +9 -4
  194. data/lib/graphql/schema/printer.rb +16 -34
  195. data/lib/graphql/schema/relay_classic_mutation.rb +32 -4
  196. data/lib/graphql/schema/resolver/has_payload_type.rb +34 -4
  197. data/lib/graphql/schema/resolver.rb +123 -63
  198. data/lib/graphql/schema/scalar.rb +11 -1
  199. data/lib/graphql/schema/subscription.rb +57 -21
  200. data/lib/graphql/schema/timeout.rb +29 -15
  201. data/lib/graphql/schema/timeout_middleware.rb +3 -1
  202. data/lib/graphql/schema/type_expression.rb +1 -1
  203. data/lib/graphql/schema/type_membership.rb +18 -4
  204. data/lib/graphql/schema/union.rb +41 -1
  205. data/lib/graphql/schema/unique_within_type.rb +1 -2
  206. data/lib/graphql/schema/validation.rb +12 -2
  207. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  208. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  209. data/lib/graphql/schema/validator/exclusion_validator.rb +33 -0
  210. data/lib/graphql/schema/validator/format_validator.rb +48 -0
  211. data/lib/graphql/schema/validator/inclusion_validator.rb +35 -0
  212. data/lib/graphql/schema/validator/length_validator.rb +59 -0
  213. data/lib/graphql/schema/validator/numericality_validator.rb +82 -0
  214. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  215. data/lib/graphql/schema/validator.rb +174 -0
  216. data/lib/graphql/schema/warden.rb +153 -28
  217. data/lib/graphql/schema.rb +364 -330
  218. data/lib/graphql/static_validation/all_rules.rb +1 -0
  219. data/lib/graphql/static_validation/base_visitor.rb +8 -5
  220. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  221. data/lib/graphql/static_validation/error.rb +3 -1
  222. data/lib/graphql/static_validation/literal_validator.rb +51 -26
  223. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +44 -87
  224. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +22 -6
  225. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +28 -22
  226. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  227. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -1
  228. data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -43
  229. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  230. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  231. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  232. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  233. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  234. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +6 -7
  235. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +9 -10
  236. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +8 -8
  237. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +4 -2
  238. data/lib/graphql/static_validation/validation_context.rb +9 -3
  239. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  240. data/lib/graphql/static_validation/validator.rb +42 -8
  241. data/lib/graphql/static_validation.rb +1 -0
  242. data/lib/graphql/string_encoding_error.rb +13 -3
  243. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +118 -19
  244. data/lib/graphql/subscriptions/broadcast_analyzer.rb +81 -0
  245. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +21 -0
  246. data/lib/graphql/subscriptions/event.rb +81 -30
  247. data/lib/graphql/subscriptions/instrumentation.rb +0 -1
  248. data/lib/graphql/subscriptions/serialize.rb +33 -6
  249. data/lib/graphql/subscriptions/subscription_root.rb +15 -4
  250. data/lib/graphql/subscriptions.rb +88 -45
  251. data/lib/graphql/tracing/active_support_notifications_tracing.rb +2 -1
  252. data/lib/graphql/tracing/appoptics_tracing.rb +173 -0
  253. data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
  254. data/lib/graphql/tracing/new_relic_tracing.rb +1 -12
  255. data/lib/graphql/tracing/platform_tracing.rb +43 -17
  256. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  257. data/lib/graphql/tracing/scout_tracing.rb +11 -0
  258. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  259. data/lib/graphql/tracing/statsd_tracing.rb +42 -0
  260. data/lib/graphql/tracing.rb +9 -33
  261. data/lib/graphql/types/big_int.rb +5 -1
  262. data/lib/graphql/types/int.rb +10 -3
  263. data/lib/graphql/types/iso_8601_date.rb +3 -3
  264. data/lib/graphql/types/iso_8601_date_time.rb +25 -10
  265. data/lib/graphql/types/relay/base_connection.rb +6 -90
  266. data/lib/graphql/types/relay/base_edge.rb +2 -34
  267. data/lib/graphql/types/relay/connection_behaviors.rb +156 -0
  268. data/lib/graphql/types/relay/default_relay.rb +27 -0
  269. data/lib/graphql/types/relay/edge_behaviors.rb +53 -0
  270. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  271. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  272. data/lib/graphql/types/relay/node.rb +2 -4
  273. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  274. data/lib/graphql/types/relay/node_field.rb +2 -20
  275. data/lib/graphql/types/relay/nodes_field.rb +2 -20
  276. data/lib/graphql/types/relay/page_info.rb +2 -14
  277. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  278. data/lib/graphql/types/relay.rb +11 -3
  279. data/lib/graphql/types/string.rb +8 -2
  280. data/lib/graphql/unauthorized_error.rb +2 -2
  281. data/lib/graphql/union_type.rb +2 -0
  282. data/lib/graphql/upgrader/member.rb +1 -0
  283. data/lib/graphql/upgrader/schema.rb +1 -0
  284. data/lib/graphql/version.rb +1 -1
  285. data/lib/graphql.rb +65 -31
  286. data/readme.md +3 -6
  287. metadata +77 -112
  288. data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
  289. data/lib/graphql/literal_validation_error.rb +0 -6
  290. data/lib/graphql/types/relay/base_field.rb +0 -22
  291. data/lib/graphql/types/relay/base_interface.rb +0 -29
  292. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -24,10 +24,11 @@ module GraphQL
24
24
  # Really we only need description from here, but:
25
25
  extend Schema::Member::BaseDSLMethods
26
26
  extend GraphQL::Schema::Member::HasArguments
27
+ extend GraphQL::Schema::Member::HasValidators
27
28
  include Schema::Member::HasPath
28
29
  extend Schema::Member::HasPath
29
30
 
30
- # @param object [Object] the initialize object, pass to {Query.initialize} as `root_value`
31
+ # @param object [Object] The application object that this field is being resolved on
31
32
  # @param context [GraphQL::Query::Context]
32
33
  # @param field [GraphQL::Schema::Field]
33
34
  def initialize(object:, context:, field:)
@@ -36,10 +37,10 @@ module GraphQL
36
37
  @field = field
37
38
  # Since this hash is constantly rebuilt, cache it for this call
38
39
  @arguments_by_keyword = {}
39
- self.class.arguments.each do |name, arg|
40
+ self.class.arguments(context).each do |name, arg|
40
41
  @arguments_by_keyword[arg.keyword] = arg
41
42
  end
42
- @arguments_loads_as_type = self.class.arguments_loads_as_type
43
+ @prepared_arguments = nil
43
44
  end
44
45
 
45
46
  # @return [Object] The application object this field is being resolved on
@@ -48,9 +49,18 @@ module GraphQL
48
49
  # @return [GraphQL::Query::Context]
49
50
  attr_reader :context
50
51
 
52
+ # @return [GraphQL::Dataloader]
53
+ def dataloader
54
+ context.dataloader
55
+ end
56
+
51
57
  # @return [GraphQL::Schema::Field]
52
58
  attr_reader :field
53
59
 
60
+ def arguments
61
+ @prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)")
62
+ end
63
+
54
64
  # This method is _actually_ called by the runtime,
55
65
  # it does some preparation and then eventually calls
56
66
  # the user-defined `#resolve` method.
@@ -74,6 +84,8 @@ module GraphQL
74
84
  # for that argument, or may return a lazy object
75
85
  load_arguments_val = load_arguments(args)
76
86
  context.schema.after_lazy(load_arguments_val) do |loaded_args|
87
+ @prepared_arguments = loaded_args
88
+ Schema::Validator.validate!(self.class.validators, object, context, loaded_args, as: @field)
77
89
  # Then call `authorized?`, which may raise or may return a lazy object
78
90
  authorized_val = if loaded_args.any?
79
91
  authorized?(**loaded_args)
@@ -97,7 +109,7 @@ module GraphQL
97
109
  public_send(self.class.resolve_method)
98
110
  end
99
111
  else
100
- nil
112
+ raise GraphQL::UnauthorizedFieldError.new(context: context, object: object, type: field.owner, field: field)
101
113
  end
102
114
  end
103
115
  end
@@ -133,7 +145,7 @@ module GraphQL
133
145
  # @raise [GraphQL::UnauthorizedError] To signal an authorization failure
134
146
  # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
135
147
  def authorized?(**inputs)
136
- self.class.arguments.each_value do |argument|
148
+ self.class.arguments(context).each_value do |argument|
137
149
  arg_keyword = argument.keyword
138
150
  if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
139
151
  arg_auth, err = argument.authorized?(self, arg_value, context)
@@ -148,6 +160,16 @@ module GraphQL
148
160
  end
149
161
  end
150
162
 
163
+ # Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
164
+ #
165
+ # By default, the error is re-raised and passed along to {{Schema.unauthorized_object}}.
166
+ #
167
+ # Any value returned here will be used _instead of_ of the loaded object.
168
+ # @param err [GraphQL::UnauthorizedError]
169
+ def unauthorized_object(err)
170
+ raise err
171
+ end
172
+
151
173
  private
152
174
 
153
175
  def load_arguments(args)
@@ -157,18 +179,14 @@ module GraphQL
157
179
  args.each do |key, value|
158
180
  arg_defn = @arguments_by_keyword[key]
159
181
  if arg_defn
160
- if value.nil?
161
- prepared_args[key] = value
162
- else
163
- prepped_value = prepared_args[key] = load_argument(key, value)
164
- if context.schema.lazy?(prepped_value)
165
- prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
166
- prepared_args[key] = finished_prepped_value
167
- end
182
+ prepped_value = prepared_args[key] = arg_defn.load_and_authorize_value(self, value, context)
183
+ if context.schema.lazy?(prepped_value)
184
+ prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
185
+ prepared_args[key] = finished_prepped_value
168
186
  end
169
187
  end
170
188
  else
171
- # These are `extras: [...]`
189
+ # these are `extras:`
172
190
  prepared_args[key] = value
173
191
  end
174
192
  end
@@ -181,8 +199,8 @@ module GraphQL
181
199
  end
182
200
  end
183
201
 
184
- def load_argument(name, value)
185
- public_send("load_#{name}", value)
202
+ def get_argument(name, context = GraphQL::Query::NullContext)
203
+ self.class.get_argument(name, context)
186
204
  end
187
205
 
188
206
  class << self
@@ -205,8 +223,10 @@ module GraphQL
205
223
  own_extras + (superclass.respond_to?(:extras) ? superclass.extras : [])
206
224
  end
207
225
 
208
- # Specifies whether or not the field is nullable. Defaults to `true`
209
- # TODO unify with {#type}
226
+ # If `true` (default), then the return type for this resolver will be nullable.
227
+ # If `false`, then the return type is non-null.
228
+ #
229
+ # @see #type which sets the return type of this field and accepts a `null:` option
210
230
  # @param allow_null [Boolean] Whether or not the response can be null
211
231
  def null(allow_null = nil)
212
232
  if !allow_null.nil?
@@ -220,7 +240,7 @@ module GraphQL
220
240
  # or use it as a configuration method to assign a return type
221
241
  # instead of generating one.
222
242
  # TODO unify with {#null}
223
- # @param new_type [Class, nil] If a type definition class is provided, it will be used as the return type of the field
243
+ # @param new_type [Class, Array<Class>, nil] If a type definition class is provided, it will be used as the return type of the field
224
244
  # @param null [true, false] Whether or not the field may return `nil`
225
245
  # @return [Class] The type which this field returns.
226
246
  def type(new_type = nil, null: nil)
@@ -250,18 +270,78 @@ module GraphQL
250
270
  @complexity || (superclass.respond_to?(:complexity) ? superclass.complexity : 1)
251
271
  end
252
272
 
273
+ def broadcastable(new_broadcastable)
274
+ @broadcastable = new_broadcastable
275
+ end
276
+
277
+ # @return [Boolean, nil]
278
+ def broadcastable?
279
+ if defined?(@broadcastable)
280
+ @broadcastable
281
+ else
282
+ (superclass.respond_to?(:broadcastable?) ? superclass.broadcastable? : nil)
283
+ end
284
+ end
285
+
286
+ # Get or set the `max_page_size:` which will be configured for fields using this resolver
287
+ # (`nil` means "unlimited max page size".)
288
+ # @param max_page_size [Integer, nil] Set a new value
289
+ # @return [Integer, nil] The `max_page_size` assigned to fields that use this resolver
290
+ def max_page_size(new_max_page_size = :not_given)
291
+ if new_max_page_size != :not_given
292
+ @max_page_size = new_max_page_size
293
+ elsif defined?(@max_page_size)
294
+ @max_page_size
295
+ elsif superclass.respond_to?(:max_page_size)
296
+ superclass.max_page_size
297
+ else
298
+ nil
299
+ end
300
+ end
301
+
302
+ # @return [Boolean] `true` if this resolver or a superclass has an assigned `max_page_size`
303
+ def has_max_page_size?
304
+ defined?(@max_page_size) || (superclass.respond_to?(:has_max_page_size?) && superclass.has_max_page_size?)
305
+ end
306
+
253
307
  def field_options
254
- {
308
+
309
+ all_args = {}
310
+ all_argument_definitions.each do |arg|
311
+ if (prev_entry = all_args[arg.graphql_name])
312
+ if prev_entry.is_a?(Array)
313
+ prev_entry << arg
314
+ else
315
+ all_args[arg.graphql_name] = [prev_entry, arg]
316
+ end
317
+ else
318
+ all_args[arg.graphql_name] = arg
319
+ end
320
+ end
321
+
322
+ field_opts = {
255
323
  type: type_expr,
256
324
  description: description,
257
325
  extras: extras,
258
326
  resolver_method: :resolve_with_support,
259
327
  resolver_class: self,
260
- arguments: arguments,
328
+ arguments: all_args,
261
329
  null: null,
262
330
  complexity: complexity,
263
- extensions: extensions,
331
+ broadcastable: broadcastable?,
264
332
  }
333
+
334
+ # If there aren't any, then the returned array is `[].freeze`,
335
+ # but passing that along breaks some user code.
336
+ if (exts = extensions).any?
337
+ field_opts[:extensions] = exts
338
+ end
339
+
340
+ if has_max_page_size?
341
+ field_opts[:max_page_size] = max_page_size
342
+ end
343
+
344
+ field_opts
265
345
  end
266
346
 
267
347
  # A non-normalized type configuration, without `null` applied
@@ -273,63 +353,43 @@ module GraphQL
273
353
  # also add some preparation hook methods which will be used for this argument
274
354
  # @see {GraphQL::Schema::Argument#initialize} for the signature
275
355
  def argument(*args, **kwargs, &block)
276
- loads = kwargs[:loads]
277
356
  # Use `from_resolver: true` to short-circuit the InputObject's own `loads:` implementation
278
357
  # so that we can support `#load_{x}` methods below.
279
- arg_defn = super(*args, from_resolver: true, **kwargs)
280
- own_arguments_loads_as_type[arg_defn.keyword] = loads if loads
281
-
282
- if loads && arg_defn.type.list?
283
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
284
- def load_#{arg_defn.keyword}(values)
285
- argument = @arguments_by_keyword[:#{arg_defn.keyword}]
286
- lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
287
- context.schema.after_lazy(values) do |values2|
288
- GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, lookup_as_type, value) })
289
- end
290
- end
291
- RUBY
292
- elsif loads
293
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
294
- def load_#{arg_defn.keyword}(value)
295
- argument = @arguments_by_keyword[:#{arg_defn.keyword}]
296
- lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
297
- load_application_object(argument, lookup_as_type, value)
298
- end
299
- RUBY
300
- else
301
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
302
- def load_#{arg_defn.keyword}(value)
303
- value
304
- end
305
- RUBY
306
- end
307
-
308
- arg_defn
309
- end
310
-
311
- # @api private
312
- def arguments_loads_as_type
313
- inherited_lookups = superclass.respond_to?(:arguments_loads_as_type) ? superclass.arguments_loads_as_type : {}
314
- inherited_lookups.merge(own_arguments_loads_as_type)
358
+ super(*args, from_resolver: true, **kwargs)
315
359
  end
316
360
 
317
361
  # Registers new extension
318
362
  # @param extension [Class] Extension class
319
363
  # @param options [Hash] Optional extension options
320
364
  def extension(extension, **options)
321
- extensions << {extension => options}
365
+ @own_extensions ||= []
366
+ @own_extensions << {extension => options}
322
367
  end
323
368
 
324
369
  # @api private
325
370
  def extensions
326
- @extensions ||= []
371
+ own_exts = @own_extensions
372
+ # Jump through some hoops to avoid creating arrays when we don't actually need them
373
+ if superclass.respond_to?(:extensions)
374
+ s_exts = superclass.extensions
375
+ if own_exts
376
+ if s_exts.any?
377
+ own_exts + s_exts
378
+ else
379
+ own_exts
380
+ end
381
+ else
382
+ s_exts
383
+ end
384
+ else
385
+ own_exts || EMPTY_ARRAY
386
+ end
327
387
  end
328
388
 
329
389
  private
330
390
 
331
- def own_arguments_loads_as_type
332
- @own_arguments_loads_as_type ||= {}
391
+ def own_extensions
392
+ @own_extensions
333
393
  end
334
394
  end
335
395
  end
@@ -43,13 +43,23 @@ module GraphQL
43
43
 
44
44
  def validate_non_null_input(value, ctx)
45
45
  result = Query::InputValidationResult.new
46
- if coerce_input(value, ctx).nil?
46
+ coerced_result = begin
47
+ ctx.query.with_error_handling do
48
+ coerce_input(value, ctx)
49
+ end
50
+ rescue GraphQL::CoercionError => err
51
+ err
52
+ end
53
+
54
+ if coerced_result.nil?
47
55
  str_value = if value == Float::INFINITY
48
56
  ""
49
57
  else
50
58
  " #{GraphQL::Language.serialize(value)}"
51
59
  end
52
60
  result.add_problem("Could not coerce value#{str_value} to #{graphql_name}")
61
+ elsif coerced_result.is_a?(GraphQL::CoercionError)
62
+ result.add_problem(coerced_result.message, message: coerced_result.message, extensions: coerced_result.extensions)
53
63
  end
54
64
  result
55
65
  end
@@ -12,19 +12,9 @@ module GraphQL
12
12
  #
13
13
  # Also, `#unsubscribe` terminates the subscription.
14
14
  class Subscription < GraphQL::Schema::Resolver
15
- class EarlyTerminationError < StandardError
16
- end
17
-
18
- # Raised when `unsubscribe` is called; caught by `subscriptions.rb`
19
- class UnsubscribedError < EarlyTerminationError
20
- end
21
-
22
- # Raised when `no_update` is returned; caught by `subscriptions.rb`
23
- class NoUpdateError < EarlyTerminationError
24
- end
25
15
  extend GraphQL::Schema::Resolver::HasPayloadType
26
16
  extend GraphQL::Schema::Member::HasFields
27
-
17
+ NO_UPDATE = :no_update
28
18
  # The generated payload type is required; If there's no payload,
29
19
  # propagate null.
30
20
  null false
@@ -35,6 +25,22 @@ module GraphQL
35
25
  @mode = context.query.subscription_update? ? :update : :subscribe
36
26
  end
37
27
 
28
+ def resolve_with_support(**args)
29
+ result = nil
30
+ unsubscribed = true
31
+ catch :graphql_subscription_unsubscribed do
32
+ result = super
33
+ unsubscribed = false
34
+ end
35
+
36
+
37
+ if unsubscribed
38
+ context.skip
39
+ else
40
+ result
41
+ end
42
+ end
43
+
38
44
  # Implement the {Resolve} API
39
45
  def resolve(**args)
40
46
  # Dispatch based on `@mode`, which will raise a `NoMethodError` if we ever
@@ -52,11 +58,9 @@ module GraphQL
52
58
  end
53
59
  end
54
60
 
55
- # Default implementation returns the root object.
61
+ # The default implementation returns nothing on subscribe.
56
62
  # Override it to return an object or
57
- # `:no_response` to return nothing.
58
- #
59
- # The default is `:no_response`.
63
+ # `:no_response` to (explicitly) return nothing.
60
64
  def subscribe(args = {})
61
65
  :no_response
62
66
  end
@@ -64,15 +68,16 @@ module GraphQL
64
68
  # Wrap the user-provided `#update` hook
65
69
  def resolve_update(**args)
66
70
  ret_val = args.any? ? update(**args) : update
67
- if ret_val == :no_update
68
- raise NoUpdateError
71
+ if ret_val == NO_UPDATE
72
+ context.namespace(:subscriptions)[:no_update] = true
73
+ context.skip
69
74
  else
70
75
  ret_val
71
76
  end
72
77
  end
73
78
 
74
79
  # The default implementation returns the root object.
75
- # Override it to return `:no_update` if you want to
80
+ # Override it to return {NO_UPDATE} if you want to
76
81
  # skip updates sometimes. Or override it to return a different object.
77
82
  def update(args = {})
78
83
  object
@@ -90,17 +95,20 @@ module GraphQL
90
95
 
91
96
  # Call this to halt execution and remove this subscription from the system
92
97
  def unsubscribe
93
- raise UnsubscribedError
98
+ context.namespace(:subscriptions)[:unsubscribed] = true
99
+ throw :graphql_subscription_unsubscribed
94
100
  end
95
101
 
102
+ READING_SCOPE = ::Object.new
96
103
  # Call this method to provide a new subscription_scope; OR
97
104
  # call it without an argument to get the subscription_scope
98
105
  # @param new_scope [Symbol]
106
+ # @param optional [Boolean] If true, then don't require `scope:` to be provided to updates to this subscription.
99
107
  # @return [Symbol]
100
- READING_SCOPE = ::Object.new
101
- def self.subscription_scope(new_scope = READING_SCOPE)
108
+ def self.subscription_scope(new_scope = READING_SCOPE, optional: false)
102
109
  if new_scope != READING_SCOPE
103
110
  @subscription_scope = new_scope
111
+ @subscription_scope_optional = optional
104
112
  elsif defined?(@subscription_scope)
105
113
  @subscription_scope
106
114
  else
@@ -108,6 +116,34 @@ module GraphQL
108
116
  end
109
117
  end
110
118
 
119
+ def self.subscription_scope_optional?
120
+ if defined?(@subscription_scope_optional)
121
+ @subscription_scope_optional
122
+ else
123
+ find_inherited_value(:subscription_scope_optional, false)
124
+ end
125
+ end
126
+
127
+ # This is called during initial subscription to get a "name" for this subscription.
128
+ # Later, when `.trigger` is called, this will be called again to build another "name".
129
+ # Any subscribers with matching topic will begin the update flow.
130
+ #
131
+ # The default implementation creates a string using the field name, subscription scope, and argument keys and values.
132
+ # In that implementation, only `.trigger` calls with _exact matches_ result in updates to subscribers.
133
+ #
134
+ # To implement a filtered stream-type subscription flow, override this method to return a string with field name and subscription scope.
135
+ # Then, implement {#update} to compare its arguments to the current `object` and return {NO_UPDATE} when an
136
+ # update should be filtered out.
137
+ #
138
+ # @see {#update} for how to skip updates when an event comes with a matching topic.
139
+ # @param arguments [Hash<String => Object>] The arguments for this topic, in GraphQL-style (camelized strings)
140
+ # @param field [GraphQL::Schema::Field]
141
+ # @param scope [Object, nil] A value corresponding to `.trigger(... scope:)` (for updates) or the `subscription_scope` found in `context` (for initial subscriptions).
142
+ # @return [String] An identifier corresponding to a stream of updates
143
+ def self.topic_for(arguments:, field:, scope:)
144
+ Subscriptions::Serialize.dump_recursive([scope, field.graphql_name, arguments])
145
+ end
146
+
111
147
  # Overriding Resolver#field_options to include subscription_scope
112
148
  def self.field_options
113
149
  super.merge(
@@ -7,7 +7,7 @@ module GraphQL
7
7
  # to the `errors` key. Any already-resolved fields will be in the `data` key, so
8
8
  # you'll get a partial response.
9
9
  #
10
- # You can subclass `GraphQL::Schema::Timeout` and override the `handle_timeout` method
10
+ # You can subclass `GraphQL::Schema::Timeout` and override `max_seconds` and/or `handle_timeout`
11
11
  # to provide custom logic when a timeout error occurs.
12
12
  #
13
13
  # Note that this will stop a query _in between_ field resolutions, but
@@ -33,8 +33,6 @@ module GraphQL
33
33
  # end
34
34
  #
35
35
  class Timeout
36
- attr_reader :max_seconds
37
-
38
36
  def self.use(schema, **options)
39
37
  tracer = new(**options)
40
38
  schema.tracer(tracer)
@@ -48,32 +46,39 @@ module GraphQL
48
46
  def trace(key, data)
49
47
  case key
50
48
  when 'execute_multiplex'
51
- timeout_state = {
52
- timeout_at: Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) + max_seconds * 1000,
53
- timed_out: false
54
- }
55
-
56
49
  data.fetch(:multiplex).queries.each do |query|
50
+ timeout_duration_s = max_seconds(query)
51
+ timeout_state = if timeout_duration_s == false
52
+ # if the method returns `false`, don't apply a timeout
53
+ false
54
+ else
55
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
56
+ timeout_at = now + (max_seconds(query) * 1000)
57
+ {
58
+ timeout_at: timeout_at,
59
+ timed_out: false
60
+ }
61
+ end
57
62
  query.context.namespace(self.class)[:state] = timeout_state
58
63
  end
59
64
 
60
65
  yield
61
66
  when 'execute_field', 'execute_field_lazy'
62
- query = data[:context] ? data.fetch(:context).query : data.fetch(:query)
63
- timeout_state = query.context.namespace(self.class).fetch(:state)
64
- if Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
67
+ query_context = data[:context] || data[:query].context
68
+ timeout_state = query_context.namespace(self.class).fetch(:state)
69
+ # If the `:state` is `false`, then `max_seconds(query)` opted out of timeout for this query.
70
+ if timeout_state != false && Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
65
71
  error = if data[:context]
66
- context = data.fetch(:context)
67
- GraphQL::Schema::Timeout::TimeoutError.new(context.parent_type, context.field)
72
+ GraphQL::Schema::Timeout::TimeoutError.new(query_context.parent_type, query_context.field)
68
73
  else
69
74
  field = data.fetch(:field)
70
75
  GraphQL::Schema::Timeout::TimeoutError.new(field.owner, field)
71
76
  end
72
77
 
73
78
  # Only invoke the timeout callback for the first timeout
74
- unless timeout_state[:timed_out]
79
+ if !timeout_state[:timed_out]
75
80
  timeout_state[:timed_out] = true
76
- handle_timeout(error, query)
81
+ handle_timeout(error, query_context.query)
77
82
  end
78
83
 
79
84
  error
@@ -85,6 +90,15 @@ module GraphQL
85
90
  end
86
91
  end
87
92
 
93
+ # Called at the start of each query.
94
+ # The default implementation returns the `max_seconds:` value from installing this plugin.
95
+ #
96
+ # @param query [GraphQL::Query] The query that's about to run
97
+ # @return [Integer, false] The number of seconds after which to interrupt query execution and call {#handle_error}, or `false` to bypass the timeout.
98
+ def max_seconds(query)
99
+ @max_seconds
100
+ end
101
+
88
102
  # Invoked when a query times out.
89
103
  # @param error [GraphQL::Schema::Timeout::TimeoutError]
90
104
  # @param query [GraphQL::Error]
@@ -23,12 +23,14 @@ module GraphQL
23
23
  # Bugsnag.notify(timeout_error, {query_string: query_ctx.query.query_string})
24
24
  # end
25
25
  #
26
+ # @api deprecated
27
+ # @see Schema::Timeout
26
28
  class TimeoutMiddleware
27
29
  # @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields
28
30
  def initialize(max_seconds:, context_key: nil, &block)
29
31
  @max_seconds = max_seconds
30
32
  if context_key
31
- warn("TimeoutMiddleware's `context_key` is ignored, timeout data is now stored in isolated storage")
33
+ GraphQL::Deprecation.warn("TimeoutMiddleware's `context_key` is ignored, timeout data is now stored in isolated storage")
32
34
  end
33
35
  @error_handler = block
34
36
  end
@@ -11,7 +11,7 @@ module GraphQL
11
11
  def self.build_type(type_owner, ast_node)
12
12
  case ast_node
13
13
  when GraphQL::Language::Nodes::TypeName
14
- type_owner.get_type(ast_node.name)
14
+ type_owner.get_type(ast_node.name) # rubocop:disable Development/ContextIsPassedCop -- this is a `context` or `warden`, it's already query-aware
15
15
  when GraphQL::Language::Nodes::NonNullType
16
16
  ast_inner_type = ast_node.of_type
17
17
  inner_type = build_type(type_owner, ast_inner_type)
@@ -4,8 +4,6 @@ module GraphQL
4
4
  class Schema
5
5
  # This class joins an object type to an abstract type (interface or union) of which
6
6
  # it is a member.
7
- #
8
- # TODO: Not yet implemented for interfaces.
9
7
  class TypeMembership
10
8
  # @return [Class<GraphQL::Schema::Object>]
11
9
  attr_accessor :object_type
@@ -26,9 +24,25 @@ module GraphQL
26
24
  end
27
25
 
28
26
  # @return [Boolean] if false, {#object_type} will be treated as _not_ a member of {#abstract_type}
29
- def visible?(_ctx)
30
- true
27
+ def visible?(ctx)
28
+ warden = Warden.from_context(ctx)
29
+ (@object_type.respond_to?(:visible?) ? warden.visible_type?(@object_type, ctx) : true) &&
30
+ (@abstract_type.respond_to?(:visible?) ? warden.visible_type?(@abstract_type, ctx) : true)
31
31
  end
32
+
33
+ def graphql_name
34
+ "#{@object_type.graphql_name}.#{@abstract_type.kind.interface? ? "implements" : "belongsTo" }.#{@abstract_type.graphql_name}"
35
+ end
36
+
37
+ def path
38
+ graphql_name
39
+ end
40
+
41
+ def inspect
42
+ "#<#{self.class} #{@object_type.inspect} => #{@abstract_type.inspect}>"
43
+ end
44
+
45
+ alias :type_class :itself
32
46
  end
33
47
  end
34
48
  end