graphql 2.5.11 → 2.6.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.
Files changed (154) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/field_extractor.rb +17 -1
  4. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  5. data/lib/generators/graphql/templates/schema.erb +2 -1
  6. data/lib/graphql/analysis/query_complexity.rb +31 -15
  7. data/lib/graphql/analysis.rb +20 -13
  8. data/lib/graphql/backtrace/table.rb +10 -1
  9. data/lib/graphql/current.rb +7 -1
  10. data/lib/graphql/dashboard/application_controller.rb +45 -0
  11. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  12. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  13. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  14. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
  15. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
  16. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
  17. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
  18. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
  19. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
  20. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
  21. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
  22. data/lib/graphql/dashboard.rb +14 -73
  23. data/lib/graphql/dataloader/async_dataloader.rb +336 -60
  24. data/lib/graphql/dataloader/null_dataloader.rb +48 -10
  25. data/lib/graphql/dataloader/source.rb +32 -19
  26. data/lib/graphql/dataloader.rb +107 -39
  27. data/lib/graphql/date_encoding_error.rb +1 -1
  28. data/lib/graphql/execution/directive_checks.rb +2 -0
  29. data/lib/graphql/execution/field_resolve_step.rb +804 -0
  30. data/lib/graphql/execution/finalize.rb +229 -0
  31. data/lib/graphql/execution/input_values.rb +333 -0
  32. data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
  33. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  34. data/lib/graphql/execution/interpreter/resolve.rb +7 -13
  35. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
  36. data/lib/graphql/execution/interpreter/runtime.rb +60 -40
  37. data/lib/graphql/execution/interpreter.rb +17 -23
  38. data/lib/graphql/execution/lazy.rb +1 -1
  39. data/lib/graphql/execution/load_argument_step.rb +102 -0
  40. data/lib/graphql/execution/multiplex.rb +1 -1
  41. data/lib/graphql/execution/next.rb +98 -0
  42. data/lib/graphql/execution/prepare_object_step.rb +151 -0
  43. data/lib/graphql/execution/resolve_type_step.rb +27 -0
  44. data/lib/graphql/execution/runner.rb +457 -0
  45. data/lib/graphql/execution/selections_step.rb +95 -0
  46. data/lib/graphql/execution.rb +15 -4
  47. data/lib/graphql/execution_error.rb +17 -12
  48. data/lib/graphql/float_decoding_error.rb +13 -0
  49. data/lib/graphql/float_encoding_error.rb +28 -0
  50. data/lib/graphql/introspection/directive_type.rb +7 -3
  51. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  52. data/lib/graphql/introspection/entry_points.rb +11 -3
  53. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  54. data/lib/graphql/introspection/field_type.rb +13 -5
  55. data/lib/graphql/introspection/input_value_type.rb +21 -13
  56. data/lib/graphql/introspection/schema_type.rb +6 -2
  57. data/lib/graphql/introspection/type_type.rb +64 -28
  58. data/lib/graphql/invalid_null_error.rb +11 -5
  59. data/lib/graphql/language/block_string.rb +6 -11
  60. data/lib/graphql/language/cache.rb +84 -14
  61. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  62. data/lib/graphql/language/lexer.rb +27 -17
  63. data/lib/graphql/language/nodes.rb +2 -1
  64. data/lib/graphql/language/parser.rb +1 -1
  65. data/lib/graphql/language.rb +32 -17
  66. data/lib/graphql/pagination/connection.rb +2 -0
  67. data/lib/graphql/pagination/connections.rb +30 -0
  68. data/lib/graphql/pagination/relation_connection.rb +10 -1
  69. data/lib/graphql/query/context.rb +10 -3
  70. data/lib/graphql/query/null_context.rb +9 -3
  71. data/lib/graphql/query/partial.rb +18 -3
  72. data/lib/graphql/query/variable_validation_error.rb +16 -1
  73. data/lib/graphql/query/variables.rb +1 -1
  74. data/lib/graphql/query.rb +12 -3
  75. data/lib/graphql/railtie.rb +2 -1
  76. data/lib/graphql/runtime_error.rb +6 -0
  77. data/lib/graphql/schema/argument.rb +13 -1
  78. data/lib/graphql/schema/build_from_definition.rb +28 -1
  79. data/lib/graphql/schema/directive/feature.rb +4 -0
  80. data/lib/graphql/schema/directive/transform.rb +20 -0
  81. data/lib/graphql/schema/directive.rb +48 -13
  82. data/lib/graphql/schema/field/connection_extension.rb +4 -37
  83. data/lib/graphql/schema/field/scope_extension.rb +18 -13
  84. data/lib/graphql/schema/field.rb +79 -48
  85. data/lib/graphql/schema/field_extension.rb +11 -8
  86. data/lib/graphql/schema/has_single_input_argument.rb +24 -13
  87. data/lib/graphql/schema/input_object.rb +6 -2
  88. data/lib/graphql/schema/interface.rb +26 -0
  89. data/lib/graphql/schema/introspection_system.rb +6 -21
  90. data/lib/graphql/schema/list.rb +5 -1
  91. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
  92. data/lib/graphql/schema/member/has_arguments.rb +43 -14
  93. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  94. data/lib/graphql/schema/member/has_dataloader.rb +37 -0
  95. data/lib/graphql/schema/member/has_fields.rb +86 -5
  96. data/lib/graphql/schema/member.rb +5 -0
  97. data/lib/graphql/schema/non_null.rb +1 -1
  98. data/lib/graphql/schema/object.rb +1 -0
  99. data/lib/graphql/schema/printer.rb +1 -1
  100. data/lib/graphql/schema/ractor_shareable.rb +1 -0
  101. data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
  102. data/lib/graphql/schema/resolver.rb +76 -1
  103. data/lib/graphql/schema/subscription.rb +53 -10
  104. data/lib/graphql/schema/timeout.rb +2 -2
  105. data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
  106. data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
  107. data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
  108. data/lib/graphql/schema/validator/format_validator.rb +3 -3
  109. data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
  110. data/lib/graphql/schema/validator/length_validator.rb +6 -6
  111. data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
  112. data/lib/graphql/schema/validator/required_validator.rb +38 -5
  113. data/lib/graphql/schema/validator.rb +9 -0
  114. data/lib/graphql/schema/visibility/profile.rb +74 -53
  115. data/lib/graphql/schema/visibility/visit.rb +1 -1
  116. data/lib/graphql/schema/visibility.rb +32 -24
  117. data/lib/graphql/schema/wrapper.rb +11 -1
  118. data/lib/graphql/schema.rb +93 -27
  119. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  120. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  121. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  122. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  123. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  124. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  125. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
  126. data/lib/graphql/static_validation/rules/fields_will_merge.rb +351 -248
  127. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  128. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  129. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  130. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  131. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  132. data/lib/graphql/static_validation/validation_context.rb +1 -1
  133. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  134. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +40 -10
  135. data/lib/graphql/subscriptions/serialize.rb +10 -4
  136. data/lib/graphql/subscriptions.rb +36 -1
  137. data/lib/graphql/testing/helpers.rb +12 -9
  138. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  139. data/lib/graphql/testing.rb +1 -0
  140. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  141. data/lib/graphql/tracing/detailed_trace.rb +70 -7
  142. data/lib/graphql/tracing/perfetto_trace.rb +213 -81
  143. data/lib/graphql/tracing/sentry_trace.rb +3 -1
  144. data/lib/graphql/tracing/trace.rb +6 -0
  145. data/lib/graphql/types/float.rb +18 -4
  146. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  147. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  148. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  149. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  150. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  151. data/lib/graphql/unauthorized_error.rb +9 -1
  152. data/lib/graphql/version.rb +1 -1
  153. data/lib/graphql.rb +8 -2
  154. metadata +23 -59
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ class SelectionsStep
5
+ def initialize(parent_type:, field_resolve_step:, selections:, objects:, results:, runner:, query:, path:, clobber: true)
6
+ @path = path
7
+ @field_resolve_step = field_resolve_step
8
+ @parent_type = parent_type
9
+ @selections = selections
10
+ @runner = runner
11
+ @objects = objects
12
+ @results = results
13
+ @query = query
14
+ @graphql_objects = nil
15
+ @all_selections = nil
16
+ @killed = false
17
+ @clobber = clobber
18
+ end
19
+
20
+ attr_reader :path, :query, :objects, :results, :field_resolve_step
21
+
22
+ attr_accessor :killed
23
+
24
+ def graphql_objects
25
+ @graphql_objects ||= @objects.map do |obj|
26
+ @parent_type.scoped_new(obj, @query.context)
27
+ end
28
+ end
29
+
30
+ def call
31
+ @all_selections = [{}, (prototype_result = {})]
32
+ @runner.gather_selections(@parent_type, @selections, self, self.query, @all_selections, @all_selections[1], into: @all_selections[0])
33
+ continue_selections = []
34
+ i = 0
35
+ l = @all_selections.length
36
+ while i < l
37
+ grouped_selections = @all_selections[i]
38
+ selections_prototype_result = @all_selections[i + 1]
39
+ if (directives_owner = grouped_selections.delete(:__node))
40
+ directives = directives_owner.directives
41
+ continue_execution = true
42
+ directives.each do |dir_node|
43
+ dir_defn = @runner.runtime_directives[dir_node.name]
44
+ if dir_defn # not present for `skip` or `include`
45
+ dir_args, _errors = @runner.input_values[query].argument_values(dir_defn, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
46
+ result = case directives_owner
47
+ when Language::Nodes::FragmentSpread
48
+ dir_defn.resolve_fragment_spread(directives_owner, @parent_type, @objects, dir_args, self.query.context)
49
+ when Language::Nodes::InlineFragment
50
+ dir_defn.resolve_inline_fragment(directives_owner, @parent_type, @objects, dir_args, self.query.context)
51
+ else
52
+ raise ArgumentError, "Unhandled directive owner (#{directives_owner.class}): #{directives_owner.inspect}"
53
+ end
54
+ if result.is_a?(Finalizer)
55
+ result.path = path
56
+ @results.each do |r|
57
+ @runner.add_finalizer(@query, r, nil, result)
58
+ end
59
+ if result.is_a?(HaltExecution)
60
+ continue_execution = false
61
+ break
62
+ end
63
+ end
64
+
65
+ if continue_execution
66
+ prototype_result.merge!(selections_prototype_result)
67
+ grouped_selections.each_value { |v| continue_selections << v }
68
+ end
69
+ else
70
+ grouped_selections.each_value { |v| continue_selections << v }
71
+ end
72
+ end
73
+ else
74
+ grouped_selections.each_value { |v| continue_selections << v }
75
+ end
76
+
77
+ if @clobber
78
+ i2 = 0
79
+ l2 = @results.length
80
+ while i2 < l2
81
+ @results[i2].replace(prototype_result)
82
+ i2 += 1
83
+ end
84
+ end
85
+
86
+ i += 2
87
+ end
88
+
89
+ continue_selections.each do |frs|
90
+ @runner.add_step(frs)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/execution/directive_checks"
3
+ require "graphql/execution/next"
3
4
  require "graphql/execution/interpreter"
4
5
  require "graphql/execution/lazy"
5
6
  require "graphql/execution/lookahead"
@@ -9,10 +10,20 @@ require "graphql/execution/errors"
9
10
  module GraphQL
10
11
  module Execution
11
12
  # @api private
12
- class Skip < GraphQL::Error; end
13
+ class Skip < GraphQL::RuntimeError
14
+ attr_accessor :path
15
+ def ast_nodes=(_ignored); end
13
16
 
14
- # Just a singleton for implementing {Query::Context#skip}
15
- # @api private
16
- SKIP = Skip.new
17
+ def finalize_graphql_result(query, result_data, key)
18
+ case result_data
19
+ when Hash
20
+ result_data.delete(key)
21
+ when Array
22
+ result_data.delete_at(key)
23
+ else
24
+ raise "Unexpected result data #{result_data.class}: #{result_data}"
25
+ end
26
+ end
27
+ end
17
28
  end
18
29
  end
@@ -3,10 +3,7 @@ module GraphQL
3
3
  # If a field's resolve function returns a {ExecutionError},
4
4
  # the error will be inserted into the response's `"errors"` key
5
5
  # and the field will resolve to `nil`.
6
- class ExecutionError < GraphQL::Error
7
- # @return [GraphQL::Language::Nodes::Field] the field where the error occurred
8
- attr_accessor :ast_node
9
-
6
+ class ExecutionError < GraphQL::RuntimeError
10
7
  # @return [String] an array describing the JSON-path into the execution
11
8
  # response which corresponds to this error.
12
9
  attr_accessor :path
@@ -21,25 +18,33 @@ module GraphQL
21
18
  # under the `extensions` key.
22
19
  attr_accessor :extensions
23
20
 
24
- def initialize(message, ast_node: nil, options: nil, extensions: nil)
25
- @ast_node = ast_node
21
+ def initialize(message, ast_node: nil, ast_nodes: nil, options: nil, extensions: nil)
22
+ @ast_nodes = ast_nodes || [ast_node]
26
23
  @options = options
27
24
  @extensions = extensions
28
25
  super(message)
29
26
  end
30
27
 
28
+ def finalize_graphql_result(query, result_data, key)
29
+ add_this_error = !query.context.errors.any? { |e| e.eql?(self) }
30
+ if add_this_error
31
+ query.context.add_error(self)
32
+ end
33
+ if ast_node.is_a?(GraphQL::Language::Nodes::Directive)
34
+ # This is for backwards compatibility ... what does the spec say?
35
+ result_data.delete(key)
36
+ else
37
+ result_data[key] = nil
38
+ end
39
+ end
40
+
31
41
  # @return [Hash] An entry for the response's "errors" key
32
42
  def to_h
33
43
  hash = {
34
44
  "message" => message,
35
45
  }
36
46
  if ast_node
37
- hash["locations"] = [
38
- {
39
- "line" => ast_node.line,
40
- "column" => ast_node.col,
41
- }
42
- ]
47
+ hash["locations"] = @ast_nodes.map { |a| { "line" => a.line, "column" => a.col } }
43
48
  end
44
49
  if path
45
50
  hash["path"] = path
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ # This error is raised when `Types::Float` is given a non-finite input value.
4
+ class FloatDecodingError < GraphQL::RuntimeTypeError
5
+ # The value which couldn't be decoded
6
+ attr_reader :float_value
7
+
8
+ def initialize(value)
9
+ @float_value = value
10
+ super("Float is not finite: #{value.inspect}.")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ # This error is raised when `Types::Float` is asked to return a non-finite value.
4
+ class FloatEncodingError < GraphQL::RuntimeTypeError
5
+ # The value which couldn't be encoded
6
+ attr_reader :float_value
7
+
8
+ # @return [GraphQL::Schema::Field] The field that returned a non-finite float
9
+ attr_reader :field
10
+
11
+ # @return [Array<String, Integer>] Where the field appeared in the GraphQL response
12
+ attr_reader :path
13
+
14
+ def initialize(value, context:)
15
+ @float_value = value
16
+ @field = context[:current_field]
17
+ @path = context[:current_path]
18
+ message = "Float is not finite: #{value.inspect}".dup
19
+ if @path
20
+ message << " @ #{@path.join(".")}"
21
+ end
22
+ if @field
23
+ message << " (#{@field.path})"
24
+ end
25
+ super("#{message}.")
26
+ end
27
+ end
28
+ end
@@ -12,7 +12,7 @@ module GraphQL
12
12
  field :name, String, null: false, method: :graphql_name
13
13
  field :description, String
14
14
  field :locations, [GraphQL::Schema::LateBoundType.new("__DirectiveLocation")], null: false, scope: false
15
- field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false do
15
+ field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false, resolve_each: :resolve_args do
16
16
  argument :include_deprecated, Boolean, required: false, default_value: false
17
17
  end
18
18
  field :on_operation, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_operation?
@@ -21,11 +21,15 @@ module GraphQL
21
21
 
22
22
  field :is_repeatable, Boolean, method: :repeatable?
23
23
 
24
- def args(include_deprecated:)
25
- args = @context.types.arguments(@object)
24
+ def self.resolve_args(object, context, include_deprecated:)
25
+ args = context.types.arguments(object)
26
26
  args = args.reject(&:deprecation_reason) unless include_deprecated
27
27
  args
28
28
  end
29
+
30
+ def args(include_deprecated:)
31
+ self.class.resolve_args(object, context, include_deprecated: include_deprecated)
32
+ end
29
33
  end
30
34
  end
31
35
  end
@@ -2,9 +2,13 @@
2
2
  module GraphQL
3
3
  module Introspection
4
4
  class DynamicFields < Introspection::BaseObject
5
- field :__typename, String, "The name of this type", null: false, dynamic_introspection: true
5
+ field :__typename, String, "The name of this type", null: false, dynamic_introspection: true, resolve_each: true
6
6
 
7
7
  def __typename
8
+ self.class.__typename(object, context)
9
+ end
10
+
11
+ def self.__typename(object, context)
8
12
  object.class.graphql_name
9
13
  end
10
14
  end
@@ -2,19 +2,27 @@
2
2
  module GraphQL
3
3
  module Introspection
4
4
  class EntryPoints < Introspection::BaseObject
5
- field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false, dynamic_introspection: true
6
- field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", dynamic_introspection: true do
5
+ field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false, dynamic_introspection: true, resolve_static: true
6
+ field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", dynamic_introspection: true, resolve_static: true do
7
7
  argument :name, String
8
8
  end
9
9
 
10
- def __schema
10
+ def self.__schema(context)
11
11
  # Apply wrapping manually since this field isn't wrapped by instrumentation
12
12
  schema = context.schema
13
13
  schema_type = schema.introspection_system.types["__Schema"]
14
14
  schema_type.wrap(schema, context)
15
15
  end
16
16
 
17
+ def __schema
18
+ self.class.__schema(context)
19
+ end
20
+
17
21
  def __type(name:)
22
+ self.class.__type(context, name: name)
23
+ end
24
+
25
+ def self.__type(context, name:)
18
26
  if context.types.reachable_type?(name) && (type = context.types.type(name))
19
27
  type
20
28
  elsif (type = context.schema.extra_types.find { |t| t.graphql_name == name })
@@ -6,17 +6,17 @@ module GraphQL
6
6
  description "One possible value for a given Enum. Enum values are unique values, not a "\
7
7
  "placeholder for a string or numeric value. However an Enum value is returned in "\
8
8
  "a JSON response as a string."
9
- field :name, String, null: false
9
+ field :name, String, null: false, method: :graphql_name
10
10
  field :description, String
11
- field :is_deprecated, Boolean, null: false
11
+ field :is_deprecated, Boolean, null: false, resolve_each: :resolve_is_deprecated
12
12
  field :deprecation_reason, String
13
13
 
14
- def name
15
- object.graphql_name
14
+ def self.resolve_is_deprecated(object, context)
15
+ !!object.deprecation_reason
16
16
  end
17
17
 
18
18
  def is_deprecated
19
- !!@object.deprecation_reason
19
+ self.class.resolve_is_deprecated(object, context)
20
20
  end
21
21
  end
22
22
  end
@@ -7,22 +7,30 @@ module GraphQL
7
7
  "a name, potentially a list of arguments, and a return type."
8
8
  field :name, String, null: false
9
9
  field :description, String
10
- field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false do
10
+ field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false, resolve_each: :resolve_args do
11
11
  argument :include_deprecated, Boolean, required: false, default_value: false
12
12
  end
13
13
  field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false
14
- field :is_deprecated, Boolean, null: false
14
+ field :is_deprecated, Boolean, null: false, resolve_each: :resolve_is_deprecated
15
15
  field :deprecation_reason, String
16
16
 
17
+ def self.resolve_is_deprecated(object, _context)
18
+ !!object.deprecation_reason
19
+ end
20
+
17
21
  def is_deprecated
18
- !!@object.deprecation_reason
22
+ self.class.resolve_is_deprecated(object, context)
19
23
  end
20
24
 
21
- def args(include_deprecated:)
22
- args = @context.types.arguments(@object)
25
+ def self.resolve_args(object, context, include_deprecated:)
26
+ args = context.types.arguments(object)
23
27
  args = args.reject(&:deprecation_reason) unless include_deprecated
24
28
  args
25
29
  end
30
+
31
+ def args(include_deprecated:)
32
+ self.class.resolve_args(object, context, include_deprecated: include_deprecated)
33
+ end
26
34
  end
27
35
  end
28
36
  end
@@ -9,53 +9,61 @@ module GraphQL
9
9
  field :name, String, null: false
10
10
  field :description, String
11
11
  field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false
12
- field :default_value, String, "A GraphQL-formatted string representing the default value for this input value."
13
- field :is_deprecated, Boolean, null: false
12
+ field :default_value, String, "A GraphQL-formatted string representing the default value for this input value.", resolve_each: :resolve_default_value
13
+ field :is_deprecated, Boolean, null: false, resolve_each: :resolve_is_deprecated
14
14
  field :deprecation_reason, String
15
15
 
16
+ def self.resolve_is_deprecated(object, context)
17
+ !!object.deprecation_reason
18
+ end
19
+
16
20
  def is_deprecated
17
- !!@object.deprecation_reason
21
+ self.class.resolve_is_deprecated(object, context)
18
22
  end
19
23
 
20
- def default_value
21
- if @object.default_value?
22
- value = @object.default_value
24
+ def self.resolve_default_value(object, context)
25
+ if object.default_value?
26
+ value = object.default_value
23
27
  if value.nil?
24
28
  'null'
25
29
  else
26
- if (@object.type.kind.list? || (@object.type.kind.non_null? && @object.type.of_type.kind.list?)) && !value.respond_to?(:map)
30
+ if (object.type.kind.list? || (object.type.kind.non_null? && object.type.of_type.kind.list?)) && !value.respond_to?(:map)
27
31
  # This is a bit odd -- we expect the default value to be an application-style value, so we use coerce result below.
28
32
  # But coerce_result doesn't wrap single-item lists, which are valid inputs to list types.
29
33
  # So, apply that wrapper here if needed.
30
34
  value = [value]
31
35
  end
32
- coerced_default_value = @object.type.coerce_result(value, @context)
33
- serialize_default_value(coerced_default_value, @object.type)
36
+ coerced_default_value = object.type.coerce_result(value, context)
37
+ serialize_default_value(coerced_default_value, object.type, context)
34
38
  end
35
39
  else
36
40
  nil
37
41
  end
38
42
  end
39
43
 
44
+ def default_value
45
+ self.class.resolve_default_value(object, context)
46
+ end
47
+
40
48
 
41
49
  private
42
50
 
43
51
  # Recursively serialize, taking care not to add quotes to enum values
44
- def serialize_default_value(value, type)
52
+ def self.serialize_default_value(value, type, context)
45
53
  if value.nil?
46
54
  'null'
47
55
  elsif type.kind.list?
48
56
  inner_type = type.of_type
49
- "[" + value.map { |v| serialize_default_value(v, inner_type) }.join(", ") + "]"
57
+ "[" + value.map { |v| serialize_default_value(v, inner_type, context) }.join(", ") + "]"
50
58
  elsif type.kind.non_null?
51
- serialize_default_value(value, type.of_type)
59
+ serialize_default_value(value, type.of_type, context)
52
60
  elsif type.kind.enum?
53
61
  value
54
62
  elsif type.kind.input_object?
55
63
  "{" +
56
64
  value.map do |k, v|
57
65
  arg_defn = type.get_argument(k, context)
58
- "#{k}: #{serialize_default_value(v, arg_defn.type)}"
66
+ "#{k}: #{serialize_default_value(v, arg_defn.type, context)}"
59
67
  end.join(", ") +
60
68
  "}"
61
69
  else
@@ -13,12 +13,16 @@ module GraphQL
13
13
  field :mutation_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server supports mutation, the type that mutation operations will be rooted at."
14
14
  field :subscription_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server support subscription, the type that subscription operations will be rooted at."
15
15
  field :directives, [GraphQL::Schema::LateBoundType.new("__Directive")], "A list of all directives supported by this server.", null: false, scope: false
16
- field :description, String, resolver_method: :schema_description
16
+ field :description, String, resolver_method: :schema_description, resolve_static: :schema_description
17
17
 
18
- def schema_description
18
+ def self.schema_description(context)
19
19
  context.schema.description
20
20
  end
21
21
 
22
+ def schema_description
23
+ self.class.schema_description(context)
24
+ end
25
+
22
26
  def types
23
27
  query_types = context.types.all_types
24
28
  types = query_types + context.schema.extra_types
@@ -11,32 +11,36 @@ module GraphQL
11
11
  "they describe. Abstract types, Union and Interface, provide the Object types "\
12
12
  "possible at runtime. List and NonNull types compose other types."
13
13
 
14
- field :kind, GraphQL::Schema::LateBoundType.new("__TypeKind"), null: false
14
+ field :kind, GraphQL::Schema::LateBoundType.new("__TypeKind"), null: false, resolve_each: :resolve_kind
15
15
  field :name, String, method: :graphql_name
16
16
  field :description, String
17
- field :fields, [GraphQL::Schema::LateBoundType.new("__Field")], scope: false do
17
+ field :fields, [GraphQL::Schema::LateBoundType.new("__Field")], scope: false, resolve_each: :resolve_fields do
18
18
  argument :include_deprecated, Boolean, required: false, default_value: false
19
19
  end
20
- field :interfaces, [GraphQL::Schema::LateBoundType.new("__Type")], scope: false
21
- field :possible_types, [GraphQL::Schema::LateBoundType.new("__Type")], scope: false
22
- field :enum_values, [GraphQL::Schema::LateBoundType.new("__EnumValue")], scope: false do
20
+ field :interfaces, [GraphQL::Schema::LateBoundType.new("__Type")], scope: false, resolve_each: :resolve_interfaces
21
+ field :possible_types, [GraphQL::Schema::LateBoundType.new("__Type")], scope: false, resolve_each: :resolve_possible_types
22
+ field :enum_values, [GraphQL::Schema::LateBoundType.new("__EnumValue")], scope: false, resolve_each: :resolve_enum_values do
23
23
  argument :include_deprecated, Boolean, required: false, default_value: false
24
24
  end
25
- field :input_fields, [GraphQL::Schema::LateBoundType.new("__InputValue")], scope: false do
25
+ field :input_fields, [GraphQL::Schema::LateBoundType.new("__InputValue")], scope: false, resolve_each: :resolve_input_fields do
26
26
  argument :include_deprecated, Boolean, required: false, default_value: false
27
27
  end
28
- field :of_type, GraphQL::Schema::LateBoundType.new("__Type")
28
+ field :of_type, GraphQL::Schema::LateBoundType.new("__Type"), resolve_each: :resolve_of_type
29
29
 
30
- field :specifiedByURL, String, resolver_method: :specified_by_url
30
+ field :specifiedByURL, String, resolve_each: :resolve_specified_by_url, resolver_method: :specified_by_url
31
31
 
32
- field :is_one_of, Boolean, null: false
32
+ field :is_one_of, Boolean, null: false, resolve_each: :resolve_is_one_of
33
33
 
34
- def is_one_of
34
+ def self.resolve_is_one_of(object, _ctx)
35
35
  object.kind.input_object? &&
36
36
  object.directives.any? { |d| d.graphql_name == "oneOf" }
37
37
  end
38
38
 
39
- def specified_by_url
39
+ def is_one_of
40
+ self.class.resolve_is_one_of(object, context)
41
+ end
42
+
43
+ def self.resolve_specified_by_url(object, _ctx)
40
44
  if object.kind.scalar?
41
45
  object.specified_by_url
42
46
  else
@@ -44,15 +48,23 @@ module GraphQL
44
48
  end
45
49
  end
46
50
 
51
+ def specified_by_url
52
+ self.class.resolve_specified_by_url(object, context)
53
+ end
54
+
55
+ def self.resolve_kind(object, context)
56
+ object.kind.name
57
+ end
58
+
47
59
  def kind
48
- @object.kind.name
60
+ self.class.resolve_kind(object, context)
49
61
  end
50
62
 
51
- def enum_values(include_deprecated:)
52
- if !@object.kind.enum?
63
+ def self.resolve_enum_values(object, context, include_deprecated:)
64
+ if !object.kind.enum?
53
65
  nil
54
66
  else
55
- enum_values = @context.types.enum_values(@object)
67
+ enum_values = context.types.enum_values(object)
56
68
 
57
69
  if !include_deprecated
58
70
  enum_values = enum_values.select {|f| !f.deprecation_reason }
@@ -62,17 +74,25 @@ module GraphQL
62
74
  end
63
75
  end
64
76
 
65
- def interfaces
66
- if @object.kind.object? || @object.kind.interface?
67
- @context.types.interfaces(@object).sort_by(&:graphql_name)
77
+ def enum_values(include_deprecated:)
78
+ self.class.resolve_enum_values(object, context, include_deprecated: include_deprecated)
79
+ end
80
+
81
+ def self.resolve_interfaces(object, context)
82
+ if object.kind.object? || object.kind.interface?
83
+ context.types.interfaces(object).sort_by(&:graphql_name)
68
84
  else
69
85
  nil
70
86
  end
71
87
  end
72
88
 
73
- def input_fields(include_deprecated:)
74
- if @object.kind.input_object?
75
- args = @context.types.arguments(@object)
89
+ def interfaces
90
+ self.class.resolve_interfaces(object, context)
91
+ end
92
+
93
+ def self.resolve_input_fields(object, context, include_deprecated:)
94
+ if object.kind.input_object?
95
+ args = context.types.arguments(object)
76
96
  args = args.reject(&:deprecation_reason) unless include_deprecated
77
97
  args
78
98
  else
@@ -80,19 +100,27 @@ module GraphQL
80
100
  end
81
101
  end
82
102
 
83
- def possible_types
84
- if @object.kind.abstract?
85
- @context.types.possible_types(@object).sort_by(&:graphql_name)
103
+ def input_fields(include_deprecated:)
104
+ self.class.resolve_input_fields(object, context, include_deprecated: include_deprecated)
105
+ end
106
+
107
+ def self.resolve_possible_types(object, context)
108
+ if object.kind.abstract?
109
+ context.types.possible_types(object).sort_by(&:graphql_name)
86
110
  else
87
111
  nil
88
112
  end
89
113
  end
90
114
 
91
- def fields(include_deprecated:)
92
- if !@object.kind.fields?
115
+ def possible_types
116
+ self.class.resolve_possible_types(object, context)
117
+ end
118
+
119
+ def self.resolve_fields(object, context, include_deprecated:)
120
+ if !object.kind.fields?
93
121
  nil
94
122
  else
95
- fields = @context.types.fields(@object)
123
+ fields = context.types.fields(object)
96
124
  if !include_deprecated
97
125
  fields = fields.select {|f| !f.deprecation_reason }
98
126
  end
@@ -100,8 +128,16 @@ module GraphQL
100
128
  end
101
129
  end
102
130
 
131
+ def fields(include_deprecated:)
132
+ self.class.resolve_fields(object, context, include_deprecated: include_deprecated)
133
+ end
134
+
135
+ def self.resolve_of_type(object, _ctx)
136
+ object.kind.wraps? ? object.of_type : nil
137
+ end
138
+
103
139
  def of_type
104
- @object.kind.wraps? ? @object.of_type : nil
140
+ self.class.resolve_of_type(object, context)
105
141
  end
106
142
  end
107
143
  end
@@ -2,7 +2,7 @@
2
2
  module GraphQL
3
3
  # Raised automatically when a field's resolve function returns `nil`
4
4
  # for a non-null field.
5
- class InvalidNullError < GraphQL::Error
5
+ class InvalidNullError < GraphQL::RuntimeError
6
6
  # @return [GraphQL::BaseType] The owner of {#field}
7
7
  attr_reader :parent_type
8
8
 
@@ -10,17 +10,23 @@ module GraphQL
10
10
  attr_reader :field
11
11
 
12
12
  # @return [GraphQL::Language::Nodes::Field] the field where the error occurred
13
- attr_reader :ast_node
13
+ def ast_node
14
+ @ast_nodes.first
15
+ end
16
+
17
+ attr_reader :ast_nodes
14
18
 
15
19
  # @return [Boolean] indicates an array result caused the error
16
20
  attr_reader :is_from_array
17
21
 
18
- def initialize(parent_type, field, ast_node, is_from_array: false)
22
+ attr_accessor :path
23
+
24
+ def initialize(parent_type, field, ast_node_or_nodes, is_from_array: false, path: nil)
19
25
  @parent_type = parent_type
20
26
  @field = field
21
- @ast_node = ast_node
27
+ @ast_nodes = Array(ast_node_or_nodes)
22
28
  @is_from_array = is_from_array
23
-
29
+ @path = path
24
30
  # For List elements, identify the non-null error is for an
25
31
  # element and the required element type so it's not ambiguous
26
32
  # whether it was caused by a null instead of the list or a