graphql 1.10.0.pre1 → 1.10.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
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