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
@@ -21,6 +21,7 @@ def initialize(query_string, filename:, tracer: Tracing::NullTracer)
21
21
  @query_string = query_string
22
22
  @filename = filename
23
23
  @tracer = tracer
24
+ @reused_next_token = [nil, nil]
24
25
  end
25
26
 
26
27
  def parse_document
@@ -51,7 +52,9 @@ def next_token
51
52
  if lexer_token.nil?
52
53
  nil
53
54
  else
54
- [lexer_token.name, lexer_token]
55
+ @reused_next_token[0] = lexer_token.name
56
+ @reused_next_token[1] = lexer_token
57
+ @reused_next_token
55
58
  end
56
59
  end
57
60
 
@@ -442,6 +442,7 @@ def initialize(query_string, filename:, tracer: Tracing::NullTracer)
442
442
  @query_string = query_string
443
443
  @filename = filename
444
444
  @tracer = tracer
445
+ @reused_next_token = [nil, nil]
445
446
  end
446
447
 
447
448
  def parse_document
@@ -472,7 +473,9 @@ def next_token
472
473
  if lexer_token.nil?
473
474
  nil
474
475
  else
475
- [lexer_token.name, lexer_token]
476
+ @reused_next_token[0] = lexer_token.name
477
+ @reused_next_token[1] = lexer_token
478
+ @reused_next_token
476
479
  end
477
480
  end
478
481
 
@@ -14,7 +14,7 @@ module GraphQL
14
14
  attr_reader :value
15
15
  attr_reader :prev_token, :line, :col
16
16
 
17
- def initialize(value:, name:, line:, col:, prev_token:)
17
+ def initialize(name, value, line, col, prev_token)
18
18
  @name = name
19
19
  @value = -value
20
20
  @line = line
@@ -68,7 +68,6 @@ module GraphQL
68
68
 
69
69
  limited_nodes = limited_nodes.first(first) if first
70
70
  limited_nodes = limited_nodes.last(last) if last
71
- limited_nodes = limited_nodes.first(max_page_size) if max_page_size && !first && !last
72
71
 
73
72
  limited_nodes
74
73
  end
@@ -26,7 +26,18 @@ module GraphQL
26
26
  # @return [GraphQL::Query::Context]
27
27
  attr_accessor :context
28
28
 
29
- attr_accessor :before, :after
29
+ # Raw access to client-provided values. (`max_page_size` not applied to first or last.)
30
+ attr_accessor :before_value, :after_value, :first_value, :last_value
31
+
32
+ # @return [String, nil] the client-provided cursor
33
+ def before
34
+ @before_value
35
+ end
36
+
37
+ # @return [String, nil] the client-provided cursor
38
+ def after
39
+ @after_value
40
+ end
30
41
 
31
42
  # @param items [Object] some unpaginated collection item, like an `Array` or `ActiveRecord::Relation`
32
43
  # @param context [Query::Context]
@@ -34,13 +45,14 @@ module GraphQL
34
45
  # @param after [String, nil] A cursor for pagination, if the client provided one
35
46
  # @param last [Integer, nil] Limit parameter from the client, if provided
36
47
  # @param before [String, nil] A cursor for pagination, if the client provided one.
48
+ # @param max_page_size [Integer, nil] A configured value to cap the result size. Applied as `first` if neither first or last are given.
37
49
  def initialize(items, context: nil, first: nil, after: nil, max_page_size: nil, last: nil, before: nil)
38
50
  @items = items
39
51
  @context = context
40
- @first = first
41
- @after = after
42
- @last = last
43
- @before = before
52
+ @first_value = first
53
+ @after_value = after
54
+ @last_value = last
55
+ @before_value = before
44
56
  @max_page_size = max_page_size
45
57
  end
46
58
 
@@ -50,15 +62,24 @@ module GraphQL
50
62
  end
51
63
 
52
64
  attr_writer :first
53
- # @return [Integer, nil] a clamped `first` value. (The underlying instance variable doesn't have limits on it)
65
+ # @return [Integer, nil]
66
+ # A clamped `first` value.
67
+ # (The underlying instance variable doesn't have limits on it.)
68
+ # If neither `first` nor `last` is given, but `max_page_size` is present, max_page_size is used for first.
54
69
  def first
55
- limit_pagination_argument(@first, max_page_size)
70
+ @first ||= begin
71
+ capped = limit_pagination_argument(@first_value, max_page_size)
72
+ if capped.nil? && last.nil?
73
+ capped = max_page_size
74
+ end
75
+ capped
76
+ end
56
77
  end
57
78
 
58
79
  attr_writer :last
59
- # @return [Integer, nil] a clamped `last` value. (The underlying instance variable doesn't have limits on it)
80
+ # @return [Integer, nil] A clamped `last` value. (The underlying instance variable doesn't have limits on it)
60
81
  def last
61
- limit_pagination_argument(@last, max_page_size)
82
+ @last ||= limit_pagination_argument(@last_value, max_page_size)
62
83
  end
63
84
 
64
85
  # @return [Array<Edge>] {nodes}, but wrapped with Edge instances
@@ -66,7 +87,7 @@ module GraphQL
66
87
  @edges ||= nodes.map { |n| self.class.edge_class.new(n, self) }
67
88
  end
68
89
 
69
- # @return [Array<Object>] A slice of {items}, constrained by {@first}/{@after}/{@last}/{@before}
90
+ # @return [Array<Object>] A slice of {items}, constrained by {@first_value}/{@after_value}/{@last_value}/{@before_value}
70
91
  def nodes
71
92
  raise PaginationImplementationMissingError, "Implement #{self.class}#nodes to paginate `@items`"
72
93
  end
@@ -29,8 +29,13 @@ module GraphQL
29
29
  end
30
30
 
31
31
  def self.use(schema_defn)
32
- [schema_defn.target, schema_defn.target.class].each do |schema|
33
- schema.connections = self.new
32
+ if schema_defn.is_a?(Class)
33
+ schema_defn.connections = self.new
34
+ else
35
+ # Unwrap a `.define` object
36
+ schema_defn = schema_defn.target
37
+ schema_defn.connections = self.new
38
+ schema_defn.class.connections = schema_defn.connections
34
39
  end
35
40
  end
36
41
 
@@ -53,6 +53,7 @@ module GraphQL
53
53
  raise "#{self.class}#null_relation(relation) must return an empty relation for a #{relation.class} (#{relation.inspect})"
54
54
  end
55
55
 
56
+ # @return [Integer]
56
57
  def offset_from_cursor(cursor)
57
58
  decode(cursor).to_i
58
59
  end
@@ -130,13 +131,6 @@ module GraphQL
130
131
  end
131
132
  end
132
133
 
133
- # Apply max page size if nothing else was applied
134
- if max_page_size && !first && !last
135
- if relation_limit(paginated_nodes).nil? || relation_limit(paginated_nodes) > max_page_size
136
- paginated_nodes = set_limit(paginated_nodes, max_page_size)
137
- end
138
- end
139
-
140
134
  @has_next_page = !!(
141
135
  (before_offset && before_offset > 0) ||
142
136
  (first && sliced_nodes_count > first)
@@ -78,12 +78,18 @@ module GraphQL
78
78
  # @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
79
79
  # @param except [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns truthy
80
80
  # @param only [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns false
81
- def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, except: nil, only: nil)
81
+ def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, except: nil, only: nil, warden: nil)
82
82
  # Even if `variables: nil` is passed, use an empty hash for simpler logic
83
83
  variables ||= {}
84
+
85
+ # Use the `.graphql_definition` here which will return legacy types instead of classes
86
+ if schema.is_a?(Class) && !schema.interpreter?
87
+ schema = schema.graphql_definition
88
+ end
84
89
  @schema = schema
85
90
  @filter = schema.default_filter.merge(except: except, only: only)
86
91
  @context = schema.context_class.new(query: self, object: root_value, values: context)
92
+ @warden = warden
87
93
  @subscription_topic = subscription_topic
88
94
  @root_value = root_value
89
95
  @fragments = nil
@@ -157,7 +163,7 @@ module GraphQL
157
163
  @lookahead ||= begin
158
164
  ast_node = selected_operation
159
165
  root_type = warden.root_type_for_operation(ast_node.operation_type || "query")
160
- root_type = root_type.metadata[:type_class] || raise("Invariant: `lookahead` only works with class-based types")
166
+ root_type = root_type.type_class || raise("Invariant: `lookahead` only works with class-based types")
161
167
  GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node])
162
168
  end
163
169
  end
@@ -314,8 +320,7 @@ module GraphQL
314
320
 
315
321
  def prepare_ast
316
322
  @prepared_ast = true
317
- @warden = GraphQL::Schema::Warden.new(@filter, schema: @schema, context: @context)
318
-
323
+ @warden ||= GraphQL::Schema::Warden.new(@filter, schema: @schema, context: @context)
319
324
  parse_error = nil
320
325
  @document ||= begin
321
326
  if query_string
@@ -88,6 +88,10 @@ module GraphQL
88
88
 
89
89
  def_delegators :to_h, :keys, :values, :each, :any?
90
90
 
91
+ def prepare
92
+ self
93
+ end
94
+
91
95
  # Access each key, value and type for the arguments in this set.
92
96
  # @yield [argument_value] The {ArgumentValue} for each argument
93
97
  # @yieldparam argument_value [ArgumentValue]
@@ -119,6 +123,8 @@ module GraphQL
119
123
  ruby_kwargs
120
124
  end
121
125
 
126
+ alias :to_hash :to_kwargs
127
+
122
128
  private
123
129
 
124
130
  class ArgumentValue
@@ -151,7 +157,8 @@ module GraphQL
151
157
  wrap_value(value, arg_defn_type.of_type, context)
152
158
  when GraphQL::InputObjectType
153
159
  if value.is_a?(Hash)
154
- arg_defn_type.arguments_class.new(value, context: context, defaults_used: Set.new)
160
+ result = arg_defn_type.arguments_class.new(value, context: context, defaults_used: Set.new)
161
+ result.prepare
155
162
  else
156
163
  value
157
164
  end
@@ -127,7 +127,8 @@ module GraphQL
127
127
  ruby_kwargs
128
128
  end
129
129
  else
130
- argument_owner.arguments_class.new(values_hash, context: context, defaults_used: defaults_used)
130
+ result = argument_owner.arguments_class.new(values_hash, context: context, defaults_used: defaults_used)
131
+ result.prepare
131
132
  end
132
133
  end
133
134
  end
@@ -35,7 +35,11 @@ module GraphQL
35
35
  if validation_result.valid?
36
36
  if value_was_provided
37
37
  # Add the variable if a value was provided
38
- memo[variable_name] = variable_type.coerce_input(provided_value, ctx)
38
+ memo[variable_name] = if provided_value.nil?
39
+ nil
40
+ else
41
+ variable_type.coerce_input(provided_value, ctx)
42
+ end
39
43
  elsif default_value != nil
40
44
  # Add the variable if it wasn't provided but it has a default value (including `null`)
41
45
  memo[variable_name] = GraphQL::Query::LiteralInput.coerce(variable_type, default_value, self)
@@ -143,7 +143,7 @@ module GraphQL
143
143
 
144
144
  # An opaque operation which returns a connection-specific cursor.
145
145
  def cursor_from_node(object)
146
- raise NotImplementedError, "must return a cursor for this object/connection pair"
146
+ raise GraphQL::RequiredImplementationMissingError, "must return a cursor for this object/connection pair"
147
147
  end
148
148
 
149
149
  def inspect
@@ -165,11 +165,11 @@ module GraphQL
165
165
  end
166
166
 
167
167
  def paged_nodes
168
- raise NotImplementedError, "must return nodes for this connection after paging"
168
+ raise GraphQL::RequiredImplementationMissingError, "must return nodes for this connection after paging"
169
169
  end
170
170
 
171
171
  def sliced_nodes
172
- raise NotImplementedError, "must return all nodes for this connection after chopping off first and last"
172
+ raise GraphQL::RequiredImplementationMissingError, "must return all nodes for this connection after chopping off first and last"
173
173
  end
174
174
  end
175
175
  end
@@ -25,12 +25,16 @@ module GraphQL
25
25
 
26
26
  def has_next_page
27
27
  if first
28
- paged_nodes.length >= first && sliced_nodes_count > first
29
- elsif GraphQL::Relay::ConnectionType.bidirectional_pagination && last
30
- sliced_nodes_count >= last
31
- else
32
- false
28
+ if defined?(ActiveRecord::Relation) && nodes.is_a?(ActiveRecord::Relation)
29
+ initial_offset = after ? offset_from_cursor(after) : 0
30
+ return paged_nodes.length >= first && nodes.offset(first + initial_offset).exists?
31
+ end
32
+ return paged_nodes.length >= first && sliced_nodes_count > first
33
+ end
34
+ if GraphQL::Relay::ConnectionType.bidirectional_pagination && last
35
+ return sliced_nodes_count >= last
33
36
  end
37
+ false
34
38
  end
35
39
 
36
40
  def has_previous_page
@@ -36,6 +36,7 @@ require "graphql/schema/scalar"
36
36
  require "graphql/schema/object"
37
37
  require "graphql/schema/union"
38
38
  require "graphql/schema/directive"
39
+ require "graphql/schema/directive/deprecated"
39
40
  require "graphql/schema/directive/include"
40
41
  require "graphql/schema/directive/skip"
41
42
  require "graphql/schema/directive/feature"
@@ -54,7 +55,6 @@ module GraphQL
54
55
  # - types for exposing your application
55
56
  # - query analyzers for assessing incoming queries (including max depth & max complexity restrictions)
56
57
  # - execution strategies for running incoming queries
57
- # - middleware for interacting with execution
58
58
  #
59
59
  # Schemas start with root types, {Schema#query}, {Schema#mutation} and {Schema#subscription}.
60
60
  # The schema will traverse the tree of fields & types, using those as starting points.
@@ -66,14 +66,10 @@ module GraphQL
66
66
  # Schemas can specify how queries should be executed against them.
67
67
  # `query_execution_strategy`, `mutation_execution_strategy` and `subscription_execution_strategy`
68
68
  # each apply to corresponding root types.
69
- #
70
- # A schema accepts a `Relay::GlobalNodeIdentification` instance for use with Relay IDs.
71
- #
69
+ # #
72
70
  # @example defining a schema
73
- # MySchema = GraphQL::Schema.define do
71
+ # class MySchema < GraphQL::Schema
74
72
  # query QueryType
75
- # middleware PermissionMiddleware
76
- # rescue_from(ActiveRecord::RecordNotFound) { "Not found" }
77
73
  # # If types are only connected by way of interfaces, they must be added here
78
74
  # orphan_types ImageType, AudioType
79
75
  # end
@@ -85,6 +81,14 @@ module GraphQL
85
81
  include GraphQL::Define::InstanceDefinable
86
82
  extend GraphQL::Schema::FindInheritedValue
87
83
 
84
+ class UnresolvedLateBoundTypeError < GraphQL::Error
85
+ attr_reader :type
86
+ def initialize(type:)
87
+ @type = type
88
+ super("Late bound type was never found: #{type.inspect}")
89
+ end
90
+ end
91
+
88
92
  accepts_definitions \
89
93
  :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
90
94
  :max_depth, :max_complexity, :default_max_page_size,
@@ -99,7 +103,7 @@ module GraphQL
99
103
  mutation: ->(schema, t) { schema.mutation = t.respond_to?(:graphql_definition) ? t.graphql_definition : t },
100
104
  subscription: ->(schema, t) { schema.subscription = t.respond_to?(:graphql_definition) ? t.graphql_definition : t },
101
105
  disable_introspection_entry_points: ->(schema) { schema.disable_introspection_entry_points = true },
102
- directives: ->(schema, directives) { schema.directives = directives.reduce({}) { |m, d| m[d.name] = d; m } },
106
+ directives: ->(schema, directives) { schema.directives = directives.reduce({}) { |m, d| m[d.graphql_name] = d; m } },
103
107
  directive: ->(schema, directive) { schema.directives[directive.graphql_name] = directive },
104
108
  instrument: ->(schema, type, instrumenter, after_built_ins: false) {
105
109
  if type == :field && after_built_ins
@@ -154,6 +158,10 @@ module GraphQL
154
158
  # [Boolean] True if this object disables the introspection entry point fields
155
159
  attr_accessor :disable_introspection_entry_points
156
160
 
161
+ def disable_introspection_entry_points?
162
+ !!@disable_introspection_entry_points
163
+ end
164
+
157
165
  class << self
158
166
  attr_writer :default_execution_strategy
159
167
  end
@@ -167,8 +175,6 @@ module GraphQL
167
175
  attr_reader :tracers
168
176
 
169
177
  DYNAMIC_FIELDS = ["__type", "__typename", "__schema"].freeze
170
- EMPTY_ARRAY = [].freeze
171
- EMPTY_HASH = {}.freeze
172
178
 
173
179
  attr_reader :static_validator, :object_from_id_proc, :id_from_object_proc, :resolve_type_proc
174
180
 
@@ -176,7 +182,10 @@ module GraphQL
176
182
  @tracers = []
177
183
  @definition_error = nil
178
184
  @orphan_types = []
179
- @directives = self.class.default_directives
185
+ @directives = {}
186
+ self.class.default_directives.each do |name, dir|
187
+ @directives[name] = dir.graphql_definition
188
+ end
180
189
  @static_validator = GraphQL::StaticValidation::Validator.new(schema: self)
181
190
  @middleware = MiddlewareChain.new(final_step: GraphQL::Execution::Execute::FieldResolveStep)
182
191
  @query_analyzers = []
@@ -282,13 +291,13 @@ module GraphQL
282
291
  ensure_defined
283
292
  # Assert that all necessary configs are present:
284
293
  validation_error = Validation.validate(self)
285
- validation_error && raise(NotImplementedError, validation_error)
294
+ validation_error && raise(GraphQL::RequiredImplementationMissingError, validation_error)
286
295
  rebuild_artifacts
287
296
 
288
297
  @definition_error = nil
289
298
  nil
290
299
  rescue StandardError => err
291
- if @raise_definition_error || err.is_a?(CyclicalDefinitionError)
300
+ if @raise_definition_error || err.is_a?(CyclicalDefinitionError) || err.is_a?(GraphQL::RequiredImplementationMissingError)
292
301
  raise
293
302
  else
294
303
  # Raise this error _later_ to avoid messing with Rails constant loading
@@ -325,6 +334,10 @@ module GraphQL
325
334
  end
326
335
  end
327
336
 
337
+ def get_type(type_name)
338
+ @types[type_name]
339
+ end
340
+
328
341
  # @api private
329
342
  def introspection_system
330
343
  @introspection_system ||= begin
@@ -336,9 +349,13 @@ module GraphQL
336
349
  # Returns a list of Arguments and Fields referencing a certain type
337
350
  # @param type_name [String]
338
351
  # @return [Hash]
339
- def references_to(type_name)
352
+ def references_to(type_name = nil)
340
353
  rebuild_artifacts unless defined?(@type_reference_map)
341
- @type_reference_map.fetch(type_name, [])
354
+ if type_name
355
+ @type_reference_map.fetch(type_name, [])
356
+ else
357
+ @type_reference_map
358
+ end
342
359
  end
343
360
 
344
361
  # Returns a list of Union types in which a type is a member
@@ -416,8 +433,8 @@ module GraphQL
416
433
  def get_field(parent_type, field_name)
417
434
  with_definition_error_check do
418
435
  parent_type_name = case parent_type
419
- when GraphQL::BaseType
420
- parent_type.name
436
+ when GraphQL::BaseType, Class, Module
437
+ parent_type.graphql_name
421
438
  when String
422
439
  parent_type
423
440
  else
@@ -444,7 +461,7 @@ module GraphQL
444
461
  end
445
462
 
446
463
  def type_from_ast(ast_node, context:)
447
- GraphQL::Schema::TypeExpression.build_type(self.types, ast_node)
464
+ GraphQL::Schema::TypeExpression.build_type(self, ast_node)
448
465
  end
449
466
 
450
467
  # @see [GraphQL::Schema::Warden] Restricted access to members of a schema
@@ -493,7 +510,7 @@ module GraphQL
493
510
  def resolve_type(type, object, ctx = :__undefined__)
494
511
  check_resolved_type(type, object, ctx) do |ok_type, ok_object, ok_ctx|
495
512
  if @resolve_type_proc.nil?
496
- raise(NotImplementedError, "Can't determine GraphQL type for: #{ok_object.inspect}, define `resolve_type (type, obj, ctx) -> { ... }` inside `Schema.define`.")
513
+ raise(GraphQL::RequiredImplementationMissingError, "Can't determine GraphQL type for: #{ok_object.inspect}, define `resolve_type (type, obj, ctx) -> { ... }` inside `Schema.define`.")
497
514
  end
498
515
  @resolve_type_proc.call(ok_type, ok_object, ok_ctx)
499
516
  end
@@ -554,7 +571,7 @@ module GraphQL
554
571
  # @return [Any] The application object identified by `id`
555
572
  def object_from_id(id, ctx)
556
573
  if @object_from_id_proc.nil?
557
- raise(NotImplementedError, "Can't fetch an object for id \"#{id}\" because the schema's `object_from_id (id, ctx) -> { ... }` function is not defined")
574
+ raise(GraphQL::RequiredImplementationMissingError, "Can't fetch an object for id \"#{id}\" because the schema's `object_from_id (id, ctx) -> { ... }` function is not defined")
558
575
  else
559
576
  @object_from_id_proc.call(id, ctx)
560
577
  end
@@ -596,9 +613,40 @@ module GraphQL
596
613
 
597
614
  # Can't delegate to `class`
598
615
  alias :_schema_class :class
599
- def_delegators :_schema_class, :visible?, :accessible?, :authorized?, :unauthorized_object, :unauthorized_field, :inaccessible_fields
616
+ def_delegators :_schema_class, :unauthorized_object, :unauthorized_field, :inaccessible_fields
600
617
  def_delegators :_schema_class, :directive
601
618
 
619
+
620
+ # Given this schema member, find the class-based definition object
621
+ # whose `method_name` should be treated as an application hook
622
+ # @see {.visible?}
623
+ # @see {.accessible?}
624
+ def call_on_type_class(member, method_name, context, default:)
625
+ member = if member.respond_to?(:type_class)
626
+ member.type_class
627
+ else
628
+ member
629
+ end
630
+
631
+ if member.respond_to?(:relay_node_type) && (t = member.relay_node_type)
632
+ member = t
633
+ end
634
+
635
+ if member.respond_to?(method_name)
636
+ member.public_send(method_name, context)
637
+ else
638
+ default
639
+ end
640
+ end
641
+
642
+ def visible?(member, context)
643
+ call_on_type_class(member, :visible?, context, default: true)
644
+ end
645
+
646
+ def accessible?(member, context)
647
+ call_on_type_class(member, :accessible?, context, default: true)
648
+ end
649
+
602
650
  # A function to call when {#execute} receives an invalid query string
603
651
  #
604
652
  # @see {DefaultParseError} is the default behavior.
@@ -621,7 +669,7 @@ module GraphQL
621
669
  # @return [String] a unique identifier for `object` which clients can use to refetch it
622
670
  def id_from_object(object, type, ctx)
623
671
  if @id_from_object_proc.nil?
624
- raise(NotImplementedError, "Can't generate an ID for #{object.inspect} of type #{type}, schema's `id_from_object` must be defined")
672
+ raise(GraphQL::RequiredImplementationMissingError, "Can't generate an ID for #{object.inspect} of type #{type}, schema's `id_from_object` must be defined")
625
673
  else
626
674
  @id_from_object_proc.call(object, type, ctx)
627
675
  end
@@ -643,15 +691,17 @@ module GraphQL
643
691
  # @param definition_or_path [String] A schema definition string, or a path to a file containing the definition
644
692
  # @param default_resolve [<#call(type, field, obj, args, ctx)>] A callable for handling field resolution
645
693
  # @param parser [Object] An object for handling definition string parsing (must respond to `parse`)
646
- # @return [GraphQL::Schema] the schema described by `document`
647
- def self.from_definition(definition_or_path, default_resolve: BuildFromDefinition::DefaultResolve, parser: BuildFromDefinition::DefaultParser)
694
+ # @param using [Hash] Plugins to attach to the created schema with `use(key, value)`
695
+ # @param interpreter [Boolean] If false, the legacy {Execution::Execute} runtime will be used
696
+ # @return [Class] the schema described by `document`
697
+ def self.from_definition(definition_or_path, default_resolve: BuildFromDefinition::DefaultResolve, interpreter: true, parser: BuildFromDefinition::DefaultParser, using: {})
648
698
  # If the file ends in `.graphql`, treat it like a filepath
649
699
  definition = if definition_or_path.end_with?(".graphql")
650
700
  File.read(definition_or_path)
651
701
  else
652
702
  definition_or_path
653
703
  end
654
- GraphQL::Schema::BuildFromDefinition.from_definition(definition, default_resolve: default_resolve, parser: parser)
704
+ GraphQL::Schema::BuildFromDefinition.from_definition(definition, default_resolve: default_resolve, parser: parser, using: using, interpreter: interpreter)
655
705
  end
656
706
 
657
707
  # Error that is raised when [#Schema#from_definition] is passed an invalid schema definition string.
@@ -711,38 +761,83 @@ module GraphQL
711
761
  # - Delegate to that instance
712
762
  # Eventually, the methods will be moved into this class, removing the need for the singleton.
713
763
  def_delegators :graphql_definition,
714
- # Schema structure
715
- :as_json, :to_json, :to_document, :to_definition,
716
764
  # Execution
717
- :execute, :multiplex,
718
- :static_validator, :introspection_system,
719
- :query_analyzers, :tracers, :instrumenters,
720
765
  :execution_strategy_for_operation,
721
- :validate, :multiplex_analyzers, :lazy?, :lazy_method_name, :after_lazy, :sync_lazy,
766
+ :validate, :multiplex_analyzers,
722
767
  # Configuration
723
- :analysis_engine, :analysis_engine=, :using_ast_analysis?, :interpreter?,
724
- :max_complexity=, :max_depth=,
725
- :error_bubbling=,
726
- :metadata,
727
- :default_mask,
728
- :default_filter, :redefine,
768
+ :metadata, :redefine,
729
769
  :id_from_object_proc, :object_from_id_proc,
730
770
  :id_from_object=, :object_from_id=,
731
- :remove_handler,
732
- # Members
733
- :types, :get_fields, :find,
734
- :root_type_for_operation,
735
- :subscriptions,
736
- :union_memberships,
737
- :get_field, :root_types, :references_to, :type_from_ast,
738
- :possible_types,
739
- :disable_introspection_entry_points=
771
+ :remove_handler
772
+
773
+ # @return [GraphQL::Subscriptions]
774
+ attr_accessor :subscriptions
775
+
776
+ # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}.
777
+ # @see {#as_json}
778
+ # @return [String]
779
+ def to_json(*args)
780
+ JSON.pretty_generate(as_json(*args))
781
+ end
782
+
783
+ # Return the Hash response of {Introspection::INTROSPECTION_QUERY}.
784
+ # @param context [Hash]
785
+ # @param only [<#call(member, ctx)>]
786
+ # @param except [<#call(member, ctx)>]
787
+ # @return [Hash] GraphQL result
788
+ def as_json(only: nil, except: nil, context: {})
789
+ execute(Introspection::INTROSPECTION_QUERY, only: only, except: except, context: context).to_h
790
+ end
791
+
792
+ # Return the GraphQL IDL for the schema
793
+ # @param context [Hash]
794
+ # @param only [<#call(member, ctx)>]
795
+ # @param except [<#call(member, ctx)>]
796
+ # @return [String]
797
+ def to_definition(only: nil, except: nil, context: {})
798
+ GraphQL::Schema::Printer.print_schema(self, only: only, except: except, context: context)
799
+ end
800
+
801
+ # Return the GraphQL::Language::Document IDL AST for the schema
802
+ # @return [GraphQL::Language::Document]
803
+ def to_document
804
+ GraphQL::Language::DocumentFromSchemaDefinition.new(self).document
805
+ end
806
+
807
+ def find(path)
808
+ if !@finder
809
+ @find_cache = {}
810
+ @finder ||= GraphQL::Schema::Finder.new(self)
811
+ end
812
+ @find_cache[path] ||= @finder.find(path)
813
+ end
740
814
 
741
815
  def graphql_definition
742
816
  @graphql_definition ||= to_graphql
743
817
  end
744
818
 
819
+ def default_filter
820
+ GraphQL::Filter.new(except: default_mask)
821
+ end
822
+
823
+ def default_mask(new_mask = nil)
824
+ if new_mask
825
+ @own_default_mask = new_mask
826
+ else
827
+ @own_default_mask || find_inherited_value(:default_mask, Schema::NullMask)
828
+ end
829
+ end
830
+
831
+ def static_validator
832
+ GraphQL::StaticValidation::Validator.new(schema: self)
833
+ end
834
+
745
835
  def use(plugin, options = {})
836
+ if options.any?
837
+ plugin.use(self, options)
838
+ else
839
+ plugin.use(self)
840
+ end
746
841
  own_plugins << [plugin, options]
747
842
  end
748
843
 
@@ -753,14 +848,14 @@ module GraphQL
753
848
  def to_graphql
754
849
  schema_defn = self.new
755
850
  schema_defn.raise_definition_error = true
756
- schema_defn.query = query
757
- schema_defn.mutation = mutation
758
- schema_defn.subscription = subscription
851
+ schema_defn.query = query && query.graphql_definition
852
+ schema_defn.mutation = mutation && mutation.graphql_definition
853
+ schema_defn.subscription = subscription && subscription.graphql_definition
759
854
  schema_defn.max_complexity = max_complexity
760
855
  schema_defn.error_bubbling = error_bubbling
761
856
  schema_defn.max_depth = max_depth
762
857
  schema_defn.default_max_page_size = default_max_page_size
763
- schema_defn.orphan_types = orphan_types
858
+ schema_defn.orphan_types = orphan_types.map(&:graphql_definition)
764
859
  schema_defn.disable_introspection_entry_points = disable_introspection_entry_points?
765
860
 
766
861
  prepped_dirs = {}
@@ -781,33 +876,24 @@ module GraphQL
781
876
  schema_defn.query_execution_strategy = query_execution_strategy
782
877
  schema_defn.mutation_execution_strategy = mutation_execution_strategy
783
878
  schema_defn.subscription_execution_strategy = subscription_execution_strategy
784
- all_instrumenters.each do |step, insts|
879
+ schema_defn.default_mask = default_mask
880
+ instrumenters.each do |step, insts|
785
881
  insts.each do |inst|
786
882
  schema_defn.instrumenters[step] << inst
787
883
  end
788
884
  end
789
- lazy_classes.each do |lazy_class, value_method|
885
+
886
+ lazy_methods.each do |lazy_class, value_method|
790
887
  schema_defn.lazy_methods.set(lazy_class, value_method)
791
888
  end
889
+
792
890
  rescues.each do |err_class, handler|
793
891
  schema_defn.rescue_from(err_class, &handler)
794
892
  end
795
893
 
796
- if plugins.any?
797
- schema_plugins = plugins
798
- # TODO don't depend on .define
799
- schema_defn = schema_defn.redefine do
800
- schema_plugins.each do |plugin, options|
801
- if options.any?
802
- use(plugin, **options)
803
- else
804
- use(plugin)
805
- end
806
- end
807
- end
808
- end
809
- # Do this after `plugins` since Interpreter is a plugin
810
- if schema_defn.query_execution_strategy != GraphQL::Execution::Interpreter
894
+ schema_defn.subscriptions ||= self.subscriptions
895
+
896
+ if !schema_defn.interpreter?
811
897
  schema_defn.instrumenters[:query] << GraphQL::Schema::Member::Instrumentation
812
898
  end
813
899
  schema_defn.send(:rebuild_artifacts)
@@ -815,44 +901,196 @@ module GraphQL
815
901
  schema_defn
816
902
  end
817
903
 
904
+ # Build a map of `{ name => type }` and return it
905
+ # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
906
+ # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
907
+ def types
908
+ non_introspection_types.merge(introspection_system.types)
909
+ end
910
+
911
+ # @param type_name [String]
912
+ # @return [Module, nil] A type, or nil if there's no type called `type_name`
913
+ def get_type(type_name)
914
+ own_types[type_name] ||
915
+ introspection_system.types[type_name] ||
916
+ find_inherited_value(:types, EMPTY_HASH)[type_name]
917
+ end
918
+
818
919
  # @return [GraphQL::Pagination::Connections] if installed
819
920
  attr_accessor :connections
820
921
 
922
+ def new_connections?
923
+ !!connections
924
+ end
925
+
821
926
  def query(new_query_object = nil)
822
927
  if new_query_object
823
- @query_object = new_query_object
928
+ if @query_object
929
+ raise GraphQL::Error, "Second definition of `query(...)` (#{new_query_object.inspect}) is invalid, already configured with #{@query_object.inspect}"
930
+ else
931
+ @query_object = new_query_object
932
+ add_type_and_traverse(new_query_object, root: true)
933
+ nil
934
+ end
824
935
  else
825
- query_object = @query_object || find_inherited_value(:query)
826
- query_object.respond_to?(:graphql_definition) ? query_object.graphql_definition : query_object
936
+ @query_object || find_inherited_value(:query)
827
937
  end
828
938
  end
829
939
 
830
940
  def mutation(new_mutation_object = nil)
831
941
  if new_mutation_object
832
- @mutation_object = new_mutation_object
942
+ if @mutation_object
943
+ raise GraphQL::Error, "Second definition of `mutation(...)` (#{new_mutation_object.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
944
+ else
945
+ @mutation_object = new_mutation_object
946
+ add_type_and_traverse(new_mutation_object, root: true)
947
+ nil
948
+ end
833
949
  else
834
- mutation_object = @mutation_object || find_inherited_value(:mutation)
835
- mutation_object.respond_to?(:graphql_definition) ? mutation_object.graphql_definition : mutation_object
950
+ @mutation_object || find_inherited_value(:mutation)
836
951
  end
837
952
  end
838
953
 
839
954
  def subscription(new_subscription_object = nil)
840
955
  if new_subscription_object
841
- @subscription_object = new_subscription_object
956
+ if @subscription_object
957
+ raise GraphQL::Error, "Second definition of `subscription(...)` (#{new_subscription_object.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
958
+ else
959
+ @subscription_object = new_subscription_object
960
+ add_type_and_traverse(new_subscription_object, root: true)
961
+ nil
962
+ end
963
+ else
964
+ @subscription_object || find_inherited_value(:subscription)
965
+ end
966
+ end
967
+
968
+ # @see [GraphQL::Schema::Warden] Resticted access to root types
969
+ # @return [GraphQL::ObjectType, nil]
970
+ def root_type_for_operation(operation)
971
+ case operation
972
+ when "query"
973
+ query
974
+ when "mutation"
975
+ mutation
976
+ when "subscription"
977
+ subscription
978
+ else
979
+ raise ArgumentError, "unknown operation type: #{operation}"
980
+ end
981
+ end
982
+
983
+ def root_types
984
+ @root_types
985
+ end
986
+
987
+ # @param type [Module] The type definition whose possible types you want to see
988
+ # @return [Hash<String, Module>] All possible types, if no `type` is given.
989
+ # @return [Array<Module>] Possible types for `type`, if it's given.
990
+ def possible_types(type = nil)
991
+ if type
992
+ own_possible_types[type.graphql_name] ||
993
+ find_inherited_value(:possible_types, EMPTY_HASH)[type.graphql_name] ||
994
+ EMPTY_ARRAY
995
+ else
996
+ find_inherited_value(:possible_types, EMPTY_HASH)
997
+ .merge(own_possible_types)
998
+ .merge(introspection_system.possible_types)
999
+ end
1000
+ end
1001
+
1002
+ def union_memberships(type = nil)
1003
+ if type
1004
+ own_um = own_union_memberships.fetch(type.graphql_name, EMPTY_ARRAY)
1005
+ inherited_um = find_inherited_value(:union_memberships, EMPTY_HASH).fetch(type.graphql_name, EMPTY_ARRAY)
1006
+ own_um + inherited_um
1007
+ else
1008
+ joined_um = own_union_memberships.dup
1009
+ find_inherited_value(:union_memberhips, EMPTY_HASH).each do |k, v|
1010
+ um = joined_um[k] ||= []
1011
+ um.concat(v)
1012
+ end
1013
+ joined_um
1014
+ end
1015
+ end
1016
+
1017
+ def references_to(to_type = nil, from: nil)
1018
+ @own_references_to ||= Hash.new { |h, k| h[k] = [] }
1019
+ if to_type
1020
+ if !to_type.is_a?(String)
1021
+ to_type = to_type.graphql_name
1022
+ end
1023
+
1024
+ if from
1025
+ @own_references_to[to_type] << from
1026
+ else
1027
+ own_refs = @own_references_to[to_type]
1028
+ inherited_refs = find_inherited_value(:references_to, EMPTY_HASH)[to_type] || EMPTY_ARRAY
1029
+ own_refs + inherited_refs
1030
+ end
1031
+ else
1032
+ # `@own_references_to` can be quite large for big schemas,
1033
+ # and generally speaking, we won't inherit any values.
1034
+ # So optimize the most common case -- don't create a duplicate Hash.
1035
+ inherited_value = find_inherited_value(:references_to, EMPTY_HASH)
1036
+ if inherited_value.any?
1037
+ inherited_value.merge(@own_references_to)
1038
+ else
1039
+ @own_references_to
1040
+ end
1041
+ end
1042
+ end
1043
+
1044
+ def type_from_ast(ast_node, context: nil)
1045
+ type_owner = context ? context.warden : self
1046
+ GraphQL::Schema::TypeExpression.build_type(type_owner, ast_node)
1047
+ end
1048
+
1049
+ def get_field(type_or_name, field_name)
1050
+ parent_type = case type_or_name
1051
+ when LateBoundType
1052
+ get_type(type_or_name.name)
1053
+ when String
1054
+ get_type(type_or_name)
1055
+ when Module
1056
+ type_or_name
842
1057
  else
843
- subscription_object = @subscription_object || find_inherited_value(:subscription)
844
- subscription_object.respond_to?(:graphql_definition) ? subscription_object.graphql_definition : subscription_object
1058
+ raise ArgumentError, "unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})"
1059
+ end
1060
+
1061
+ if parent_type.kind.fields? && (field = parent_type.get_field(field_name))
1062
+ field
1063
+ elsif parent_type == query && (entry_point_field = introspection_system.entry_point(name: field_name))
1064
+ entry_point_field
1065
+ elsif (dynamic_field = introspection_system.dynamic_field(name: field_name))
1066
+ dynamic_field
1067
+ else
1068
+ nil
845
1069
  end
846
1070
  end
847
1071
 
1072
+ def get_fields(type)
1073
+ type.fields
1074
+ end
1075
+
848
1076
  def introspection(new_introspection_namespace = nil)
849
1077
  if new_introspection_namespace
850
1078
  @introspection = new_introspection_namespace
1079
+ # reset this cached value:
1080
+ @introspection_system = nil
851
1081
  else
852
1082
  @introspection || find_inherited_value(:introspection)
853
1083
  end
854
1084
  end
855
1085
 
1086
+ def introspection_system
1087
+ if !@introspection_system
1088
+ @introspection_system = Schema::IntrospectionSystem.new(self)
1089
+ @introspection_system.resolve_late_bindings
1090
+ end
1091
+ @introspection_system
1092
+ end
1093
+
856
1094
  def cursor_encoder(new_encoder = nil)
857
1095
  if new_encoder
858
1096
  @cursor_encoder = new_encoder
@@ -892,14 +1130,38 @@ module GraphQL
892
1130
  end
893
1131
  end
894
1132
 
1133
+ attr_writer :max_complexity
1134
+
895
1135
  def max_complexity(max_complexity = nil)
896
1136
  if max_complexity
897
1137
  @max_complexity = max_complexity
1138
+ elsif defined?(@max_complexity)
1139
+ @max_complexity
898
1140
  else
899
- @max_complexity || find_inherited_value(:max_complexity)
1141
+ find_inherited_value(:max_complexity)
900
1142
  end
901
1143
  end
902
1144
 
1145
+ attr_writer :analysis_engine
1146
+
1147
+ def analysis_engine
1148
+ @analysis_engine || find_inherited_value(:analysis_engine, GraphQL::Analysis)
1149
+ end
1150
+
1151
+ def using_ast_analysis?
1152
+ analysis_engine == GraphQL::Analysis::AST
1153
+ end
1154
+
1155
+ def interpreter?
1156
+ if defined?(@interpreter)
1157
+ @interpreter
1158
+ else
1159
+ find_inherited_value(:interpreter?, false)
1160
+ end
1161
+ end
1162
+
1163
+ attr_writer :interpreter
1164
+
903
1165
  def error_bubbling(new_error_bubbling = nil)
904
1166
  if !new_error_bubbling.nil?
905
1167
  @error_bubbling = new_error_bubbling
@@ -908,16 +1170,24 @@ module GraphQL
908
1170
  end
909
1171
  end
910
1172
 
1173
+ attr_writer :error_bubbling
1174
+
1175
+ attr_writer :max_depth
1176
+
911
1177
  def max_depth(new_max_depth = nil)
912
1178
  if new_max_depth
913
1179
  @max_depth = new_max_depth
1180
+ elsif defined?(@max_depth)
1181
+ @max_depth
914
1182
  else
915
- @max_depth || find_inherited_value(:max_depth)
1183
+ find_inherited_value(:max_depth)
916
1184
  end
917
1185
  end
918
1186
 
919
1187
  def disable_introspection_entry_points
920
1188
  @disable_introspection_entry_points = true
1189
+ # TODO: this clears the cache made in `def types`. But this is not a great solution.
1190
+ @introspection_system = nil
921
1191
  end
922
1192
 
923
1193
  def disable_introspection_entry_points?
@@ -930,6 +1200,9 @@ module GraphQL
930
1200
 
931
1201
  def orphan_types(*new_orphan_types)
932
1202
  if new_orphan_types.any?
1203
+ new_orphan_types = new_orphan_types.flatten
1204
+ add_type_and_traverse(new_orphan_types, root: false)
1205
+ @orphan_types = new_orphan_types
933
1206
  own_orphan_types.concat(new_orphan_types.flatten)
934
1207
  end
935
1208
 
@@ -958,32 +1231,60 @@ module GraphQL
958
1231
  end
959
1232
  end
960
1233
 
961
- def rescues
962
- find_inherited_value(:rescues, EMPTY_HASH).merge(own_rescues)
1234
+ # rubocop:disable Lint/DuplicateMethods
1235
+ module ResolveTypeWithType
1236
+ def resolve_type(type, obj, ctx)
1237
+ first_resolved_type = if type.is_a?(Module) && type.respond_to?(:resolve_type)
1238
+ type.resolve_type(obj, ctx)
1239
+ else
1240
+ super
1241
+ end
1242
+
1243
+ after_lazy(first_resolved_type) do |resolved_type|
1244
+ if resolved_type.nil? || (resolved_type.is_a?(Module) && resolved_type.respond_to?(:kind)) || resolved_type.is_a?(GraphQL::BaseType)
1245
+ resolved_type
1246
+ else
1247
+ raise ".resolve_type should return a type definition, but got #{resolved_type.inspect} (#{resolved_type.class}) from `resolve_type(#{type}, #{obj}, #{ctx})`"
1248
+ end
1249
+ end
1250
+ end
963
1251
  end
964
1252
 
965
1253
  def resolve_type(type, obj, ctx)
966
1254
  if type.kind.object?
967
1255
  type
968
1256
  else
969
- raise NotImplementedError, "#{self.name}.resolve_type(type, obj, ctx) must be implemented to use Union types or Interface types (tried to resolve: #{type.name})"
1257
+ raise GraphQL::RequiredImplementationMissingError, "#{self.name}.resolve_type(type, obj, ctx) must be implemented to use Union types or Interface types (tried to resolve: #{type.name})"
970
1258
  end
971
1259
  end
1260
+ # rubocop:enable Lint/DuplicateMethods
1261
+
1262
+ def inherited(child_class)
1263
+ if self == GraphQL::Schema
1264
+ child_class.directives(default_directives.values)
1265
+ end
1266
+ child_class.singleton_class.prepend(ResolveTypeWithType)
1267
+ super
1268
+ end
1269
+
1270
+ def rescues
1271
+ find_inherited_value(:rescues, EMPTY_HASH).merge(own_rescues)
1272
+ end
972
1273
 
973
1274
  def object_from_id(node_id, ctx)
974
- raise NotImplementedError, "#{self.name}.object_from_id(node_id, ctx) must be implemented to load by ID (tried to load from id `#{node_id}`)"
1275
+ raise GraphQL::RequiredImplementationMissingError, "#{self.name}.object_from_id(node_id, ctx) must be implemented to load by ID (tried to load from id `#{node_id}`)"
975
1276
  end
976
1277
 
977
1278
  def id_from_object(object, type, ctx)
978
- raise NotImplementedError, "#{self.name}.id_from_object(object, type, ctx) must be implemented to create global ids (tried to create an id for `#{object.inspect}`)"
1279
+ raise GraphQL::RequiredImplementationMissingError, "#{self.name}.id_from_object(object, type, ctx) must be implemented to create global ids (tried to create an id for `#{object.inspect}`)"
979
1280
  end
980
1281
 
981
- def visible?(member, context)
982
- call_on_type_class(member, :visible?, context, default: true)
1282
+ def visible?(member, ctx)
1283
+ member.visible?(ctx)
983
1284
  end
984
1285
 
985
- def accessible?(member, context)
986
- call_on_type_class(member, :accessible?, context, default: true)
1286
+ def accessible?(member, ctx)
1287
+ member.accessible?(ctx)
987
1288
  end
988
1289
 
989
1290
  # This hook is called when a client tries to access one or more
@@ -1037,8 +1338,18 @@ module GraphQL
1037
1338
  DefaultTypeError.call(type_err, ctx)
1038
1339
  end
1039
1340
 
1341
+ # A function to call when {#execute} receives an invalid query string
1342
+ #
1343
+ # The default is to add the error to `context.errors`
1344
+ # @param err [GraphQL::ParseError] The error encountered during parsing
1345
+ # @param ctx [GraphQL::Query::Context] The context for the query where the error occurred
1346
+ # @return void
1347
+ def parse_error(parse_err, ctx)
1348
+ ctx.errors.push(parse_err)
1349
+ end
1350
+
1040
1351
  def lazy_resolve(lazy_class, value_method)
1041
- lazy_classes[lazy_class] = value_method
1352
+ lazy_methods.set(lazy_class, value_method)
1042
1353
  end
1043
1354
 
1044
1355
  def instrument(instrument_step, instrumenter, options = {})
@@ -1051,23 +1362,28 @@ module GraphQL
1051
1362
  own_instrumenters[step] << instrumenter
1052
1363
  end
1053
1364
 
1365
+ # Add several directives at once
1366
+ # @param new_directives [Class]
1054
1367
  def directives(new_directives = nil)
1055
1368
  if new_directives
1056
- new_directives.each {|d| directive(d) }
1369
+ new_directives.each { |d| directive(d) }
1057
1370
  end
1058
1371
 
1059
1372
  find_inherited_value(:directives, default_directives).merge(own_directives)
1060
1373
  end
1061
1374
 
1375
+ # Attach a single directive to this schema
1376
+ # @param new_directive [Class]
1062
1377
  def directive(new_directive)
1378
+ add_type_and_traverse(new_directive, root: false)
1063
1379
  own_directives[new_directive.graphql_name] = new_directive
1064
1380
  end
1065
1381
 
1066
1382
  def default_directives
1067
1383
  {
1068
- "include" => GraphQL::Directive::IncludeDirective,
1069
- "skip" => GraphQL::Directive::SkipDirective,
1070
- "deprecated" => GraphQL::Directive::DeprecatedDirective,
1384
+ "include" => GraphQL::Schema::Directive::Include,
1385
+ "skip" => GraphQL::Schema::Directive::Skip,
1386
+ "deprecated" => GraphQL::Schema::Directive::Deprecated,
1071
1387
  }
1072
1388
  end
1073
1389
 
@@ -1094,7 +1410,8 @@ module GraphQL
1094
1410
  if new_middleware
1095
1411
  own_middleware << new_middleware
1096
1412
  else
1097
- graphql_definition.middleware
1413
+ # TODO make sure this is cached when running a query
1414
+ MiddlewareChain.new(steps: all_middleware, final_step: GraphQL::Execution::Execute::FieldResolveStep)
1098
1415
  end
1099
1416
  end
1100
1417
 
@@ -1106,10 +1423,125 @@ module GraphQL
1106
1423
  find_inherited_value(:multiplex_analyzers, EMPTY_ARRAY) + own_multiplex_analyzers
1107
1424
  end
1108
1425
 
1426
+ # Execute a query on itself.
1427
+ # @see {Query#initialize} for arguments.
1428
+ # @return [Hash] query result, ready to be serialized as JSON
1429
+ def execute(query_str = nil, **kwargs)
1430
+ if query_str
1431
+ kwargs[:query] = query_str
1432
+ end
1433
+ # Some of the query context _should_ be passed to the multiplex, too
1434
+ multiplex_context = if (ctx = kwargs[:context])
1435
+ {
1436
+ backtrace: ctx[:backtrace],
1437
+ tracers: ctx[:tracers],
1438
+ }
1439
+ else
1440
+ {}
1441
+ end
1442
+ # Since we're running one query, don't run a multiplex-level complexity analyzer
1443
+ all_results = multiplex([kwargs], max_complexity: nil, context: multiplex_context)
1444
+ all_results[0]
1445
+ end
1446
+
1447
+ # Execute several queries on itself, concurrently.
1448
+ #
1449
+ # @example Run several queries at once
1450
+ # context = { ... }
1451
+ # queries = [
1452
+ # { query: params[:query_1], variables: params[:variables_1], context: context },
1453
+ # { query: params[:query_2], variables: params[:variables_2], context: context },
1454
+ # ]
1455
+ # results = MySchema.multiplex(queries)
1456
+ # render json: {
1457
+ # result_1: results[0],
1458
+ # result_2: results[1],
1459
+ # }
1460
+ #
1461
+ # @see {Query#initialize} for query keyword arguments
1462
+ # @see {Execution::Multiplex#run_queries} for multiplex keyword arguments
1463
+ # @param queries [Array<Hash>] Keyword arguments for each query
1464
+ # @param context [Hash] Multiplex-level context
1465
+ # @return [Array<Hash>] One result for each query in the input
1466
+ def multiplex(queries, **kwargs)
1467
+ schema = if interpreter?
1468
+ self
1469
+ else
1470
+ graphql_definition
1471
+ end
1472
+ GraphQL::Execution::Multiplex.run_all(schema, queries, **kwargs)
1473
+ end
1474
+
1475
+ def instrumenters
1476
+ inherited_instrumenters = find_inherited_value(:instrumenters) || Hash.new { |h,k| h[k] = [] }
1477
+ inherited_instrumenters.merge(own_instrumenters) do |_step, inherited, own|
1478
+ inherited + own
1479
+ end
1480
+ end
1481
+
1482
+ # Call the given block at the right time, either:
1483
+ # - Right away, if `value` is not registered with `lazy_resolve`
1484
+ # - After resolving `value`, if it's registered with `lazy_resolve` (eg, `Promise`)
1485
+ # @api private
1486
+ def after_lazy(value)
1487
+ if lazy?(value)
1488
+ GraphQL::Execution::Lazy.new do
1489
+ result = sync_lazy(value)
1490
+ # The returned result might also be lazy, so check it, too
1491
+ after_lazy(result) do |final_result|
1492
+ yield(final_result) if block_given?
1493
+ end
1494
+ end
1495
+ else
1496
+ yield(value) if block_given?
1497
+ end
1498
+ end
1499
+
1500
+ # Override this method to handle lazy objects in a custom way.
1501
+ # @param value [Object] an instance of a class registered with {.lazy_resolve}
1502
+ # @param ctx [GraphQL::Query::Context] the context for this query
1503
+ # @return [Object] A GraphQL-ready (non-lazy) object
1504
+ def sync_lazy(value)
1505
+ lazy_method = lazy_method_name(value)
1506
+ if lazy_method
1507
+ synced_value = value.public_send(lazy_method)
1508
+ sync_lazy(synced_value)
1509
+ else
1510
+ value
1511
+ end
1512
+ end
1513
+
1514
+ # @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered wtih {#lazy_resolve}.
1515
+ def lazy_method_name(obj)
1516
+ lazy_methods.get(obj)
1517
+ end
1518
+
1519
+ # @return [Boolean] True if this object should be lazily resolved
1520
+ def lazy?(obj)
1521
+ !!lazy_method_name(obj)
1522
+ end
1523
+
1109
1524
  private
1110
1525
 
1111
- def lazy_classes
1112
- @lazy_classes ||= {}
1526
+ def lazy_methods
1527
+ if !defined?(@lazy_methods)
1528
+ if inherited_map = find_inherited_value(:lazy_methods)
1529
+ # this isn't _completely_ inherited :S (Things added after `dup` won't work)
1530
+ @lazy_methods = inherited_map.dup
1531
+ else
1532
+ @lazy_methods = GraphQL::Execution::Lazy::LazyMethodMap.new
1533
+ @lazy_methods.set(GraphQL::Execution::Lazy, :value)
1534
+ end
1535
+ end
1536
+ @lazy_methods
1537
+ end
1538
+
1539
+ def own_types
1540
+ @own_types ||= {}
1541
+ end
1542
+
1543
+ def non_introspection_types
1544
+ find_inherited_value(:non_introspection_types, EMPTY_HASH).merge(own_types)
1113
1545
  end
1114
1546
 
1115
1547
  def own_plugins
@@ -1124,15 +1556,16 @@ module GraphQL
1124
1556
  @own_orphan_types ||= []
1125
1557
  end
1126
1558
 
1127
- def own_directives
1128
- @own_directives ||= {}
1559
+ def own_possible_types
1560
+ @own_possible_types ||= {}
1129
1561
  end
1130
1562
 
1131
- def all_instrumenters
1132
- inherited_instrumenters = find_inherited_value(:all_instrumenters) || Hash.new { |h,k| h[k] = [] }
1133
- inherited_instrumenters.merge(own_instrumenters) do |_step, inherited, own|
1134
- inherited + own
1135
- end
1563
+ def own_union_memberships
1564
+ @own_union_memberships ||= {}
1565
+ end
1566
+
1567
+ def own_directives
1568
+ @own_directives ||= {}
1136
1569
  end
1137
1570
 
1138
1571
  def own_instrumenters
@@ -1159,42 +1592,172 @@ module GraphQL
1159
1592
  @own_multiplex_analyzers ||= []
1160
1593
  end
1161
1594
 
1162
- # Given this schema member, find the class-based definition object
1163
- # whose `method_name` should be treated as an application hook
1164
- # @see {.visible?}
1165
- # @see {.accessible?}
1166
- # @see {.authorized?}
1167
- def call_on_type_class(member, method_name, *args, default:)
1168
- member = if member.respond_to?(:metadata) && member.metadata
1169
- member.metadata[:type_class] || member
1170
- else
1171
- member
1595
+ # @param t [Module, Array<Module>]
1596
+ # @return [void]
1597
+ def add_type_and_traverse(t, root:)
1598
+ if root
1599
+ @root_types ||= []
1600
+ @root_types << t
1172
1601
  end
1173
-
1174
- if member.respond_to?(:relay_node_type) && (t = member.relay_node_type)
1175
- member = t
1602
+ late_types = []
1603
+ new_types = Array(t)
1604
+ new_types.each { |t| add_type(t, owner: nil, late_types: late_types) }
1605
+ missed_late_types = 0
1606
+ while (late_type_vals = late_types.shift)
1607
+ type_owner, lt = late_type_vals
1608
+ if lt.is_a?(String)
1609
+ type = Member::BuildType.constantize(lt)
1610
+ # Reset the counter, since we might succeed next go-round
1611
+ missed_late_types = 0
1612
+ update_type_owner(type_owner, type)
1613
+ add_type(type, owner: type_owner, late_types: late_types)
1614
+ elsif lt.is_a?(LateBoundType)
1615
+ if (type = get_type(lt.graphql_name))
1616
+ # Reset the counter, since we might succeed next go-round
1617
+ missed_late_types = 0
1618
+ update_type_owner(type_owner, type)
1619
+ add_type(type, owner: type_owner, late_types: late_types)
1620
+ else
1621
+ missed_late_types += 1
1622
+ # Add it back to the list, maybe we'll be able to resolve it later.
1623
+ late_types << [type_owner, lt]
1624
+ if missed_late_types == late_types.size
1625
+ # We've looked at all of them and haven't resolved one.
1626
+ raise UnresolvedLateBoundTypeError.new(type: lt)
1627
+ else
1628
+ # Try the next one
1629
+ end
1630
+ end
1631
+ else
1632
+ raise ArgumentError, "Unexpected late type: #{lt.inspect}"
1633
+ end
1176
1634
  end
1635
+ nil
1636
+ end
1177
1637
 
1178
- if member.respond_to?(method_name)
1179
- member.public_send(method_name, *args)
1638
+ def update_type_owner(owner, type)
1639
+ case owner
1640
+ when Class
1641
+ if owner.kind.union?
1642
+ # It's a union with possible_types
1643
+ # Replace the item by class name
1644
+ new_possible_types = owner.possible_types.map { |t|
1645
+ if t.is_a?(String) && (t == type.name)
1646
+ # This is a match of Ruby class names, not graphql names,
1647
+ # since strings are used to refer to constants.
1648
+ type
1649
+ elsif t.is_a?(LateBoundType) && t.graphql_name == type.graphql_name
1650
+ type
1651
+ else
1652
+ t
1653
+ end
1654
+ }
1655
+ owner.possible_types(*new_possible_types)
1656
+ own_possible_types[owner.graphql_name] = owner.possible_types
1657
+ elsif type.kind.interface? && owner.kind.object?
1658
+ new_interfaces = owner.interfaces.map do |t|
1659
+ if t.is_a?(String) && t == type.graphql_name
1660
+ type
1661
+ elsif t.is_a?(LateBoundType) && t.graphql_name == type.graphql_name
1662
+ type
1663
+ else
1664
+ t
1665
+ end
1666
+ end
1667
+ owner.implements(*new_interfaces)
1668
+ end
1669
+
1670
+ when nil
1671
+ # It's a root type
1672
+ own_types[type.graphql_name] = type
1673
+ when GraphQL::Schema::Field, GraphQL::Schema::Argument
1674
+ orig_type = owner.type
1675
+ # Apply list/non-null wrapper as needed
1676
+ if orig_type.respond_to?(:of_type)
1677
+ transforms = []
1678
+ while (orig_type.respond_to?(:of_type))
1679
+ if orig_type.kind.non_null?
1680
+ transforms << :to_non_null_type
1681
+ elsif orig_type.kind.list?
1682
+ transforms << :to_list_type
1683
+ else
1684
+ raise "Invariant: :of_type isn't non-null or list"
1685
+ end
1686
+ orig_type = orig_type.of_type
1687
+ end
1688
+ transforms.reverse_each { |t| type = type.public_send(t) }
1689
+ end
1690
+ owner.type = type
1180
1691
  else
1181
- default
1692
+ raise "Unexpected update: #{owner.inspect} #{type.inspect}"
1182
1693
  end
1183
1694
  end
1184
- end
1185
1695
 
1696
+ def add_type(type, owner:, late_types:)
1697
+ if type.respond_to?(:metadata) && type.metadata.is_a?(Hash)
1698
+ type_class = type.metadata[:type_class]
1699
+ if type_class.nil?
1700
+ raise ArgumentError, "Can't add legacy type: #{type} (#{type.class})"
1701
+ else
1702
+ type = type_class
1703
+ end
1704
+ elsif type.is_a?(String) || type.is_a?(GraphQL::Schema::LateBoundType)
1705
+ late_types << [owner, type]
1706
+ return
1707
+ end
1186
1708
 
1187
- def self.inherited(child_class)
1188
- child_class.singleton_class.class_eval do
1189
- prepend(MethodWrappers)
1190
- end
1191
- end
1709
+ if owner.is_a?(Class) && owner < GraphQL::Schema::Union
1710
+ um = own_union_memberships[type.graphql_name] ||= []
1711
+ um << owner
1712
+ end
1192
1713
 
1193
- module MethodWrappers
1194
- # Wrap the user-provided resolve-type in a correctness check
1195
- def resolve_type(type, obj, ctx = :__undefined__)
1196
- graphql_definition.check_resolved_type(type, obj, ctx) do |ok_type, ok_obj, ok_ctx|
1197
- super(ok_type, ok_obj, ok_ctx)
1714
+ if (prev_type = own_types[type.graphql_name])
1715
+ if prev_type != type
1716
+ raise ArgumentError, "Conflicting type definitions for `#{type.graphql_name}`: #{prev_type} (#{prev_type.class}), #{type} #{type.class}"
1717
+ else
1718
+ # This type was already added
1719
+ end
1720
+ elsif type.is_a?(Class) && type < GraphQL::Schema::Directive
1721
+ type.arguments.each do |_name, arg|
1722
+ arg_type = arg.type.unwrap
1723
+ references_to(arg_type, from: arg)
1724
+ add_type(arg_type, owner: arg, late_types: late_types)
1725
+ end
1726
+ else
1727
+ own_types[type.graphql_name] = type
1728
+ if type.kind.fields?
1729
+ type.fields.each do |_name, field|
1730
+ field_type = field.type.unwrap
1731
+ references_to(field_type, from: field)
1732
+ add_type(field_type, owner: field, late_types: late_types)
1733
+ field.arguments.each do |_name, arg|
1734
+ arg_type = arg.type.unwrap
1735
+ references_to(arg_type, from: arg)
1736
+ add_type(arg_type, owner: arg, late_types: late_types)
1737
+ end
1738
+ end
1739
+ end
1740
+ if type.kind.input_object?
1741
+ type.arguments.each do |_name, arg|
1742
+ arg_type = arg.type.unwrap
1743
+ references_to(arg_type, from: arg)
1744
+ add_type(arg_type, owner: arg, late_types: late_types)
1745
+ end
1746
+ end
1747
+ if type.kind.union?
1748
+ own_possible_types[type.graphql_name] = type.possible_types
1749
+ type.possible_types.each do |t|
1750
+ add_type(t, owner: type, late_types: late_types)
1751
+ end
1752
+ end
1753
+ if type.kind.object?
1754
+ own_possible_types[type.graphql_name] = [type]
1755
+ type.interfaces.each do |i|
1756
+ implementers = own_possible_types[i.graphql_name] ||= []
1757
+ implementers << type
1758
+ add_type(i, owner: type, late_types: late_types)
1759
+ end
1760
+ end
1198
1761
  end
1199
1762
  end
1200
1763
  end
@@ -1217,33 +1780,16 @@ module GraphQL
1217
1780
  end
1218
1781
  end
1219
1782
 
1220
- # Override this method to handle lazy objects in a custom way.
1221
- # @param value [Object] an instance of a class registered with {.lazy_resolve}
1222
- # @param ctx [GraphQL::Query::Context] the context for this query
1223
- # @return [Object] A GraphQL-ready (non-lazy) object
1224
- def self.sync_lazy(value)
1225
- if block_given?
1226
- # This was already hit by the instance, just give it back
1227
- yield(value)
1228
- else
1229
- # This was called directly on the class, hit the instance
1230
- # which has the lazy method map
1231
- self.graphql_definition.sync_lazy(value)
1232
- end
1233
- end
1234
-
1235
1783
  # @see Schema.sync_lazy for a hook to override
1236
1784
  # @api private
1237
1785
  def sync_lazy(value)
1238
- self.class.sync_lazy(value) { |v|
1239
- lazy_method = lazy_method_name(v)
1240
- if lazy_method
1241
- synced_value = value.public_send(lazy_method)
1242
- sync_lazy(synced_value)
1243
- else
1244
- v
1245
- end
1246
- }
1786
+ lazy_method = lazy_method_name(value)
1787
+ if lazy_method
1788
+ synced_value = value.public_send(lazy_method)
1789
+ sync_lazy(synced_value)
1790
+ else
1791
+ value
1792
+ end
1247
1793
  end
1248
1794
 
1249
1795
  protected