graphql 2.0.13 → 2.3.10

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 (228) 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 +3 -0
  6. data/lib/generators/graphql/mutation_delete_generator.rb +1 -1
  7. data/lib/generators/graphql/mutation_update_generator.rb +1 -1
  8. data/lib/generators/graphql/relay.rb +18 -1
  9. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  10. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  11. data/lib/generators/graphql/templates/base_edge.erb +2 -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_object.erb +2 -0
  17. data/lib/generators/graphql/templates/base_resolver.erb +6 -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/graphql_controller.erb +2 -0
  21. data/lib/generators/graphql/templates/loader.erb +2 -0
  22. data/lib/generators/graphql/templates/mutation.erb +2 -0
  23. data/lib/generators/graphql/templates/node_type.erb +2 -0
  24. data/lib/generators/graphql/templates/query_type.erb +2 -0
  25. data/lib/generators/graphql/templates/schema.erb +8 -0
  26. data/lib/graphql/analysis/analyzer.rb +89 -0
  27. data/lib/graphql/analysis/field_usage.rb +82 -0
  28. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  29. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  30. data/lib/graphql/analysis/query_complexity.rb +183 -0
  31. data/lib/graphql/analysis/query_depth.rb +58 -0
  32. data/lib/graphql/analysis/visitor.rb +283 -0
  33. data/lib/graphql/analysis.rb +92 -1
  34. data/lib/graphql/backtrace/inspect_result.rb +0 -12
  35. data/lib/graphql/backtrace/table.rb +2 -2
  36. data/lib/graphql/backtrace/trace.rb +93 -0
  37. data/lib/graphql/backtrace/tracer.rb +1 -1
  38. data/lib/graphql/backtrace.rb +2 -1
  39. data/lib/graphql/coercion_error.rb +1 -9
  40. data/lib/graphql/dataloader/async_dataloader.rb +88 -0
  41. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  42. data/lib/graphql/dataloader/request.rb +5 -0
  43. data/lib/graphql/dataloader/source.rb +89 -45
  44. data/lib/graphql/dataloader.rb +115 -142
  45. data/lib/graphql/duration_encoding_error.rb +16 -0
  46. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  47. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  48. data/lib/graphql/execution/interpreter/arguments_cache.rb +33 -33
  49. data/lib/graphql/execution/interpreter/resolve.rb +19 -0
  50. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +175 -0
  51. data/lib/graphql/execution/interpreter/runtime.rb +331 -455
  52. data/lib/graphql/execution/interpreter.rb +125 -61
  53. data/lib/graphql/execution/lazy.rb +6 -12
  54. data/lib/graphql/execution/lookahead.rb +124 -46
  55. data/lib/graphql/execution/multiplex.rb +3 -117
  56. data/lib/graphql/execution.rb +0 -1
  57. data/lib/graphql/introspection/directive_type.rb +3 -3
  58. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  59. data/lib/graphql/introspection/entry_points.rb +11 -5
  60. data/lib/graphql/introspection/field_type.rb +2 -2
  61. data/lib/graphql/introspection/schema_type.rb +10 -13
  62. data/lib/graphql/introspection/type_type.rb +17 -10
  63. data/lib/graphql/introspection.rb +3 -2
  64. data/lib/graphql/language/block_string.rb +34 -18
  65. data/lib/graphql/language/definition_slice.rb +1 -1
  66. data/lib/graphql/language/document_from_schema_definition.rb +75 -59
  67. data/lib/graphql/language/lexer.rb +358 -1506
  68. data/lib/graphql/language/nodes.rb +166 -93
  69. data/lib/graphql/language/parser.rb +795 -1953
  70. data/lib/graphql/language/printer.rb +340 -160
  71. data/lib/graphql/language/sanitized_printer.rb +21 -23
  72. data/lib/graphql/language/static_visitor.rb +167 -0
  73. data/lib/graphql/language/visitor.rb +188 -141
  74. data/lib/graphql/language.rb +61 -1
  75. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  76. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  77. data/lib/graphql/pagination/array_connection.rb +6 -6
  78. data/lib/graphql/pagination/connection.rb +33 -6
  79. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  80. data/lib/graphql/query/context/scoped_context.rb +101 -0
  81. data/lib/graphql/query/context.rb +117 -112
  82. data/lib/graphql/query/null_context.rb +12 -25
  83. data/lib/graphql/query/validation_pipeline.rb +6 -5
  84. data/lib/graphql/query/variables.rb +3 -3
  85. data/lib/graphql/query.rb +86 -30
  86. data/lib/graphql/railtie.rb +9 -6
  87. data/lib/graphql/rake_task.rb +29 -11
  88. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  89. data/lib/graphql/schema/addition.rb +59 -23
  90. data/lib/graphql/schema/always_visible.rb +11 -0
  91. data/lib/graphql/schema/argument.rb +55 -26
  92. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  93. data/lib/graphql/schema/build_from_definition.rb +56 -32
  94. data/lib/graphql/schema/directive/one_of.rb +24 -0
  95. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  96. data/lib/graphql/schema/directive/transform.rb +1 -1
  97. data/lib/graphql/schema/directive.rb +15 -3
  98. data/lib/graphql/schema/enum.rb +35 -24
  99. data/lib/graphql/schema/enum_value.rb +2 -3
  100. data/lib/graphql/schema/field/connection_extension.rb +2 -16
  101. data/lib/graphql/schema/field/scope_extension.rb +8 -1
  102. data/lib/graphql/schema/field.rb +147 -107
  103. data/lib/graphql/schema/field_extension.rb +1 -4
  104. data/lib/graphql/schema/find_inherited_value.rb +2 -7
  105. data/lib/graphql/schema/has_single_input_argument.rb +158 -0
  106. data/lib/graphql/schema/input_object.rb +47 -11
  107. data/lib/graphql/schema/interface.rb +15 -21
  108. data/lib/graphql/schema/introspection_system.rb +7 -17
  109. data/lib/graphql/schema/late_bound_type.rb +10 -0
  110. data/lib/graphql/schema/list.rb +2 -2
  111. data/lib/graphql/schema/loader.rb +2 -3
  112. data/lib/graphql/schema/member/base_dsl_methods.rb +18 -14
  113. data/lib/graphql/schema/member/build_type.rb +11 -3
  114. data/lib/graphql/schema/member/has_arguments.rb +170 -130
  115. data/lib/graphql/schema/member/has_ast_node.rb +12 -0
  116. data/lib/graphql/schema/member/has_deprecation_reason.rb +3 -4
  117. data/lib/graphql/schema/member/has_directives.rb +81 -61
  118. data/lib/graphql/schema/member/has_fields.rb +100 -38
  119. data/lib/graphql/schema/member/has_interfaces.rb +65 -10
  120. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  121. data/lib/graphql/schema/member/has_validators.rb +32 -6
  122. data/lib/graphql/schema/member/relay_shortcuts.rb +19 -0
  123. data/lib/graphql/schema/member/scoped.rb +19 -0
  124. data/lib/graphql/schema/member/type_system_helpers.rb +16 -0
  125. data/lib/graphql/schema/member/validates_input.rb +3 -3
  126. data/lib/graphql/schema/mutation.rb +7 -0
  127. data/lib/graphql/schema/object.rb +16 -5
  128. data/lib/graphql/schema/printer.rb +11 -8
  129. data/lib/graphql/schema/relay_classic_mutation.rb +7 -129
  130. data/lib/graphql/schema/resolver/has_payload_type.rb +9 -9
  131. data/lib/graphql/schema/resolver.rb +47 -32
  132. data/lib/graphql/schema/scalar.rb +3 -3
  133. data/lib/graphql/schema/subscription.rb +11 -4
  134. data/lib/graphql/schema/subset.rb +397 -0
  135. data/lib/graphql/schema/timeout.rb +25 -29
  136. data/lib/graphql/schema/type_expression.rb +2 -2
  137. data/lib/graphql/schema/type_membership.rb +3 -0
  138. data/lib/graphql/schema/union.rb +11 -2
  139. data/lib/graphql/schema/unique_within_type.rb +1 -1
  140. data/lib/graphql/schema/validator/all_validator.rb +60 -0
  141. data/lib/graphql/schema/validator.rb +4 -2
  142. data/lib/graphql/schema/warden.rb +238 -93
  143. data/lib/graphql/schema.rb +498 -103
  144. data/lib/graphql/static_validation/all_rules.rb +2 -1
  145. data/lib/graphql/static_validation/base_visitor.rb +7 -6
  146. data/lib/graphql/static_validation/definition_dependencies.rb +7 -1
  147. data/lib/graphql/static_validation/literal_validator.rb +24 -7
  148. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  149. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  150. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -2
  151. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
  152. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -4
  153. data/lib/graphql/static_validation/rules/fields_will_merge.rb +10 -10
  154. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  155. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  156. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  157. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  158. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +66 -0
  159. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb +29 -0
  160. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  161. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
  162. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  163. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
  164. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  165. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  166. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  167. data/lib/graphql/static_validation/validation_context.rb +5 -5
  168. data/lib/graphql/static_validation/validator.rb +4 -1
  169. data/lib/graphql/static_validation.rb +0 -1
  170. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +11 -4
  171. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  172. data/lib/graphql/subscriptions/event.rb +11 -10
  173. data/lib/graphql/subscriptions/serialize.rb +2 -0
  174. data/lib/graphql/subscriptions.rb +20 -13
  175. data/lib/graphql/testing/helpers.rb +151 -0
  176. data/lib/graphql/testing.rb +2 -0
  177. data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
  178. data/lib/graphql/tracing/appoptics_trace.rb +251 -0
  179. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  180. data/lib/graphql/tracing/appsignal_trace.rb +77 -0
  181. data/lib/graphql/tracing/data_dog_trace.rb +183 -0
  182. data/lib/graphql/tracing/data_dog_tracing.rb +9 -21
  183. data/lib/graphql/{execution/instrumentation.rb → tracing/legacy_hooks_trace.rb} +10 -28
  184. data/lib/graphql/tracing/legacy_trace.rb +69 -0
  185. data/lib/graphql/tracing/new_relic_trace.rb +75 -0
  186. data/lib/graphql/tracing/notifications_trace.rb +45 -0
  187. data/lib/graphql/tracing/platform_trace.rb +118 -0
  188. data/lib/graphql/tracing/platform_tracing.rb +17 -3
  189. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +4 -2
  190. data/lib/graphql/tracing/prometheus_trace.rb +89 -0
  191. data/lib/graphql/tracing/prometheus_tracing.rb +3 -3
  192. data/lib/graphql/tracing/scout_trace.rb +72 -0
  193. data/lib/graphql/tracing/sentry_trace.rb +112 -0
  194. data/lib/graphql/tracing/statsd_trace.rb +56 -0
  195. data/lib/graphql/tracing/trace.rb +76 -0
  196. data/lib/graphql/tracing.rb +20 -40
  197. data/lib/graphql/type_kinds.rb +7 -4
  198. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  199. data/lib/graphql/types/relay/base_connection.rb +1 -1
  200. data/lib/graphql/types/relay/connection_behaviors.rb +68 -6
  201. data/lib/graphql/types/relay/edge_behaviors.rb +33 -5
  202. data/lib/graphql/types/relay/node_behaviors.rb +8 -2
  203. data/lib/graphql/types/relay/page_info_behaviors.rb +11 -2
  204. data/lib/graphql/types/relay.rb +0 -1
  205. data/lib/graphql/types/string.rb +1 -1
  206. data/lib/graphql/types.rb +1 -0
  207. data/lib/graphql/version.rb +1 -1
  208. data/lib/graphql.rb +27 -20
  209. data/readme.md +13 -3
  210. metadata +96 -47
  211. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  212. data/lib/graphql/analysis/ast/field_usage.rb +0 -57
  213. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  214. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  215. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  216. data/lib/graphql/analysis/ast/query_depth.rb +0 -55
  217. data/lib/graphql/analysis/ast/visitor.rb +0 -269
  218. data/lib/graphql/analysis/ast.rb +0 -81
  219. data/lib/graphql/deprecation.rb +0 -9
  220. data/lib/graphql/filter.rb +0 -53
  221. data/lib/graphql/language/lexer.rl +0 -280
  222. data/lib/graphql/language/parser.y +0 -554
  223. data/lib/graphql/language/token.rb +0 -34
  224. data/lib/graphql/schema/base_64_bp.rb +0 -26
  225. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  226. data/lib/graphql/static_validation/type_stack.rb +0 -216
  227. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
  228. data/lib/graphql/types/relay/default_relay.rb +0 -21
@@ -8,9 +8,10 @@ require "graphql/language/lexer"
8
8
  require "graphql/language/nodes"
9
9
  require "graphql/language/cache"
10
10
  require "graphql/language/parser"
11
- require "graphql/language/token"
11
+ require "graphql/language/static_visitor"
12
12
  require "graphql/language/visitor"
13
13
  require "graphql/language/definition_slice"
14
+ require "strscan"
14
15
 
15
16
  module GraphQL
16
17
  module Language
@@ -31,6 +32,65 @@ module GraphQL
31
32
  else
32
33
  JSON.generate(value, quirks_mode: true)
33
34
  end
35
+ rescue JSON::GeneratorError
36
+ if Float::INFINITY == value
37
+ "Infinity"
38
+ else
39
+ raise
40
+ end
41
+ end
42
+
43
+ # Returns a new string if any single-quoted newlines were escaped.
44
+ # Otherwise, returns `query_str` unchanged.
45
+ # @return [String]
46
+ def self.escape_single_quoted_newlines(query_str)
47
+ scanner = StringScanner.new(query_str)
48
+ inside_single_quoted_string = false
49
+ new_query_str = nil
50
+ while !scanner.eos?
51
+ if (match = scanner.scan(/(?:\\"|[^"\n\r]|""")+/m)) && new_query_str
52
+ new_query_str << match
53
+ elsif scanner.scan('"')
54
+ new_query_str && (new_query_str << '"')
55
+ inside_single_quoted_string = !inside_single_quoted_string
56
+ elsif scanner.scan("\n")
57
+ if inside_single_quoted_string
58
+ new_query_str ||= query_str[0, scanner.pos - 1]
59
+ new_query_str << '\\n'
60
+ else
61
+ new_query_str && (new_query_str << "\n")
62
+ end
63
+ elsif scanner.scan("\r")
64
+ if inside_single_quoted_string
65
+ new_query_str ||= query_str[0, scanner.pos - 1]
66
+ new_query_str << '\\r'
67
+ else
68
+ new_query_str && (new_query_str << "\r")
69
+ end
70
+ elsif scanner.eos?
71
+ break
72
+ else
73
+ raise ArgumentError, "Unmatchable string scanner segment: #{scanner.rest.inspect}"
74
+ end
75
+ end
76
+ new_query_str || query_str
77
+ end
78
+
79
+ INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP = %r{
80
+ (
81
+ ((?<num>#{Lexer::INT_REGEXP}(#{Lexer::FLOAT_EXP_REGEXP})?)(?<name>#{Lexer::IDENTIFIER_REGEXP})#{Lexer::IGNORE_REGEXP}:)
82
+ |
83
+ ((?<num>#{Lexer::INT_REGEXP}#{Lexer::FLOAT_DECIMAL_REGEXP}#{Lexer::FLOAT_EXP_REGEXP})(?<name>#{Lexer::IDENTIFIER_REGEXP})#{Lexer::IGNORE_REGEXP}:)
84
+ |
85
+ ((?<num>#{Lexer::INT_REGEXP}#{Lexer::FLOAT_DECIMAL_REGEXP})(?<name>#{Lexer::IDENTIFIER_REGEXP})#{Lexer::IGNORE_REGEXP}:)
86
+ )}x
87
+
88
+ def self.add_space_between_numbers_and_names(query_str)
89
+ if query_str.match?(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP)
90
+ query_str.gsub(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP, "\\k<num> \\k<name>:")
91
+ else
92
+ query_str
93
+ end
34
94
  end
35
95
  end
36
96
  end
@@ -12,10 +12,14 @@ module GraphQL
12
12
  attr_reader :id
13
13
  # @return [Object] The value found with this ID
14
14
  attr_reader :object
15
- def initialize(argument:, id:, object:)
15
+ # @return [GraphQL::Query::Context]
16
+ attr_reader :context
17
+
18
+ def initialize(argument:, id:, object:, context:)
16
19
  @id = id
17
20
  @argument = argument
18
21
  @object = object
22
+ @context = context
19
23
  super("No object found for `#{argument.graphql_name}: #{id.inspect}`")
20
24
  end
21
25
  end
@@ -7,14 +7,6 @@ module GraphQL
7
7
  class ActiveRecordRelationConnection < Pagination::RelationConnection
8
8
  private
9
9
 
10
- def relation_larger_than(relation, initial_offset, size)
11
- if already_loaded?(relation)
12
- (relation.size + initial_offset) > size
13
- else
14
- set_offset(sliced_nodes, initial_offset + size).exists?
15
- end
16
- end
17
-
18
10
  def relation_count(relation)
19
11
  int_or_hash = if already_loaded?(relation)
20
12
  relation.size
@@ -35,10 +35,10 @@ module GraphQL
35
35
  def load_nodes
36
36
  @nodes ||= begin
37
37
  sliced_nodes = if before && after
38
- end_idx = index_from_cursor(before)-1
38
+ end_idx = index_from_cursor(before) - 2
39
39
  end_idx < 0 ? [] : items[index_from_cursor(after)..end_idx] || []
40
40
  elsif before
41
- end_idx = index_from_cursor(before)-2
41
+ end_idx = index_from_cursor(before) - 2
42
42
  end_idx < 0 ? [] : items[0..end_idx] || []
43
43
  elsif after
44
44
  items[index_from_cursor(after)..-1] || []
@@ -56,12 +56,12 @@ module GraphQL
56
56
  false
57
57
  end
58
58
 
59
- @has_next_page = if first
60
- # There are more items after these items
61
- sliced_nodes.count > first
62
- elsif before
59
+ @has_next_page = if before
63
60
  # The original array is longer than the `before` index
64
61
  index_from_cursor(before) < items.length + 1
62
+ elsif first
63
+ # There are more items after these items
64
+ sliced_nodes.count > first
65
65
  else
66
66
  false
67
67
  end
@@ -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
@@ -58,7 +66,7 @@ module GraphQL
58
66
  # @param arguments [Hash] The arguments to the field that returned the collection wrapped by this connection
59
67
  # @param max_page_size [Integer, nil] A configured value to cap the result size. Applied as `first` if neither first or last are given and no `default_page_size` is set.
60
68
  # @param default_page_size [Integer, nil] A configured value to determine the result size when neither first or last are given.
61
- def initialize(items, parent: nil, field: nil, context: nil, first: nil, after: nil, max_page_size: :not_given, default_page_size: :not_given, last: nil, before: nil, edge_class: nil, arguments: nil)
69
+ def initialize(items, parent: nil, field: nil, context: nil, first: nil, after: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, last: nil, before: nil, edge_class: nil, arguments: nil)
62
70
  @items = items
63
71
  @parent = parent
64
72
  @context = context
@@ -71,18 +79,23 @@ module GraphQL
71
79
  @edge_class = edge_class || self.class::Edge
72
80
  # This is only true if the object was _initialized_ with an override
73
81
  # or if one is assigned later.
74
- @has_max_page_size_override = max_page_size != :not_given
75
- @max_page_size = if max_page_size == :not_given
82
+ @has_max_page_size_override = max_page_size != NOT_CONFIGURED
83
+ @max_page_size = if max_page_size == NOT_CONFIGURED
76
84
  nil
77
85
  else
78
86
  max_page_size
79
87
  end
80
- @has_default_page_size_override = default_page_size != :not_given
81
- @default_page_size = if default_page_size == :not_given
88
+ @has_default_page_size_override = default_page_size != NOT_CONFIGURED
89
+ @default_page_size = if default_page_size == NOT_CONFIGURED
82
90
  nil
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 = Thread.current[:__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.any?
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,11 @@
1
1
  # frozen_string_literal: true
2
+ require "graphql/query/context/scoped_context"
3
+
2
4
  module GraphQL
3
5
  class Query
4
6
  # Expose some query-specific info to field resolve functions.
5
7
  # It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`.
6
8
  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
9
 
38
10
  class ExecutionErrors
39
11
  def initialize(ctx)
@@ -57,7 +29,6 @@ module GraphQL
57
29
  alias :push :add
58
30
  end
59
31
 
60
- include SharedMethods
61
32
  extend Forwardable
62
33
 
63
34
  # @return [Array<GraphQL::ExecutionError>] errors returned during execution
@@ -75,11 +46,10 @@ module GraphQL
75
46
  # Make a new context which delegates key lookup to `values`
76
47
  # @param query [GraphQL::Query] the query who owns this context
77
48
  # @param values [Hash] A hash of arbitrary values which will be accessible at query-time
78
- def initialize(query:, schema: query.schema, values:, object:)
49
+ def initialize(query:, schema: query.schema, values:)
79
50
  @query = query
80
51
  @schema = schema
81
52
  @provided_values = values || {}
82
- @object = object
83
53
  # Namespaced storage, where user-provided values are in `nil` namespace:
84
54
  @storage = Hash.new { |h, k| h[k] = {} }
85
55
  @storage[nil] = @provided_values
@@ -90,81 +60,6 @@ module GraphQL
90
60
  @scoped_context = ScopedContext.new(self)
91
61
  end
92
62
 
93
- class ScopedContext
94
- def initialize(query_context)
95
- @query_context = query_context
96
- @path_contexts = {}
97
- @no_path = [].freeze
98
- end
99
-
100
- def merged_context
101
- merged_ctx = {}
102
- each_present_path_ctx do |path_ctx|
103
- merged_ctx = path_ctx.merge(merged_ctx)
104
- end
105
- merged_ctx
106
- end
107
-
108
- def merge!(hash)
109
- current_ctx = @path_contexts[current_path] ||= {}
110
- current_ctx.merge!(hash)
111
- end
112
-
113
- def current_path
114
- @query_context.namespace(:interpreter)[:current_path] || @no_path
115
- end
116
-
117
- def key?(key)
118
- each_present_path_ctx do |path_ctx|
119
- if path_ctx.key?(key)
120
- return true
121
- end
122
- end
123
- false
124
- end
125
-
126
- def [](key)
127
- each_present_path_ctx do |path_ctx|
128
- if path_ctx.key?(key)
129
- return path_ctx[key]
130
- end
131
- end
132
- nil
133
- end
134
-
135
- def dig(key, *other_keys)
136
- each_present_path_ctx do |path_ctx|
137
- if path_ctx.key?(key)
138
- found_value = path_ctx[key]
139
- if other_keys.any?
140
- return found_value.dig(*other_keys)
141
- else
142
- return found_value
143
- end
144
- end
145
- end
146
- nil
147
- end
148
-
149
- private
150
-
151
- # Start at the current location,
152
- # but look up the tree for previously-assigned scoped values
153
- def each_present_path_ctx
154
- search_path = current_path.dup
155
- if (current_path_ctx = @path_contexts[search_path])
156
- yield(current_path_ctx)
157
- end
158
-
159
- while search_path.size > 0
160
- search_path.pop # look one level higher
161
- if (search_path_ctx = @path_contexts[search_path])
162
- yield(search_path_ctx)
163
- end
164
- end
165
- end
166
- end
167
-
168
63
  # @return [Hash] A hash that will be added verbatim to the result hash, as `"extensions" => { ... }`
169
64
  def response_extensions
170
65
  namespace(:__query_result_extensions__)
@@ -189,6 +84,11 @@ module GraphQL
189
84
 
190
85
  def_delegators :@query, :trace, :interpreter?
191
86
 
87
+ def types
88
+ @query.types
89
+ end
90
+
91
+ RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path])
192
92
  # @!method []=(key, value)
193
93
  # Reassign `key` to the hash passed to {Schema#execute} as `context:`
194
94
 
@@ -196,11 +96,65 @@ module GraphQL
196
96
  def [](key)
197
97
  if @scoped_context.key?(key)
198
98
  @scoped_context[key]
199
- else
99
+ elsif @provided_values.key?(key)
200
100
  @provided_values[key]
101
+ elsif RUNTIME_METADATA_KEYS.include?(key)
102
+ if key == :current_path
103
+ current_path
104
+ else
105
+ (current_runtime_state = Thread.current[:__graphql_runtime_info]) &&
106
+ (query_runtime_state = current_runtime_state[@query]) &&
107
+ (query_runtime_state.public_send(key))
108
+ end
109
+ else
110
+ # not found
111
+ nil
201
112
  end
202
113
  end
203
114
 
115
+ # Return this value to tell the runtime
116
+ # to exclude this field from the response altogether
117
+ def skip
118
+ GraphQL::Execution::SKIP
119
+ end
120
+
121
+ # Add error at query-level.
122
+ # @param error [GraphQL::ExecutionError] an execution error
123
+ # @return [void]
124
+ def add_error(error)
125
+ if !error.is_a?(ExecutionError)
126
+ raise TypeError, "expected error to be a ExecutionError, but was #{error.class}"
127
+ end
128
+ errors << error
129
+ nil
130
+ end
131
+
132
+ # @example Print the GraphQL backtrace during field resolution
133
+ # puts ctx.backtrace
134
+ #
135
+ # @return [GraphQL::Backtrace] The backtrace for this point in query execution
136
+ def backtrace
137
+ GraphQL::Backtrace.new(self)
138
+ end
139
+
140
+ def execution_errors
141
+ @execution_errors ||= ExecutionErrors.new(self)
142
+ end
143
+
144
+ def current_path
145
+ current_runtime_state = Thread.current[:__graphql_runtime_info]
146
+ query_runtime_state = current_runtime_state && current_runtime_state[@query]
147
+
148
+ path = query_runtime_state &&
149
+ (result = query_runtime_state.current_result) &&
150
+ (result.path)
151
+ if path && (rn = query_runtime_state.current_result_name)
152
+ path = path.dup
153
+ path.push(rn)
154
+ end
155
+ path
156
+ end
157
+
204
158
  def delete(key)
205
159
  if @scoped_context.key?(key)
206
160
  @scoped_context.delete(key)
@@ -212,7 +166,11 @@ module GraphQL
212
166
  UNSPECIFIED_FETCH_DEFAULT = Object.new
213
167
 
214
168
  def fetch(key, default = UNSPECIFIED_FETCH_DEFAULT)
215
- if @scoped_context.key?(key)
169
+ if RUNTIME_METADATA_KEYS.include?(key)
170
+ (runtime = Thread.current[:__graphql_runtime_info]) &&
171
+ (query_runtime_state = runtime[@query]) &&
172
+ (query_runtime_state.public_send(key))
173
+ elsif @scoped_context.key?(key)
216
174
  scoped_context[key]
217
175
  elsif @provided_values.key?(key)
218
176
  @provided_values[key]
@@ -226,7 +184,16 @@ module GraphQL
226
184
  end
227
185
 
228
186
  def dig(key, *other_keys)
229
- if @scoped_context.key?(key)
187
+ if RUNTIME_METADATA_KEYS.include?(key)
188
+ (current_runtime_state = Thread.current[:__graphql_runtime_info]) &&
189
+ (query_runtime_state = current_runtime_state[@query]) &&
190
+ (obj = query_runtime_state.public_send(key)) &&
191
+ if other_keys.empty?
192
+ obj
193
+ else
194
+ obj.dig(*other_keys)
195
+ end
196
+ elsif @scoped_context.key?(key)
230
197
  @scoped_context.dig(key, *other_keys)
231
198
  else
232
199
  @provided_values.dig(key, *other_keys)
@@ -259,7 +226,11 @@ module GraphQL
259
226
  # @param ns [Object] a usage-specific namespace identifier
260
227
  # @return [Hash] namespaced storage
261
228
  def namespace(ns)
262
- @storage[ns]
229
+ if ns == :interpreter
230
+ self
231
+ else
232
+ @storage[ns]
233
+ end
263
234
  end
264
235
 
265
236
  # @return [Boolean] true if this namespace was accessed before
@@ -267,6 +238,10 @@ module GraphQL
267
238
  @storage.key?(ns)
268
239
  end
269
240
 
241
+ def logger
242
+ @query && @query.logger
243
+ end
244
+
270
245
  def inspect
271
246
  "#<Query::Context ...>"
272
247
  end
@@ -279,6 +254,36 @@ module GraphQL
279
254
  scoped_merge!(key => value)
280
255
  nil
281
256
  end
257
+
258
+ # Use this when you need to do a scoped set _inside_ a lazy-loaded (or batch-loaded)
259
+ # block of code.
260
+ #
261
+ # @example using scoped context inside a promise
262
+ # scoped_ctx = context.scoped
263
+ # SomeBatchLoader.load(...).then do |thing|
264
+ # # use a scoped_ctx which was created _before_ dataloading:
265
+ # scoped_ctx.set!(:thing, thing)
266
+ # end
267
+ # @return [Context::Scoped]
268
+ def scoped
269
+ Scoped.new(@scoped_context, current_path)
270
+ end
271
+
272
+ class Scoped
273
+ def initialize(scoped_context, path)
274
+ @path = path
275
+ @scoped_context = scoped_context
276
+ end
277
+
278
+ def merge!(hash)
279
+ @scoped_context.merge!(hash, at: @path)
280
+ end
281
+
282
+ def set!(key, value)
283
+ @scoped_context.merge!({ key => value }, at: @path)
284
+ nil
285
+ end
286
+ end
282
287
  end
283
288
  end
284
289
  end