graphql 1.10.0.pre1 → 1.10.0.pre2

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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +1 -0
  3. data/lib/generators/graphql/install_generator.rb +1 -0
  4. data/lib/generators/graphql/mutation_generator.rb +1 -1
  5. data/lib/generators/graphql/templates/base_field.erb +0 -4
  6. data/lib/generators/graphql/templates/base_mutation.erb +8 -0
  7. data/lib/generators/graphql/templates/graphql_controller.erb +5 -0
  8. data/lib/generators/graphql/templates/mutation.erb +1 -1
  9. data/lib/generators/graphql/templates/schema.erb +1 -1
  10. data/lib/graphql.rb +4 -1
  11. data/lib/graphql/analysis/ast.rb +14 -13
  12. data/lib/graphql/analysis/ast/analyzer.rb +23 -4
  13. data/lib/graphql/analysis/ast/field_usage.rb +1 -1
  14. data/lib/graphql/analysis/ast/max_query_complexity.rb +3 -3
  15. data/lib/graphql/analysis/ast/max_query_depth.rb +7 -3
  16. data/lib/graphql/analysis/ast/query_complexity.rb +2 -2
  17. data/lib/graphql/analysis/ast/visitor.rb +3 -3
  18. data/lib/graphql/base_type.rb +1 -1
  19. data/lib/graphql/directive.rb +0 -1
  20. data/lib/graphql/directive/deprecated_directive.rb +1 -12
  21. data/lib/graphql/execution/errors.rb +4 -8
  22. data/lib/graphql/execution/interpreter.rb +5 -11
  23. data/lib/graphql/execution/interpreter/runtime.rb +56 -48
  24. data/lib/graphql/execution/lazy/lazy_method_map.rb +4 -0
  25. data/lib/graphql/execution/lookahead.rb +5 -5
  26. data/lib/graphql/execution/multiplex.rb +10 -0
  27. data/lib/graphql/function.rb +1 -1
  28. data/lib/graphql/input_object_type.rb +3 -2
  29. data/lib/graphql/interface_type.rb +1 -1
  30. data/lib/graphql/introspection/base_object.rb +2 -5
  31. data/lib/graphql/introspection/directive_type.rb +1 -1
  32. data/lib/graphql/introspection/entry_points.rb +6 -6
  33. data/lib/graphql/introspection/schema_type.rb +1 -6
  34. data/lib/graphql/introspection/type_type.rb +5 -5
  35. data/lib/graphql/language.rb +1 -1
  36. data/lib/graphql/language/block_string.rb +2 -2
  37. data/lib/graphql/language/definition_slice.rb +21 -10
  38. data/lib/graphql/language/document_from_schema_definition.rb +42 -42
  39. data/lib/graphql/language/lexer.rb +49 -48
  40. data/lib/graphql/language/lexer.rl +49 -48
  41. data/lib/graphql/language/nodes.rb +11 -8
  42. data/lib/graphql/language/parser.rb +4 -1
  43. data/lib/graphql/language/parser.y +4 -1
  44. data/lib/graphql/language/token.rb +1 -1
  45. data/lib/graphql/pagination/array_connection.rb +0 -1
  46. data/lib/graphql/pagination/connection.rb +31 -10
  47. data/lib/graphql/pagination/connections.rb +7 -2
  48. data/lib/graphql/pagination/relation_connection.rb +1 -7
  49. data/lib/graphql/query.rb +9 -4
  50. data/lib/graphql/query/arguments.rb +8 -1
  51. data/lib/graphql/query/literal_input.rb +2 -1
  52. data/lib/graphql/query/variables.rb +5 -1
  53. data/lib/graphql/relay/base_connection.rb +3 -3
  54. data/lib/graphql/relay/relation_connection.rb +9 -5
  55. data/lib/graphql/schema.rb +699 -153
  56. data/lib/graphql/schema/argument.rb +20 -4
  57. data/lib/graphql/schema/build_from_definition.rb +64 -31
  58. data/lib/graphql/schema/built_in_types.rb +5 -5
  59. data/lib/graphql/schema/directive.rb +16 -1
  60. data/lib/graphql/schema/directive/deprecated.rb +18 -0
  61. data/lib/graphql/schema/directive/feature.rb +1 -1
  62. data/lib/graphql/schema/enum.rb +39 -3
  63. data/lib/graphql/schema/field.rb +39 -9
  64. data/lib/graphql/schema/field/connection_extension.rb +4 -4
  65. data/lib/graphql/schema/find_inherited_value.rb +13 -0
  66. data/lib/graphql/schema/finder.rb +13 -11
  67. data/lib/graphql/schema/input_object.rb +109 -1
  68. data/lib/graphql/schema/interface.rb +8 -7
  69. data/lib/graphql/schema/introspection_system.rb +104 -36
  70. data/lib/graphql/schema/late_bound_type.rb +1 -0
  71. data/lib/graphql/schema/list.rb +26 -0
  72. data/lib/graphql/schema/loader.rb +10 -4
  73. data/lib/graphql/schema/member.rb +3 -0
  74. data/lib/graphql/schema/member/base_dsl_methods.rb +23 -13
  75. data/lib/graphql/schema/member/build_type.rb +1 -1
  76. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  77. data/lib/graphql/schema/member/has_fields.rb +15 -6
  78. data/lib/graphql/schema/member/instrumentation.rb +6 -1
  79. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  80. data/lib/graphql/schema/member/validates_input.rb +33 -0
  81. data/lib/graphql/schema/non_null.rb +25 -0
  82. data/lib/graphql/schema/object.rb +14 -1
  83. data/lib/graphql/schema/printer.rb +4 -3
  84. data/lib/graphql/schema/relay_classic_mutation.rb +5 -1
  85. data/lib/graphql/schema/resolver.rb +20 -2
  86. data/lib/graphql/schema/scalar.rb +18 -3
  87. data/lib/graphql/schema/subscription.rb +1 -1
  88. data/lib/graphql/schema/timeout_middleware.rb +3 -2
  89. data/lib/graphql/schema/traversal.rb +1 -1
  90. data/lib/graphql/schema/type_expression.rb +22 -24
  91. data/lib/graphql/schema/validation.rb +17 -1
  92. data/lib/graphql/schema/warden.rb +46 -17
  93. data/lib/graphql/schema/wrapper.rb +1 -1
  94. data/lib/graphql/static_validation/base_visitor.rb +10 -6
  95. data/lib/graphql/static_validation/definition_dependencies.rb +21 -12
  96. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
  97. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  98. data/lib/graphql/static_validation/type_stack.rb +2 -2
  99. data/lib/graphql/static_validation/validator.rb +1 -1
  100. data/lib/graphql/subscriptions.rb +38 -13
  101. data/lib/graphql/subscriptions/event.rb +24 -7
  102. data/lib/graphql/subscriptions/instrumentation.rb +1 -1
  103. data/lib/graphql/subscriptions/subscription_root.rb +0 -1
  104. data/lib/graphql/tracing/active_support_notifications_tracing.rb +10 -10
  105. data/lib/graphql/tracing/platform_tracing.rb +1 -2
  106. data/lib/graphql/tracing/skylight_tracing.rb +1 -0
  107. data/lib/graphql/unresolved_type_error.rb +2 -2
  108. data/lib/graphql/upgrader/member.rb +1 -1
  109. data/lib/graphql/version.rb +1 -1
  110. metadata +5 -2
@@ -6,6 +6,7 @@ module GraphQL
6
6
  # @api Private
7
7
  class LateBoundType
8
8
  attr_reader :name
9
+ alias :graphql_name :name
9
10
  def initialize(local_name)
10
11
  @name = local_name
11
12
  end
@@ -6,6 +6,8 @@ module GraphQL
6
6
  # Wraps a {Schema::Member} as a list type.
7
7
  # @see {Schema::Member::TypeSystemHelpers#to_list_type}
8
8
  class List < GraphQL::Schema::Wrapper
9
+ include Schema::Member::ValidatesInput
10
+
9
11
  def to_graphql
10
12
  @of_type.graphql_definition.to_list_type
11
13
  end
@@ -23,6 +25,30 @@ module GraphQL
23
25
  def to_type_signature
24
26
  "[#{@of_type.to_type_signature}]"
25
27
  end
28
+
29
+ # This is for introspection, where it's expected the name will be `null`
30
+ def graphql_name
31
+ nil
32
+ end
33
+
34
+ def coerce_result(value, ctx)
35
+ value.map { |i| i.nil? ? nil : of_type.coerce_result(i, ctx) }
36
+ end
37
+
38
+ def coerce_input(value, ctx)
39
+ Array(value).map { |item| item.nil? ? item : of_type.coerce_input(item, ctx) }
40
+ end
41
+
42
+ def validate_non_null_input(value, ctx)
43
+ result = GraphQL::Query::InputValidationResult.new
44
+ Array(value).each_with_index do |item, index|
45
+ item_result = of_type.validate_input(item, ctx)
46
+ if !item_result.valid?
47
+ result.merge_result!(index, item_result)
48
+ end
49
+ end
50
+ result
51
+ end
26
52
  end
27
53
  end
28
54
  end
@@ -34,7 +34,7 @@ module GraphQL
34
34
  end
35
35
 
36
36
  NullResolveType = ->(type, obj, ctx) {
37
- raise(NotImplementedError, "This schema was loaded from string, so it can't resolve types for objects")
37
+ raise(GraphQL::RequiredImplementationMissingError, "This schema was loaded from string, so it can't resolve types for objects")
38
38
  }
39
39
 
40
40
  NullScalarCoerce = ->(val, _ctx) { val }
@@ -45,13 +45,19 @@ module GraphQL
45
45
  def resolve_type(types, type)
46
46
  case kind = type.fetch("kind")
47
47
  when "ENUM", "INTERFACE", "INPUT_OBJECT", "OBJECT", "SCALAR", "UNION"
48
- types.fetch(type.fetch("name"))
48
+ type_name = type.fetch("name")
49
+ type = types[type_name] || Schema::BUILT_IN_TYPES[type_name]
50
+ if type.nil?
51
+ raise "Type not found: #{type_name.inspect} among #{types.keys.sort}"
52
+ else
53
+ type.graphql_definition
54
+ end
49
55
  when "LIST"
50
56
  ListType.new(of_type: resolve_type(types, type.fetch("ofType")))
51
57
  when "NON_NULL"
52
58
  NonNullType.new(of_type: resolve_type(types, type.fetch("ofType")))
53
59
  else
54
- fail NotImplementedError, "#{kind} not implemented"
60
+ fail GraphQL::RequiredImplementationMissingError, "#{kind} not implemented"
55
61
  end
56
62
  end
57
63
 
@@ -171,7 +177,7 @@ module GraphQL
171
177
  }
172
178
  )
173
179
  else
174
- fail NotImplementedError, "#{type["kind"]} not implemented"
180
+ fail GraphQL::RequiredImplementationMissingError, "#{type["kind"]} not implemented"
175
181
  end
176
182
  end
177
183
  end
@@ -8,6 +8,7 @@ require 'graphql/schema/member/has_path'
8
8
  require 'graphql/schema/member/relay_shortcuts'
9
9
  require 'graphql/schema/member/scoped'
10
10
  require 'graphql/schema/member/type_system_helpers'
11
+ require 'graphql/schema/member/validates_input'
11
12
  require "graphql/relay/type_extensions"
12
13
 
13
14
  module GraphQL
@@ -21,6 +22,8 @@ module GraphQL
21
22
  extend CachedGraphQLDefinition
22
23
  extend GraphQL::Relay::TypeExtensions
23
24
  extend BaseDSLMethods
25
+ extend BaseDSLMethods::ConfigurationExtension
26
+ introspection(false)
24
27
  extend TypeSystemHelpers
25
28
  extend Scoped
26
29
  extend RelayShortcuts
@@ -18,16 +18,17 @@ module GraphQL
18
18
  # @param new_name [String]
19
19
  # @return [String]
20
20
  def graphql_name(new_name = nil)
21
- case
22
- when new_name
21
+ if new_name
23
22
  @graphql_name = new_name
24
- when overridden = overridden_graphql_name
25
- overridden
26
23
  else
27
- default_graphql_name
24
+ overridden_graphql_name || default_graphql_name
28
25
  end
29
26
  end
30
27
 
28
+ def overridden_graphql_name
29
+ @graphql_name
30
+ end
31
+
31
32
  # Just a convenience method to point out that people should use graphql_name instead
32
33
  def name(new_name = nil)
33
34
  return super() if new_name.nil?
@@ -46,7 +47,20 @@ module GraphQL
46
47
  if new_description
47
48
  @description = new_description
48
49
  else
49
- @description || find_inherited_value(:description)
50
+ @description
51
+ end
52
+ end
53
+
54
+ # This pushes some configurations _down_ the inheritance tree,
55
+ # in order to prevent repetitive lookups at runtime.
56
+ module ConfigurationExtension
57
+ def inherited(child_class)
58
+ child_class.introspection(introspection)
59
+ child_class.description(description)
60
+ if overridden_graphql_name
61
+ child_class.graphql_name(overridden_graphql_name)
62
+ end
63
+ super
50
64
  end
51
65
  end
52
66
 
@@ -55,7 +69,7 @@ module GraphQL
55
69
  if !new_introspection.nil?
56
70
  @introspection = new_introspection
57
71
  else
58
- @introspection || find_inherited_value(:introspection, false)
72
+ @introspection
59
73
  end
60
74
  end
61
75
 
@@ -74,21 +88,17 @@ module GraphQL
74
88
 
75
89
  # @return [GraphQL::BaseType] Convert this type to a legacy-style object.
76
90
  def to_graphql
77
- raise NotImplementedError
91
+ raise GraphQL::RequiredImplementationMissingError
78
92
  end
79
93
 
80
94
  alias :unwrap :itself
81
95
 
82
- def overridden_graphql_name
83
- @graphql_name || find_inherited_value(:overridden_graphql_name)
84
- end
85
-
86
96
  # Creates the default name for a schema member.
87
97
  # The default name is the Ruby constant name,
88
98
  # without any namespaces and with any `-Type` suffix removed
89
99
  def default_graphql_name
90
100
  @default_graphql_name ||= begin
91
- raise NotImplementedError, 'Anonymous class should declare a `graphql_name`' if name.nil?
101
+ raise GraphQL::RequiredImplementationMissingError, 'Anonymous class should declare a `graphql_name`' if name.nil?
92
102
 
93
103
  name.split("::").last.sub(/Type\Z/, "")
94
104
  end
@@ -81,7 +81,7 @@ module GraphQL
81
81
  end
82
82
 
83
83
  if return_type.nil?
84
- raise "Unexpected type input: #{type_expr} (#{type_expr.class})"
84
+ raise "Unexpected type input: #{type_expr.inspect} (#{type_expr.class})"
85
85
  end
86
86
 
87
87
  # Apply list_type first, that way the
@@ -97,7 +97,7 @@ module GraphQL
97
97
  # (Don't want to allow arbitrary access to objects this way)
98
98
  resolved_application_object_type = context.schema.resolve_type(lookup_as_type, application_object, context)
99
99
  context.schema.after_lazy(resolved_application_object_type) do |application_object_type|
100
- possible_object_types = context.schema.possible_types(lookup_as_type)
100
+ possible_object_types = context.warden.possible_types(lookup_as_type)
101
101
  if !possible_object_types.include?(application_object_type)
102
102
  err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
103
103
  load_application_object_failed(err)
@@ -105,7 +105,7 @@ module GraphQL
105
105
  # This object was loaded successfully
106
106
  # and resolved to the right type,
107
107
  # now apply the `.authorized?` class method if there is one
108
- if (class_based_type = application_object_type.metadata[:type_class])
108
+ if (class_based_type = application_object_type.type_class)
109
109
  context.schema.after_lazy(class_based_type.authorized?(application_object, context)) do |authed|
110
110
  if authed
111
111
  application_object
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ require 'irb/ruby-token'
3
+
2
4
  module GraphQL
3
5
  class Schema
4
6
  class Member
@@ -38,16 +40,23 @@ module GraphQL
38
40
  end
39
41
  end
40
42
 
43
+ # A list of Ruby keywords.
44
+ #
45
+ # @api private
46
+ RUBY_KEYWORDS = RubyToken::TokenDefinitions.select { |definition| definition[1] == RubyToken::TkId }
47
+ .map { |definition| definition[2] }
48
+ .compact
49
+
50
+ # A list of GraphQL-Ruby keywords.
51
+ #
52
+ # @api private
53
+ GRAPHQL_RUBY_KEYWORDS = [:context, :object, :method]
54
+
41
55
  # A list of field names that we should advise users to pick a different
42
56
  # resolve method name.
43
57
  #
44
58
  # @api private
45
- CONFLICT_FIELD_NAMES = Set.new([
46
- # GraphQL-Ruby conflicts
47
- :context, :object,
48
- # Ruby built-ins conflicts
49
- :method, :class
50
- ])
59
+ CONFLICT_FIELD_NAMES = Set.new(GRAPHQL_RUBY_KEYWORDS + RUBY_KEYWORDS)
51
60
 
52
61
  # Register this field with the class, overriding a previous one if needed.
53
62
  # @param field_defn [GraphQL::Schema::Field]
@@ -78,7 +78,7 @@ module GraphQL
78
78
 
79
79
  def call(obj, args, ctx)
80
80
  result = @inner_resolve.call(obj, args, ctx)
81
- if ctx.skip == result || ctx.schema.lazy?(result) || result.nil? || result.is_a?(GraphQL::ExecutionError) || ctx.wrapped_object
81
+ if ctx.skip == result || ctx.schema.lazy?(result) || result.nil? || execution_errors?(result) || ctx.wrapped_object
82
82
  result
83
83
  else
84
84
  ctx.wrapped_object = true
@@ -88,6 +88,11 @@ module GraphQL
88
88
 
89
89
  private
90
90
 
91
+ def execution_errors?(result)
92
+ result.is_a?(GraphQL::ExecutionError) ||
93
+ (result.is_a?(Array) && result.any? && result.all? { |v| v.is_a?(GraphQL::ExecutionError) })
94
+ end
95
+
91
96
  def proxy_to_depth(inner_obj, depth, ctx)
92
97
  if depth > 0
93
98
  inner_obj.map { |i| proxy_to_depth(i, depth - 1, ctx) }
@@ -30,7 +30,7 @@ module GraphQL
30
30
 
31
31
  # @return [GraphQL::TypeKinds::TypeKind]
32
32
  def kind
33
- raise NotImplementedError
33
+ raise GraphQL::RequiredImplementationMissingError
34
34
  end
35
35
  end
36
36
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ module ValidatesInput
7
+ def valid_input?(val, ctx)
8
+ validate_input(val, ctx).valid?
9
+ end
10
+
11
+ def validate_input(val, ctx)
12
+ if val.nil?
13
+ GraphQL::Query::InputValidationResult.new
14
+ else
15
+ validate_non_null_input(val, ctx)
16
+ end
17
+ end
18
+
19
+ def valid_isolated_input?(v)
20
+ valid_input?(v, GraphQL::Query::NullContext)
21
+ end
22
+
23
+ def coerce_isolated_input(v)
24
+ coerce_input(v, GraphQL::Query::NullContext)
25
+ end
26
+
27
+ def coerce_isolated_result(v)
28
+ coerce_result(v, GraphQL::Query::NullContext)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -6,6 +6,8 @@ module GraphQL
6
6
  # Wraps a {Schema::Member} when it is required.
7
7
  # @see {Schema::Member::TypeSystemHelpers#to_non_null_type}
8
8
  class NonNull < GraphQL::Schema::Wrapper
9
+ include Schema::Member::ValidatesInput
10
+
9
11
  def to_graphql
10
12
  @of_type.graphql_definition.to_non_null_type
11
13
  end
@@ -32,6 +34,29 @@ module GraphQL
32
34
  def inspect
33
35
  "#<#{self.class.name} @of_type=#{@of_type.inspect}>"
34
36
  end
37
+
38
+ def validate_input(value, ctx)
39
+ if value.nil?
40
+ result = GraphQL::Query::InputValidationResult.new
41
+ result.add_problem("Expected value to not be null")
42
+ result
43
+ else
44
+ of_type.validate_input(value, ctx)
45
+ end
46
+ end
47
+
48
+ # This is for introspection, where it's expected the name will be `null`
49
+ def graphql_name
50
+ nil
51
+ end
52
+
53
+ def coerce_input(value, ctx)
54
+ of_type.coerce_input(value, ctx)
55
+ end
56
+
57
+ def coerce_result(value, ctx)
58
+ of_type.coerce_result(value, ctx)
59
+ end
35
60
  end
36
61
  end
37
62
  end
@@ -84,11 +84,24 @@ module GraphQL
84
84
  include(int)
85
85
  end
86
86
  end
87
+ # Remove any interfaces which are being replaced (late-bound types are updated in place this way)
88
+ own_interfaces.reject! { |i|
89
+ new_interfaces.any? { |new_i|
90
+ new_name = new_i.is_a?(String) ? new_i : new_i.graphql_name
91
+ old_name = i.is_a?(String) ? i : i.graphql_name
92
+ new_name == old_name
93
+ }
94
+ }
87
95
  own_interfaces.concat(new_interfaces)
88
96
  end
89
97
 
90
98
  def interfaces
91
- own_interfaces + (superclass <= GraphQL::Schema::Object ? superclass.interfaces : [])
99
+ inherited_interfaces = (superclass.respond_to?(:interfaces) ? superclass.interfaces : nil)
100
+ if inherited_interfaces && !inherited_interfaces.empty?
101
+ own_interfaces + inherited_interfaces
102
+ else
103
+ own_interfaces
104
+ end
92
105
  end
93
106
 
94
107
  def own_interfaces
@@ -54,13 +54,14 @@ module GraphQL
54
54
  )
55
55
 
56
56
  @document = @document_from_schema.document
57
-
58
57
  @schema = schema
59
58
  end
60
59
 
61
60
  # Return the GraphQL schema string for the introspection type system
62
61
  def self.print_introspection_schema
63
- query_root = ObjectType.define(name: "Root")
62
+ query_root = ObjectType.define(name: "Root") do
63
+ field :throwaway_field, types.String
64
+ end
64
65
  schema = GraphQL::Schema.define(query: query_root)
65
66
 
66
67
  introspection_schema_ast = GraphQL::Language::DocumentFromSchemaDefinition.new(
@@ -97,7 +98,7 @@ module GraphQL
97
98
  if directive.name == "deprecated"
98
99
  reason = directive.arguments.find { |arg| arg.name == "reason" }
99
100
 
100
- if reason.value == GraphQL::Directive::DEFAULT_DEPRECATION_REASON
101
+ if reason.value == GraphQL::Schema::Directive::DEFAULT_DEPRECATION_REASON
101
102
  "@deprecated"
102
103
  else
103
104
  "@deprecated(reason: #{reason.value.to_s.inspect})"
@@ -33,9 +33,13 @@ module GraphQL
33
33
  # But when using the interpreter, no instrumenters are applied.
34
34
  if context.interpreter?
35
35
  input = inputs[:input].to_kwargs
36
+
37
+ new_extras = field ? field.extras : []
38
+ all_extras = self.class.extras + new_extras
39
+
36
40
  # Transfer these from the top-level hash to the
37
41
  # shortcutted `input:` object
38
- self.class.extras.each do |ext|
42
+ all_extras.each do |ext|
39
43
  # It's possible that the `extra` was not passed along by this point,
40
44
  # don't re-add it if it wasn't given here.
41
45
  if inputs.key?(ext)
@@ -29,9 +29,11 @@ module GraphQL
29
29
 
30
30
  # @param object [Object] the initialize object, pass to {Query.initialize} as `root_value`
31
31
  # @param context [GraphQL::Query::Context]
32
- def initialize(object:, context:)
32
+ # @param field [GraphQL::Schema::Field]
33
+ def initialize(object:, context:, field:)
33
34
  @object = object
34
35
  @context = context
36
+ @field = field
35
37
  # Since this hash is constantly rebuilt, cache it for this call
36
38
  @arguments_by_keyword = {}
37
39
  self.class.arguments.each do |name, arg|
@@ -46,6 +48,9 @@ module GraphQL
46
48
  # @return [GraphQL::Query::Context]
47
49
  attr_reader :context
48
50
 
51
+ # @return [GraphQL::Schema::Field]
52
+ attr_reader :field
53
+
49
54
  # This method is _actually_ called by the runtime,
50
55
  # it does some preparation and then eventually calls
51
56
  # the user-defined `#resolve` method.
@@ -103,7 +108,7 @@ module GraphQL
103
108
  # Do the work. Everything happens here.
104
109
  # @return [Object] An object corresponding to the return type
105
110
  def resolve(**args)
106
- raise NotImplementedError, "#{self.class.name}#resolve should execute the field's logic"
111
+ raise GraphQL::RequiredImplementationMissingError, "#{self.class.name}#resolve should execute the field's logic"
107
112
  end
108
113
 
109
114
  # Called before arguments are prepared.
@@ -255,6 +260,7 @@ module GraphQL
255
260
  arguments: arguments,
256
261
  null: null,
257
262
  complexity: complexity,
263
+ extensions: extensions,
258
264
  }
259
265
  end
260
266
 
@@ -308,6 +314,18 @@ module GraphQL
308
314
  inherited_lookups.merge(own_arguments_loads_as_type)
309
315
  end
310
316
 
317
+ # Registers new extension
318
+ # @param extension [Class] Extension class
319
+ # @param options [Hash] Optional extension options
320
+ def extension(extension, **options)
321
+ extensions << {extension => options}
322
+ end
323
+
324
+ # @api private
325
+ def extensions
326
+ @extensions ||= []
327
+ end
328
+
311
329
  private
312
330
 
313
331
  def own_arguments_loads_as_type