graphql 1.9.0.pre2 → 1.9.0.pre3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7576d86b005acda0b6db32b531eb5ee5994b46d5
4
- data.tar.gz: 5d27e5a79e0b2ab1d8c64edd8aa4ed5bc66b72e8
3
+ metadata.gz: d574e6bf6c3c4644fd85e279ddf56ab3128bef2d
4
+ data.tar.gz: a6227d0ec2ee59131b2de45ccf87c2541279543d
5
5
  SHA512:
6
- metadata.gz: 001ec87271eaefae504d24deac1c3b6d241f1b944b2297050e3d35a8a72c6a016ec9dd6a48f3c1bdef1ab5f9eb6aac2f5f31ca745bfdf9edb077abbc6f8158d7
7
- data.tar.gz: fafac00586652e70e096f68a92dd20bcf712e09343d721638987f3ba8d69e77250fd25de5743ca521687c1a22a8df5355b4f7b84f500ff898446c29fe08e4bed
6
+ metadata.gz: 3d91a0736831fa204240513b2cff401bf51a8ffb09739662fd95415bf2a91f53f088dcbcb026ff883d43476980260d1ab222d6bd5c969060ce279e9badf7ecb5
7
+ data.tar.gz: 0eaa0115f08cae059cb4d514fc572cf064956dda38ac87919d841b72103aa67df7f871e761375704ade18b5e77c474c99e5e514dcb098608bba7b3e629a0241d
@@ -40,6 +40,7 @@ module GraphQL
40
40
  @field = field
41
41
  @root_type = root_type
42
42
  @query = query
43
+ @selected_type = @field ? @field.type.unwrap : root_type
43
44
  end
44
45
 
45
46
  # @return [Array<GraphQL::Language::Nodes::Field>]
@@ -72,16 +73,10 @@ module GraphQL
72
73
  # Like {#selects?}, but can be used for chaining.
73
74
  # It returns a null object (check with {#selected?})
74
75
  # @return [GraphQL::Execution::Lookahead]
75
- def selection(field_name, arguments: nil)
76
+ def selection(field_name, selected_type: @selected_type, arguments: nil)
76
77
  next_field_name = normalize_name(field_name)
77
78
 
78
- next_field_owner = if @field
79
- @field.type.unwrap
80
- else
81
- @root_type
82
- end
83
-
84
- next_field_defn = FieldHelpers.get_field(@query.schema, next_field_owner, next_field_name)
79
+ next_field_defn = FieldHelpers.get_field(@query.schema, selected_type, next_field_name)
85
80
  if next_field_defn
86
81
  next_nodes = []
87
82
  @ast_nodes.each do |ast_node|
@@ -118,7 +113,7 @@ module GraphQL
118
113
  def selections(arguments: nil)
119
114
  subselections_by_name = {}
120
115
  @ast_nodes.each do |node|
121
- find_selections(subselections_by_name, node.selections, arguments)
116
+ find_selections(subselections_by_name, @selected_type, node.selections, arguments)
122
117
  end
123
118
 
124
119
  # Items may be filtered out if `arguments` doesn't match
@@ -139,6 +134,10 @@ module GraphQL
139
134
  @field && @field.original_name
140
135
  end
141
136
 
137
+ def inspect
138
+ "#<GraphQL::Execution::Lookahead #{@field ? "@field=#{@field.path.inspect}": "@root_type=#{@root_type}"} @ast_nodes.size=#{@ast_nodes.size}>"
139
+ end
140
+
142
141
  # This is returned for {Lookahead#selection} when a non-existent field is passed
143
142
  class NullLookahead < Lookahead
144
143
  # No inputs required here.
@@ -160,6 +159,10 @@ module GraphQL
160
159
  def selections(*)
161
160
  []
162
161
  end
162
+
163
+ def inspect
164
+ "#<GraphQL::Execution::Lookahead::NullLookahead>"
165
+ end
163
166
  end
164
167
 
165
168
  # A singleton, so that misses don't come with overhead.
@@ -184,16 +187,22 @@ module GraphQL
184
187
  end
185
188
  end
186
189
 
187
- def find_selections(subselections_by_name, ast_selections, arguments)
190
+ def find_selections(subselections_by_name, selected_type, ast_selections, arguments)
188
191
  ast_selections.each do |ast_selection|
189
192
  case ast_selection
190
193
  when GraphQL::Language::Nodes::Field
191
- subselections_by_name[ast_selection.name] ||= selection(ast_selection.name, arguments: arguments)
194
+ subselections_by_name[ast_selection.name] ||= selection(ast_selection.name, selected_type: selected_type, arguments: arguments)
192
195
  when GraphQL::Language::Nodes::InlineFragment
193
- find_selections(subselections_by_name, ast_selection.selections, arguments)
196
+ if (t = ast_selection.type)
197
+ # Assuming this is valid, that `t` will be found.
198
+ selected_type = @query.schema.types[t.name].metadata[:type_class]
199
+ end
200
+ find_selections(subselections_by_name, selected_type, ast_selection.selections, arguments)
194
201
  when GraphQL::Language::Nodes::FragmentSpread
195
202
  frag_defn = @query.fragments[ast_selection.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{ast_selection.name} (found: #{@query.fragments.keys})")
196
- find_selections(subselections_by_name, frag_defn.selections, arguments)
203
+ # Again, assuming a valid AST
204
+ selected_type = @query.schema.types[frag_defn.type.name].metadata[:type_class]
205
+ find_selections(subselections_by_name, selected_type, frag_defn.selections, arguments)
197
206
  else
198
207
  raise "Invariant: Unexpected selection type: #{ast_selection.class}"
199
208
  end
@@ -65,9 +65,16 @@ module GraphQL
65
65
  # Visit `document` and all children, applying hooks as you go
66
66
  # @return [void]
67
67
  def visit
68
- @result, _nil_parent = on_node_with_modifications(@document, nil)
68
+ result = on_node_with_modifications(@document, nil)
69
+ @result = if result.is_a?(Array)
70
+ result.first
71
+ else
72
+ # The node wasn't modified
73
+ @document
74
+ end
69
75
  end
70
76
 
77
+ # Call the user-defined handler for `node`.
71
78
  def visit_node(node, parent)
72
79
  public_send(node.visit_method, node, parent)
73
80
  end
@@ -80,77 +87,81 @@ module GraphQL
80
87
  # For compatibility, it calls hook procs, too.
81
88
  # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
82
89
  # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
83
- # @return [void]
90
+ # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
84
91
  def on_abstract_node(node, parent)
85
92
  if node == DELETE_NODE
86
93
  # This might be passed to `super(DELETE_NODE, ...)`
87
94
  # by a user hook, don't want to keep visiting in that case.
88
- [node, parent]
95
+ nil
89
96
  else
90
97
  # Run hooks if there are any
91
- begin_hooks_ok = @visitors.none? || begin_visit(node, parent)
98
+ new_node = node
99
+ begin_hooks_ok = @visitors.none? || begin_visit(new_node, parent)
92
100
  if begin_hooks_ok
93
101
  node.children.each do |child_node|
94
- new_child_and_node = on_node_with_modifications(child_node, node)
102
+ new_child_and_node = on_node_with_modifications(child_node, new_node)
95
103
  # Reassign `node` in case the child hook makes a modification
96
104
  if new_child_and_node.is_a?(Array)
97
- node = new_child_and_node[1]
105
+ new_node = new_child_and_node[1]
98
106
  end
99
107
  end
100
108
  end
101
- @visitors.any? && end_visit(node, parent)
102
- [node, parent]
109
+ @visitors.any? && end_visit(new_node, parent)
110
+
111
+ if new_node.equal?(node)
112
+ nil
113
+ else
114
+ [new_node, parent]
115
+ end
103
116
  end
104
117
  end
105
118
 
106
119
  # We don't use `alias` here because it breaks `super`
107
- def self.make_visit_method(node_method, super_method)
108
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
120
+ def self.make_visit_method(node_method)
121
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
109
122
  def #{node_method}(node, parent)
110
- #{super_method}(node, parent)
123
+ child_mod = on_abstract_node(node, parent)
124
+ # If visiting the children returned changes, continue passing those.
125
+ child_mod || [node, parent]
111
126
  end
112
- EOS
127
+ RUBY
113
128
  end
114
129
 
115
- make_visit_method :on_argument, :on_abstract_node
116
- make_visit_method :on_directive, :on_abstract_node
117
- make_visit_method :on_directive_definition, :on_abstract_node
118
- make_visit_method :on_directive_location, :on_abstract_node
119
- make_visit_method :on_document, :on_abstract_node
120
- make_visit_method :on_enum, :on_abstract_node
121
- make_visit_method :on_enum_type_definition, :on_abstract_node
122
- make_visit_method :on_enum_type_extension, :on_abstract_node
123
- make_visit_method :on_enum_value_definition, :on_abstract_node
124
- make_visit_method :on_field, :on_abstract_node
125
- make_visit_method :on_field_definition, :on_abstract_node
126
- make_visit_method :on_fragment_definition, :on_abstract_node
127
- make_visit_method :on_fragment_spread, :on_abstract_node
128
- make_visit_method :on_inline_fragment, :on_abstract_node
129
- make_visit_method :on_input_object, :on_abstract_node
130
- make_visit_method :on_input_object_type_definition, :on_abstract_node
131
- make_visit_method :on_input_object_type_extension, :on_abstract_node
132
- make_visit_method :on_input_value_definition, :on_abstract_node
133
- make_visit_method :on_interface_type_definition, :on_abstract_node
134
- make_visit_method :on_interface_type_extension, :on_abstract_node
135
- make_visit_method :on_list_type, :on_abstract_node
136
- make_visit_method :on_non_null_type, :on_abstract_node
137
- make_visit_method :on_null_value, :on_abstract_node
138
- make_visit_method :on_object_type_definition, :on_abstract_node
139
- make_visit_method :on_object_type_extension, :on_abstract_node
140
- make_visit_method :on_operation_definition, :on_abstract_node
141
- make_visit_method :on_scalar_type_definition, :on_abstract_node
142
- make_visit_method :on_scalar_type_extension, :on_abstract_node
143
- make_visit_method :on_schema_definition, :on_abstract_node
144
- make_visit_method :on_schema_extension, :on_abstract_node
145
- make_visit_method :on_type_name, :on_abstract_node
146
- make_visit_method :on_union_type_definition, :on_abstract_node
147
- make_visit_method :on_union_type_extension, :on_abstract_node
148
- make_visit_method :on_variable_definition, :on_abstract_node
149
- make_visit_method :on_variable_identifier, :on_abstract_node
150
-
151
- def visit_node(node, parent)
152
- public_send(node.visit_method, node, parent)
153
- end
130
+ make_visit_method :on_argument
131
+ make_visit_method :on_directive
132
+ make_visit_method :on_directive_definition
133
+ make_visit_method :on_directive_location
134
+ make_visit_method :on_document
135
+ make_visit_method :on_enum
136
+ make_visit_method :on_enum_type_definition
137
+ make_visit_method :on_enum_type_extension
138
+ make_visit_method :on_enum_value_definition
139
+ make_visit_method :on_field
140
+ make_visit_method :on_field_definition
141
+ make_visit_method :on_fragment_definition
142
+ make_visit_method :on_fragment_spread
143
+ make_visit_method :on_inline_fragment
144
+ make_visit_method :on_input_object
145
+ make_visit_method :on_input_object_type_definition
146
+ make_visit_method :on_input_object_type_extension
147
+ make_visit_method :on_input_value_definition
148
+ make_visit_method :on_interface_type_definition
149
+ make_visit_method :on_interface_type_extension
150
+ make_visit_method :on_list_type
151
+ make_visit_method :on_non_null_type
152
+ make_visit_method :on_null_value
153
+ make_visit_method :on_object_type_definition
154
+ make_visit_method :on_object_type_extension
155
+ make_visit_method :on_operation_definition
156
+ make_visit_method :on_scalar_type_definition
157
+ make_visit_method :on_scalar_type_extension
158
+ make_visit_method :on_schema_definition
159
+ make_visit_method :on_schema_extension
160
+ make_visit_method :on_type_name
161
+ make_visit_method :on_union_type_definition
162
+ make_visit_method :on_union_type_extension
163
+ make_visit_method :on_variable_definition
164
+ make_visit_method :on_variable_identifier
154
165
 
155
166
  private
156
167
 
@@ -177,7 +188,7 @@ module GraphQL
177
188
  # The user-provided hook didn't make any modifications.
178
189
  # In fact, the hook might have returned who-knows-what, so
179
190
  # ignore the return value and use the original values.
180
- [node, parent]
191
+ new_node_and_new_parent
181
192
  end
182
193
  end
183
194
 
@@ -14,7 +14,16 @@ module GraphQL
14
14
  end
15
15
 
16
16
  def to_h
17
- super.merge({ "value" => value, "problems" => validation_result.problems })
17
+ # It is possible there are other extension items in this error, so handle
18
+ # a one level deep merge explicitly. However beyond that only show the
19
+ # latest value and problems.
20
+ super.merge({ "extensions" => { "value" => value, "problems" => validation_result.problems }}) do |key, oldValue, newValue|
21
+ if oldValue.respond_to? merge
22
+ oldValue.merge(newValue)
23
+ else
24
+ newValue
25
+ end
26
+ end
18
27
  end
19
28
  end
20
29
  end
@@ -42,6 +42,7 @@ require "graphql/schema/directive/transform"
42
42
  require "graphql/schema/resolver"
43
43
  require "graphql/schema/mutation"
44
44
  require "graphql/schema/relay_classic_mutation"
45
+ require "graphql/schema/subscription"
45
46
 
46
47
  module GraphQL
47
48
  # A GraphQL schema which may be queried with {GraphQL::Query}.
@@ -184,7 +185,7 @@ module GraphQL
184
185
  @introspection_namespace = nil
185
186
  @introspection_system = nil
186
187
  @interpeter = false
187
- @error_bubbling = true
188
+ @error_bubbling = false
188
189
  end
189
190
 
190
191
  # @return [Boolean] True if using the new {GraphQL::Execution::Interpreter}
@@ -915,11 +916,15 @@ module GraphQL
915
916
  end
916
917
 
917
918
  def resolve_type(type, obj, ctx)
918
- raise NotImplementedError, "#{self.name}.resolve_type(type, obj, ctx) must be implemented to use Union types or Interface types (tried to resolve: #{type.name})"
919
+ if type.kind.object?
920
+ type
921
+ else
922
+ raise NotImplementedError, "#{self.name}.resolve_type(type, obj, ctx) must be implemented to use Union types or Interface types (tried to resolve: #{type.name})"
923
+ end
919
924
  end
920
925
 
921
926
  def object_from_id(node_id, ctx)
922
- raise NotImplementedError, "#{self.name}.object_from_id(node_id, ctx) must be implemented to use the `node` field (tried to load from id `#{node_id}`)"
927
+ raise NotImplementedError, "#{self.name}.object_from_id(node_id, ctx) must be implemented to load by ID (tried to load from id `#{node_id}`)"
923
928
  end
924
929
 
925
930
  def id_from_object(object, type, ctx)
@@ -59,10 +59,11 @@ module GraphQL
59
59
  # It also normalizes positional arguments into keywords for {Schema::Field#initialize}.
60
60
  # @param resolver [Class] A {GraphQL::Schema::Resolver} class to use for field configuration
61
61
  # @param mutation [Class] A {GraphQL::Schema::Mutation} class to use for field configuration
62
+ # @param subscription [Class] A {GraphQL::Schema::Subscription} class to use for field configuration
62
63
  # @return [GraphQL::Schema:Field] an instance of `self
63
64
  # @see {.initialize} for other options
64
- def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, **kwargs, &block)
65
- if (parent_config = resolver || mutation)
65
+ def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
66
+ if (parent_config = resolver || mutation || subscription)
66
67
  # Get the parent config, merge in local overrides
67
68
  kwargs = parent_config.field_options.merge(kwargs)
68
69
  # Add a reference to that parent class
@@ -60,6 +60,7 @@ module GraphQL
60
60
  #
61
61
  class Mutation < GraphQL::Schema::Resolver
62
62
  extend GraphQL::Schema::Member::HasFields
63
+ extend GraphQL::Schema::Resolver::HasPayloadType
63
64
 
64
65
  class << self
65
66
  # Override this method to handle legacy-style usages of `MyMutation.field`
@@ -71,62 +72,17 @@ module GraphQL
71
72
  end
72
73
  end
73
74
 
74
- # Call this method to get the derived return type of the mutation,
75
- # or use it as a configuration method to assign a return type
76
- # instead of generating one.
77
- # @param new_payload_type [Class, nil] If a type definition class is provided, it will be used as the return type of the mutation field
78
- # @return [Class] The object type which this mutation returns.
79
- def payload_type(new_payload_type = nil)
80
- if new_payload_type
81
- @payload_type = new_payload_type
82
- end
83
- @payload_type ||= generate_payload_type
84
- end
85
-
86
- alias :type :payload_type
87
- alias :type_expr :payload_type
88
-
89
- def field_class(new_class = nil)
90
- if new_class
91
- @field_class = new_class
92
- else
93
- @field_class || find_inherited_method(:field_class, GraphQL::Schema::Field)
94
- end
95
- end
96
-
97
- # An object class to use for deriving return types
98
- # @param new_class [Class, nil] Defaults to {GraphQL::Schema::Object}
99
- # @return [Class]
100
- def object_class(new_class = nil)
101
- if new_class
102
- @object_class = new_class
103
- end
104
- @object_class || (superclass.respond_to?(:object_class) ? superclass.object_class : GraphQL::Schema::Object)
105
- end
106
-
107
75
  def visible?(context)
108
76
  true
109
77
  end
110
78
 
111
79
  private
112
80
 
113
- # Build a subclass of {.object_class} based on `self`.
114
- # This value will be cached as `{.payload_type}`.
115
- # Override this hook to customize return type generation.
81
+ # Override this to attach self as `mutation`
116
82
  def generate_payload_type
117
- mutation_name = graphql_name
118
- mutation_fields = fields
119
- mutation_class = self
120
- Class.new(object_class) do
121
- graphql_name("#{mutation_name}Payload")
122
- description("Autogenerated return type of #{mutation_name}")
123
- mutation(mutation_class)
124
- mutation_fields.each do |name, f|
125
- # Reattach the already-defined field here
126
- # (The field's `.owner` will still point to the mutation, not the object type, I think)
127
- add_field(f)
128
- end
129
- end
83
+ payload_class = super
84
+ payload_class.mutation(self)
85
+ payload_class
130
86
  end
131
87
  end
132
88
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require "graphql/schema/resolver/has_payload_type"
2
3
 
3
4
  module GraphQL
4
5
  class Schema
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Resolver
6
+ # Adds `field(...)` helper to resolvers so that they can
7
+ # generate payload types.
8
+ #
9
+ # Or, an already-defined one can be attached with `payload_type(...)`.
10
+ module HasPayloadType
11
+ # Call this method to get the derived return type of the mutation,
12
+ # or use it as a configuration method to assign a return type
13
+ # instead of generating one.
14
+ # @param new_payload_type [Class, nil] If a type definition class is provided, it will be used as the return type of the mutation field
15
+ # @return [Class] The object type which this mutation returns.
16
+ def payload_type(new_payload_type = nil)
17
+ if new_payload_type
18
+ @payload_type = new_payload_type
19
+ end
20
+ @payload_type ||= generate_payload_type
21
+ end
22
+
23
+ alias :type :payload_type
24
+ alias :type_expr :payload_type
25
+
26
+ def field_class(new_class = nil)
27
+ if new_class
28
+ @field_class = new_class
29
+ else
30
+ @field_class || find_inherited_method(:field_class, GraphQL::Schema::Field)
31
+ end
32
+ end
33
+
34
+ # An object class to use for deriving return types
35
+ # @param new_class [Class, nil] Defaults to {GraphQL::Schema::Object}
36
+ # @return [Class]
37
+ def object_class(new_class = nil)
38
+ if new_class
39
+ @object_class = new_class
40
+ end
41
+ @object_class || (superclass.respond_to?(:object_class) ? superclass.object_class : GraphQL::Schema::Object)
42
+ end
43
+
44
+ private
45
+
46
+ # Build a subclass of {.object_class} based on `self`.
47
+ # This value will be cached as `{.payload_type}`.
48
+ # Override this hook to customize return type generation.
49
+ def generate_payload_type
50
+ resolver_name = graphql_name
51
+ resolver_fields = fields
52
+ Class.new(object_class) do
53
+ graphql_name("#{resolver_name}Payload")
54
+ description("Autogenerated return type of #{resolver_name}")
55
+ resolver_fields.each do |name, f|
56
+ # Reattach the already-defined field here
57
+ # (The field's `.owner` will still point to the mutation, not the object type, I think)
58
+ add_field(f)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end