graphql 1.11.11 → 1.12.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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +5 -5
  3. data/lib/generators/graphql/relay_generator.rb +63 -0
  4. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  5. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  6. data/lib/generators/graphql/templates/node_type.erb +9 -0
  7. data/lib/generators/graphql/templates/object.erb +1 -1
  8. data/lib/generators/graphql/templates/query_type.erb +1 -3
  9. data/lib/generators/graphql/templates/schema.erb +8 -35
  10. data/lib/graphql/analysis/analyze_query.rb +7 -0
  11. data/lib/graphql/analysis/ast/visitor.rb +9 -1
  12. data/lib/graphql/analysis/ast.rb +11 -2
  13. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  14. data/lib/graphql/backtrace/table.rb +22 -2
  15. data/lib/graphql/backtrace/tracer.rb +40 -9
  16. data/lib/graphql/backtrace.rb +28 -19
  17. data/lib/graphql/backwards_compatibility.rb +1 -0
  18. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  19. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  20. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  21. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  22. data/lib/graphql/dataloader/null_dataloader.rb +21 -0
  23. data/lib/graphql/dataloader/request.rb +24 -0
  24. data/lib/graphql/dataloader/request_all.rb +22 -0
  25. data/lib/graphql/dataloader/source.rb +93 -0
  26. data/lib/graphql/dataloader.rb +197 -0
  27. data/lib/graphql/define/assign_global_id_field.rb +1 -1
  28. data/lib/graphql/define/instance_definable.rb +32 -2
  29. data/lib/graphql/define/type_definer.rb +5 -5
  30. data/lib/graphql/deprecated_dsl.rb +5 -0
  31. data/lib/graphql/enum_type.rb +2 -0
  32. data/lib/graphql/execution/errors.rb +4 -0
  33. data/lib/graphql/execution/execute.rb +7 -0
  34. data/lib/graphql/execution/interpreter/arguments.rb +51 -14
  35. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  36. data/lib/graphql/execution/interpreter/runtime.rb +210 -124
  37. data/lib/graphql/execution/interpreter.rb +10 -6
  38. data/lib/graphql/execution/multiplex.rb +20 -6
  39. data/lib/graphql/function.rb +4 -0
  40. data/lib/graphql/input_object_type.rb +2 -0
  41. data/lib/graphql/interface_type.rb +3 -1
  42. data/lib/graphql/language/document_from_schema_definition.rb +50 -23
  43. data/lib/graphql/language/nodes.rb +0 -5
  44. data/lib/graphql/language/visitor.rb +0 -1
  45. data/lib/graphql/object_type.rb +2 -0
  46. data/lib/graphql/pagination/connection.rb +5 -1
  47. data/lib/graphql/pagination/connections.rb +6 -16
  48. data/lib/graphql/query/context.rb +4 -0
  49. data/lib/graphql/query/serial_execution.rb +1 -0
  50. data/lib/graphql/query.rb +2 -0
  51. data/lib/graphql/relay/base_connection.rb +7 -0
  52. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  53. data/lib/graphql/relay/connection_type.rb +1 -1
  54. data/lib/graphql/relay/mutation.rb +1 -0
  55. data/lib/graphql/relay/node.rb +3 -0
  56. data/lib/graphql/relay/type_extensions.rb +2 -0
  57. data/lib/graphql/scalar_type.rb +2 -0
  58. data/lib/graphql/schema/argument.rb +30 -10
  59. data/lib/graphql/schema/build_from_definition.rb +145 -58
  60. data/lib/graphql/schema/directive/flagged.rb +57 -0
  61. data/lib/graphql/schema/directive.rb +76 -0
  62. data/lib/graphql/schema/enum.rb +3 -0
  63. data/lib/graphql/schema/enum_value.rb +13 -7
  64. data/lib/graphql/schema/field/connection_extension.rb +3 -2
  65. data/lib/graphql/schema/field.rb +28 -10
  66. data/lib/graphql/schema/input_object.rb +36 -28
  67. data/lib/graphql/schema/interface.rb +1 -0
  68. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  69. data/lib/graphql/schema/member/build_type.rb +3 -3
  70. data/lib/graphql/schema/member/has_arguments.rb +24 -6
  71. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  72. data/lib/graphql/schema/member/has_directives.rb +98 -0
  73. data/lib/graphql/schema/member/has_validators.rb +31 -0
  74. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  75. data/lib/graphql/schema/member.rb +4 -0
  76. data/lib/graphql/schema/object.rb +11 -0
  77. data/lib/graphql/schema/printer.rb +5 -4
  78. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  79. data/lib/graphql/schema/resolver.rb +7 -0
  80. data/lib/graphql/schema/subscription.rb +19 -1
  81. data/lib/graphql/schema/timeout_middleware.rb +2 -0
  82. data/lib/graphql/schema/validation.rb +2 -0
  83. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  84. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  85. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  86. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  87. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  88. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  89. data/lib/graphql/schema/validator.rb +163 -0
  90. data/lib/graphql/schema.rb +61 -23
  91. data/lib/graphql/static_validation/validator.rb +2 -0
  92. data/lib/graphql/subscriptions.rb +17 -20
  93. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  94. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  95. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  96. data/lib/graphql/tracing.rb +2 -2
  97. data/lib/graphql/types/relay/base_connection.rb +2 -92
  98. data/lib/graphql/types/relay/base_edge.rb +2 -35
  99. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  100. data/lib/graphql/types/relay/default_relay.rb +27 -0
  101. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  102. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  103. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  104. data/lib/graphql/types/relay/node.rb +2 -4
  105. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  106. data/lib/graphql/types/relay/node_field.rb +1 -19
  107. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  108. data/lib/graphql/types/relay/page_info.rb +2 -14
  109. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  110. data/lib/graphql/types/relay.rb +11 -3
  111. data/lib/graphql/union_type.rb +2 -0
  112. data/lib/graphql/upgrader/member.rb +1 -0
  113. data/lib/graphql/upgrader/schema.rb +1 -0
  114. data/lib/graphql/version.rb +1 -1
  115. data/lib/graphql.rb +38 -4
  116. metadata +34 -9
  117. data/lib/graphql/types/relay/base_field.rb +0 -22
  118. data/lib/graphql/types/relay/base_interface.rb +0 -29
  119. data/lib/graphql/types/relay/base_object.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d5cb52d52c7bff9e3ec317756e7f2d171fac202208ca3bfe4bce711b4f3216b
4
- data.tar.gz: 0d087f84cd41caaea9e8953b78b4bef69349209a8e62321bb604cc68f5a3765d
3
+ metadata.gz: 433f1170eb17ab192761efcd8c27cf9efe5f952ab47b501baca270e33d6007cd
4
+ data.tar.gz: c56045f746385ae37ebd0f82116aad10782e9265c13e0e85e39488ef3db02c74
5
5
  SHA512:
6
- metadata.gz: 68ae0b7fec852524c7527b3222a06c217dd03723120204baed3a6bc67b87e5e629010b2b393c9e4c2c0747e10655f17d1cc06196ac6864bf899569039d9d3b21
7
- data.tar.gz: 56ed8bd3fb0b5e3c27cee6180a91b9b8ece3364a7682a90e2a4e4604663e2744d30d46ac85988394b00f41c2341b4c761c09c83d21925e08e1223a91fdba333f
6
+ metadata.gz: 6d580c78b6b659602067fcfd3043eff7ef2b20da1d50c1ca2b234614d864e1a232cb65e7456cfdf85764523f30bd5232823ea19b4664208aef875cc68e2e5fc9
7
+ data.tar.gz: 172bc16fadfeee1f022646498dd2ce359bd885fb96ddbeeab50f7583d27811501e583194dd582a7cf47d63545c2fe00c4fa4625b6b247155f537ff4a6de60ac1
@@ -43,9 +43,6 @@ module Graphql
43
43
  # post "/graphql", to: "graphql#execute"
44
44
  # ```
45
45
  #
46
- # Accept a `--relay` option which adds
47
- # The root `node(id: ID!)` field.
48
- #
49
46
  # Accept a `--batch` option which adds `GraphQL::Batch` setup.
50
47
  #
51
48
  # Use `--no-graphiql` to skip `graphiql-rails` installation.
@@ -79,8 +76,8 @@ module Graphql
79
76
 
80
77
  class_option :relay,
81
78
  type: :boolean,
82
- default: false,
83
- desc: "Include GraphQL::Relay installation"
79
+ default: true,
80
+ desc: "Include installation of Relay conventions (nodes, connections, edges)"
84
81
 
85
82
  class_option :batch,
86
83
  type: :boolean,
@@ -164,7 +161,10 @@ if Rails.env.development?
164
161
  RUBY
165
162
  end
166
163
  end
164
+ end
167
165
 
166
+ if options[:relay]
167
+ generate("graphql:relay")
168
168
  end
169
169
 
170
170
  if gemfile_modified?
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+ require 'rails/generators/base'
3
+ require_relative 'core'
4
+
5
+ module Graphql
6
+ module Generators
7
+ class RelayGenerator < Rails::Generators::Base
8
+ include Core
9
+
10
+ desc "Add base types and fields for Relay-style nodes and connections"
11
+ source_root File.expand_path('../templates', __FILE__)
12
+
13
+ def install_relay
14
+ # Add Node, `node(id:)`, and `nodes(ids:)`
15
+ template("node_type.erb", "#{options[:directory]}/types/node_type.rb")
16
+ in_root do
17
+ fields = " # Add `node(id: ID!) and `nodes(ids: [ID!]!)`\n include GraphQL::Types::Relay::HasNodeField\n include GraphQL::Types::Relay::HasNodesField\n\n"
18
+ inject_into_file "#{options[:directory]}/types/query_type.rb", fields, after: /class .*QueryType\s*<\s*[^\s]+?\n/m, force: false
19
+ end
20
+
21
+ # Add connections and edges
22
+ template("base_connection.erb", "#{options[:directory]}/types/base_connection.rb")
23
+ template("base_edge.erb", "#{options[:directory]}/types/base_edge.rb")
24
+ connectionable_type_files = {
25
+ "#{options[:directory]}/types/base_object.rb" => /class .*BaseObject\s*<\s*[^\s]+?\n/m,
26
+ "#{options[:directory]}/types/base_union.rb" => /class .*BaseUnion\s*<\s*[^\s]+?\n/m,
27
+ "#{options[:directory]}/types/base_interface.rb" => /include GraphQL::Schema::Interface\n/m,
28
+ }
29
+ in_root do
30
+ connectionable_type_files.each do |type_class_file, sentinel|
31
+ inject_into_file type_class_file, " connection_type_class(Types::BaseConnection)\n", after: sentinel, force: false
32
+ inject_into_file type_class_file, " edge_type_class(Types::BaseEdge)\n", after: sentinel, force: false
33
+ end
34
+ end
35
+
36
+ # Add object ID hooks & connection plugin
37
+ schema_code = <<-RUBY
38
+
39
+ # Relay-style Object Identification:
40
+
41
+ # Return a string UUID for `object`
42
+ def self.id_from_object(object, type_definition, query_ctx)
43
+ # Here's a simple implementation which:
44
+ # - joins the type name & object.id
45
+ # - encodes it with base64:
46
+ # GraphQL::Schema::UniqueWithinType.encode(type_definition.name, object.id)
47
+ end
48
+
49
+ # Given a string UUID, find the object
50
+ def self.object_from_id(id, query_ctx)
51
+ # For example, to decode the UUIDs generated above:
52
+ # type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id)
53
+ #
54
+ # Then, based on `type_name` and `id`
55
+ # find an object in your application
56
+ # ...
57
+ end
58
+ RUBY
59
+ inject_into_file schema_file_path, schema_code, before: /^end\n/m, force: false
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,8 @@
1
+ <% module_namespacing_when_supported do -%>
2
+ module Types
3
+ class BaseConnection < Types::BaseObject
4
+ # add `nodes` and `pageInfo` fields, as well as `edge_type(...)` and `node_nullable(...)` overrides
5
+ include GraphQL::Types::Relay::ConnectionBehaviors
6
+ end
7
+ end
8
+ <% end -%>
@@ -0,0 +1,8 @@
1
+ <% module_namespacing_when_supported do -%>
2
+ module Types
3
+ class BaseEdge < Types::BaseObject
4
+ # add `node` and `cursor` fields, as well as `node_type(...)` override
5
+ include GraphQL::Types::Relay::EdgeBehaviors
6
+ end
7
+ end
8
+ <% end -%>
@@ -0,0 +1,9 @@
1
+ <% module_namespacing_when_supported do -%>
2
+ module Types
3
+ module NodeType
4
+ include Types::BaseInterface
5
+ # Add the `id` field
6
+ include GraphQL::Types::Relay::NodeBehaviors
7
+ end
8
+ end
9
+ <% end -%>
@@ -1,7 +1,7 @@
1
1
  <% module_namespacing_when_supported do -%>
2
2
  module Types
3
3
  class <%= type_ruby_name.split('::')[-1] %> < Types::BaseObject
4
- <% if options.node %> implements GraphQL::Relay::Node.interface
4
+ <% if options.node %> implements GraphQL::Types::Relay::Node
5
5
  <% end %><% normalized_fields.each do |f| %> <%= f.to_ruby %>
6
6
  <% end %> end
7
7
  end
@@ -10,8 +10,6 @@ module Types
10
10
  def test_field
11
11
  "Hello World!"
12
12
  end
13
- <% if options[:relay] %>
14
- field :node, field: GraphQL::Relay::Node.field
15
- <% end %> end
13
+ end
16
14
  end
17
15
  <% end -%>
@@ -1,42 +1,15 @@
1
1
  <% module_namespacing_when_supported do -%>
2
2
  class <%= schema_name %> < GraphQL::Schema
3
3
  query(Types::QueryType)
4
-
5
- # Opt in to the new runtime (default in future graphql-ruby versions)
6
- use GraphQL::Execution::Interpreter
7
- use GraphQL::Analysis::AST
8
-
9
- # Add built-in connections for pagination
10
- use GraphQL::Pagination::Connections
11
- <% if options[:relay] %>
12
- # Relay Object Identification:
13
-
14
- # Return a string UUID for `object`
15
- def self.id_from_object(object, type_definition, query_ctx)
16
- # Here's a simple implementation which:
17
- # - joins the type name & object.id
18
- # - encodes it with base64:
19
- # GraphQL::Schema::UniqueWithinType.encode(type_definition.name, object.id)
20
- end
21
-
22
- # Given a string UUID, find the object
23
- def self.object_from_id(id, query_ctx)
24
- # For example, to decode the UUIDs generated above:
25
- # type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id)
26
- #
27
- # Then, based on `type_name` and `id`
28
- # find an object in your application
29
- # ...
30
- end
31
-
32
- # Object Resolution
33
- def self.resolve_type(type, obj, ctx)
4
+ <% if options[:batch] %>
5
+ # GraphQL::Batch setup:
6
+ use GraphQL::Batch
7
+ <% end %>
8
+ # Union and Interface Resolution
9
+ def self.resolve_type(abstract_type, obj, ctx)
34
10
  # TODO: Implement this function
35
- # to return the correct type for `obj`
11
+ # to return the correct object type for `obj`
36
12
  raise(GraphQL::RequiredImplementationMissingError)
37
13
  end
38
- <% end %><% if options[:batch] %>
39
- # GraphQL::Batch setup:
40
- use GraphQL::Batch
41
- <% end %>end
14
+ end
42
15
  <% end -%>
@@ -3,6 +3,11 @@ module GraphQL
3
3
  module Analysis
4
4
  module_function
5
5
 
6
+ def use(schema_class)
7
+ schema = schema_class.is_a?(Class) ? schema_class : schema_class.target
8
+ schema.analysis_engine = self
9
+ end
10
+
6
11
  # @return [void]
7
12
  def analyze_multiplex(multiplex, analyzers)
8
13
  multiplex.trace("analyze_multiplex", { multiplex: multiplex }) do
@@ -38,6 +43,8 @@ module GraphQL
38
43
  # @param analyzers [Array<#call>] Objects that respond to `#call(memo, visit_type, irep_node)`
39
44
  # @return [Array<Any>] Results from those analyzers
40
45
  def analyze_query(query, analyzers, multiplex_states: [])
46
+ warn "Legacy analysis will be removed in GraphQL-Ruby 2.0, please upgrade to AST Analysis: https://graphql-ruby.org/queries/ast_analysis.html (schema: #{query.schema})"
47
+
41
48
  query.trace("analyze_query", { query: query }) do
42
49
  analyzers_to_run = analyzers.select do |analyzer|
43
50
  if analyzer.respond_to?(:analyze?)
@@ -19,6 +19,7 @@ module GraphQL
19
19
  @field_definitions = []
20
20
  @argument_definitions = []
21
21
  @directive_definitions = []
22
+ @rescued_errors = []
22
23
  @query = query
23
24
  @schema = query.schema
24
25
  @response_path = []
@@ -32,6 +33,9 @@ module GraphQL
32
33
  # @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
33
34
  attr_reader :object_types
34
35
 
36
+ # @return [Array<GraphQL::AnalysisError]
37
+ attr_reader :rescued_errors
38
+
35
39
  def visit
36
40
  return unless @document
37
41
  super
@@ -239,7 +243,11 @@ module GraphQL
239
243
 
240
244
  def call_analyzers(method, node, parent)
241
245
  @analyzers.each do |analyzer|
242
- analyzer.public_send(method, node, parent, self)
246
+ begin
247
+ analyzer.public_send(method, node, parent, self)
248
+ rescue AnalysisError => err
249
+ @rescued_errors << err
250
+ end
243
251
  end
244
252
  end
245
253
 
@@ -13,7 +13,12 @@ module GraphQL
13
13
  module_function
14
14
 
15
15
  def use(schema_class)
16
- schema_class.analysis_engine = GraphQL::Analysis::AST
16
+ if schema_class.analysis_engine == self
17
+ definition_line = caller(2, 1).first
18
+ warn("GraphQL::Analysis::AST is now the default; remove `use GraphQL::Analysis::AST` from the schema definition (#{definition_line})")
19
+ else
20
+ schema_class.analysis_engine = self
21
+ end
17
22
  end
18
23
 
19
24
  # Analyze a multiplex, and all queries within.
@@ -67,7 +72,11 @@ module GraphQL
67
72
 
68
73
  visitor.visit
69
74
 
70
- query_analyzers.map(&:result)
75
+ if visitor.rescued_errors.any?
76
+ visitor.rescued_errors
77
+ else
78
+ query_analyzers.map(&:result)
79
+ end
71
80
  else
72
81
  []
73
82
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Backtrace
4
+ module LegacyTracer
5
+ module_function
6
+
7
+ # Implement the {GraphQL::Tracing} API.
8
+ def trace(key, metadata)
9
+ case key
10
+ when "lex", "parse"
11
+ # No context here, don't have a query yet
12
+ nil
13
+ when "execute_multiplex", "analyze_multiplex"
14
+ # No query context yet
15
+ nil
16
+ when "validate", "analyze_query", "execute_query", "execute_query_lazy"
17
+ query = metadata[:query] || metadata[:queries].first
18
+ push_data = query
19
+ multiplex = query.multiplex
20
+ when "execute_field", "execute_field_lazy"
21
+ # The interpreter passes `query:`, legacy passes `context:`
22
+ context = metadata[:context] || ((q = metadata[:query]) && q.context)
23
+ push_data = context
24
+ multiplex = context.query.multiplex
25
+ else
26
+ # Custom key, no backtrace data for this
27
+ nil
28
+ end
29
+
30
+ if push_data
31
+ multiplex.context[:last_graphql_backtrace_context] = push_data
32
+ end
33
+
34
+ if key == "execute_multiplex"
35
+ begin
36
+ yield
37
+ rescue StandardError => err
38
+ # This is an unhandled error from execution,
39
+ # Re-raise it with a GraphQL trace.
40
+ potential_context = metadata[:multiplex].context[:last_graphql_backtrace_context]
41
+
42
+ if potential_context.is_a?(GraphQL::Query::Context) || potential_context.is_a?(GraphQL::Query::Context::FieldResolutionContext)
43
+ raise TracedError.new(err, potential_context)
44
+ else
45
+ raise
46
+ end
47
+ ensure
48
+ metadata[:multiplex].context.delete(:last_graphql_backtrace_context)
49
+ end
50
+ else
51
+ yield
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -79,6 +79,25 @@ module GraphQL
79
79
  # @return [Array] 5 items for a backtrace table (not `key`)
80
80
  def build_rows(context_entry, rows:, top: false)
81
81
  case context_entry
82
+ when Backtrace::Frame
83
+ field_alias = context_entry.ast_node.respond_to?(:alias) && context_entry.ast_node.alias
84
+ value = if top && @override_value
85
+ @override_value
86
+ else
87
+ @context.query.context.namespace(:interpreter)[:runtime].value_at(context_entry.path)
88
+ end
89
+ rows << [
90
+ "#{context_entry.ast_node ? context_entry.ast_node.position.join(":") : ""}",
91
+ "#{context_entry.field.path}#{field_alias ? " as #{field_alias}" : ""}",
92
+ "#{context_entry.object.object.inspect}",
93
+ context_entry.arguments.to_h.inspect,
94
+ Backtrace::InspectResult.inspect_result(value),
95
+ ]
96
+ if (parent = context_entry.parent_frame)
97
+ build_rows(parent, rows: rows)
98
+ else
99
+ rows
100
+ end
82
101
  when GraphQL::Query::Context::FieldResolutionContext
83
102
  ctx = context_entry
84
103
  field_name = "#{ctx.irep_node.owner_type.name}.#{ctx.field.name}"
@@ -112,15 +131,16 @@ module GraphQL
112
131
  if object.is_a?(GraphQL::Schema::Object)
113
132
  object = object.object
114
133
  end
134
+ value = context_entry.namespace(:interpreter)[:runtime].value_at([])
115
135
  rows << [
116
136
  "#{position}",
117
137
  "#{op_type}#{op_name ? " #{op_name}" : ""}",
118
138
  "#{object.inspect}",
119
139
  query.variables.to_h.inspect,
120
- Backtrace::InspectResult.inspect_result(query.context.value),
140
+ Backtrace::InspectResult.inspect_result(value),
121
141
  ]
122
142
  else
123
- raise "Unexpected get_rows subject #{context_entry.inspect}"
143
+ raise "Unexpected get_rows subject #{context_entry.class} (#{context_entry.inspect})"
124
144
  end
125
145
  end
126
146
  end
@@ -1,46 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  class Backtrace
4
+ # TODO this is not fiber-friendly
4
5
  module Tracer
5
6
  module_function
6
7
 
7
8
  # Implement the {GraphQL::Tracing} API.
8
9
  def trace(key, metadata)
9
- push_data = case key
10
+ case key
10
11
  when "lex", "parse"
11
12
  # No context here, don't have a query yet
12
13
  nil
13
14
  when "execute_multiplex", "analyze_multiplex"
14
- metadata[:multiplex].queries
15
+ # No query context yet
16
+ nil
15
17
  when "validate", "analyze_query", "execute_query", "execute_query_lazy"
16
- metadata[:query] || metadata[:queries]
18
+ query = metadata[:query] || metadata[:queries].first
19
+ push_key = []
20
+ push_data = query
21
+ multiplex = query.multiplex
17
22
  when "execute_field", "execute_field_lazy"
18
- # The interpreter passes `query:`, legacy passes `context:`
19
- metadata[:context] || ((q = metadata[:query]) && q.context)
23
+ query = metadata[:query] || raise(ArgumentError, "Add `legacy: true` to use GraphQL::Backtrace without the interpreter runtime.")
24
+ context = query.context
25
+ multiplex = query.multiplex
26
+ push_key = metadata[:path].reject { |i| i.is_a?(Integer) }
27
+ parent_frame = multiplex.context[:graphql_backtrace_contexts][push_key[0..-2]]
28
+ if parent_frame.nil?
29
+ p push_key
30
+ binding.pry
31
+ end
32
+ if parent_frame.is_a?(GraphQL::Query)
33
+ parent_frame = parent_frame.context
34
+ end
35
+
36
+ push_data = Frame.new(
37
+ query: query,
38
+ path: push_key,
39
+ ast_node: metadata[:ast_node],
40
+ field: metadata[:field],
41
+ object: metadata[:object],
42
+ arguments: metadata[:arguments],
43
+ parent_frame: parent_frame,
44
+ )
20
45
  else
21
46
  # Custom key, no backtrace data for this
22
47
  nil
23
48
  end
24
49
 
25
50
  if push_data
26
- Thread.current[:last_graphql_backtrace_context] = push_data
51
+ multiplex.context[:graphql_backtrace_contexts][push_key] = push_data
52
+ multiplex.context[:last_graphql_backtrace_context] = push_data
27
53
  end
28
54
 
29
55
  if key == "execute_multiplex"
56
+ multiplex_context = metadata[:multiplex].context
57
+ multiplex_context[:graphql_backtrace_contexts] = {}
30
58
  begin
31
59
  yield
32
60
  rescue StandardError => err
33
61
  # This is an unhandled error from execution,
34
62
  # Re-raise it with a GraphQL trace.
35
- potential_context = Thread.current[:last_graphql_backtrace_context]
63
+ potential_context = multiplex_context[:last_graphql_backtrace_context]
36
64
 
37
- if potential_context.is_a?(GraphQL::Query::Context) || potential_context.is_a?(GraphQL::Query::Context::FieldResolutionContext)
65
+ if potential_context.is_a?(GraphQL::Query::Context) ||
66
+ potential_context.is_a?(GraphQL::Query::Context::FieldResolutionContext) ||
67
+ potential_context.is_a?(Backtrace::Frame)
38
68
  raise TracedError.new(err, potential_context)
39
69
  else
40
70
  raise
41
71
  end
42
72
  ensure
43
- Thread.current[:last_graphql_backtrace_context] = nil
73
+ multiplex_context.delete(:graphql_backtrace_contexts)
74
+ multiplex_context.delete(:last_graphql_backtrace_context)
44
75
  end
45
76
  else
46
77
  yield
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/backtrace/inspect_result"
3
+ require "graphql/backtrace/legacy_tracer"
3
4
  require "graphql/backtrace/table"
4
5
  require "graphql/backtrace/traced_error"
5
6
  require "graphql/backtrace/tracer"
@@ -9,13 +10,12 @@ module GraphQL
9
10
  # {TracedError} provides a GraphQL backtrace with arguments and return values.
10
11
  # The underlying error is available as {TracedError#cause}.
11
12
  #
12
- # WARNING: {.enable} is not threadsafe because {GraphQL::Tracing.install} is not threadsafe.
13
- #
14
13
  # @example toggling backtrace annotation
15
- # # to enable:
16
- # GraphQL::Backtrace.enable
17
- # # later, to disable:
18
- # GraphQL::Backtrace.disable
14
+ # class MySchema < GraphQL::Schema
15
+ # if Rails.env.development? || Rails.env.test?
16
+ # use GraphQL::Backtrace
17
+ # end
18
+ # end
19
19
  #
20
20
  class Backtrace
21
21
  include Enumerable
@@ -23,19 +23,13 @@ module GraphQL
23
23
 
24
24
  def_delegators :to_a, :each, :[]
25
25
 
26
- def self.enable
27
- warn("GraphQL::Backtrace.enable is deprecated, add `use GraphQL::Backtrace` to your schema definition instead.")
28
- GraphQL::Tracing.install(Backtrace::Tracer)
29
- nil
30
- end
31
-
32
- def self.disable
33
- GraphQL::Tracing.uninstall(Backtrace::Tracer)
34
- nil
35
- end
36
-
37
- def self.use(schema_defn)
38
- schema_defn.tracer(self::Tracer)
26
+ def self.use(schema_defn, legacy: false)
27
+ tracer = if legacy
28
+ self::LegacyTracer
29
+ else
30
+ self::Tracer
31
+ end
32
+ schema_defn.tracer(tracer)
39
33
  end
40
34
 
41
35
  def initialize(context, value: nil)
@@ -51,5 +45,20 @@ module GraphQL
51
45
  def to_a
52
46
  @table.to_backtrace
53
47
  end
48
+
49
+ # Used for internal bookkeeping
50
+ # @api private
51
+ class Frame
52
+ attr_reader :path, :query, :ast_node, :object, :field, :arguments, :parent_frame
53
+ def initialize(path:, query:, ast_node:, object:, field:, arguments:, parent_frame:)
54
+ @path = path
55
+ @query = query
56
+ @ast_node = ast_node
57
+ @field = field
58
+ @object = object
59
+ @arguments = arguments
60
+ @parent_frame = parent_frame
61
+ end
62
+ end
54
63
  end
55
64
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  # Helpers for migrating in a backwards-compatible way
4
+ # Remove this in GraphQL-Ruby 2.0, when all users of it will be gone.
4
5
  # @api private
5
6
  module BackwardsCompatibility
6
7
  module_function
@@ -32,6 +32,7 @@ module GraphQL
32
32
  # @param execution_strategy [<#new, #execute>] An execution strategy class
33
33
  # @return [Class<Minitest::Test>] A test suite for this execution strategy
34
34
  def self.build_suite(execution_strategy)
35
+ warn "#{self} will be removed from GraphQL-Ruby 2.0. There is no replacement, please open an issue on GitHub if you need support."
35
36
  Class.new(Minitest::Test) do
36
37
  class << self
37
38
  attr_accessor :counter_schema, :specification_schema
@@ -7,6 +7,8 @@ module GraphQL
7
7
  # @param execution_strategy [<#new, #execute>] An execution strategy class
8
8
  # @return [Class<Minitest::Test>] A test suite for this execution strategy
9
9
  def self.build_suite(execution_strategy)
10
+ warn "#{self} will be removed from GraphQL-Ruby 2.0. There is no replacement, please open an issue on GitHub if you need support."
11
+
10
12
  Class.new(Minitest::Test) do
11
13
  class << self
12
14
  attr_accessor :lazy_schema
@@ -11,6 +11,8 @@ module GraphQL
11
11
  # @yieldreturn [GraphQL::Language::Nodes::Document]
12
12
  # @return [Class<Minitest::Test>] A test suite for this parse function
13
13
  def self.build_suite(&block)
14
+ warn "#{self} will be removed from GraphQL-Ruby 2.0. There is no replacement, please open an issue on GitHub if you need support."
15
+
14
16
  Class.new(Minitest::Test) do
15
17
  include QueryAssertions
16
18
  include ParseErrorSpecification
@@ -8,6 +8,8 @@ module GraphQL
8
8
  # @yieldreturn [GraphQL::Language::Nodes::Document]
9
9
  # @return [Class<Minitest::Test>] A test suite for this parse function
10
10
  def self.build_suite(&block)
11
+ warn "#{self} will be removed from GraphQL-Ruby 2.0. There is no replacement, please open an issue on GitHub if you need support."
12
+
11
13
  Class.new(Minitest::Test) do
12
14
  @@parse_fn = block
13
15
 
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Dataloader
5
+ # The default implementation of dataloading -- all no-ops.
6
+ #
7
+ # The Dataloader interface isn't public, but it enables
8
+ # simple internal code while adding the option to add Dataloader.
9
+ class NullDataloader < Dataloader
10
+ def enqueue
11
+ yield
12
+ end
13
+
14
+ # These are all no-ops because code was
15
+ # executed sychronously.
16
+ def run; end
17
+ def yield; end
18
+ def yielded?; false; end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Dataloader
4
+ # @see Source#request which returns an instance of this
5
+ class Request
6
+ def initialize(source, key)
7
+ @source = source
8
+ @key = key
9
+ end
10
+
11
+ # Call this method to cause the current Fiber to wait for the results of this request.
12
+ #
13
+ # @return [Object] the object loaded for `key`
14
+ def load
15
+ if @source.results.key?(@key)
16
+ @source.results[@key]
17
+ else
18
+ @source.sync
19
+ @source.results[@key]
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end