graphql 0.17.2 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql.rb +1 -0
- data/lib/graphql/analysis/query_depth.rb +1 -1
- data/lib/graphql/base_type.rb +25 -1
- data/lib/graphql/define.rb +2 -0
- data/lib/graphql/define/assign_connection.rb +11 -0
- data/lib/graphql/define/assign_global_id_field.rb +11 -0
- data/lib/graphql/define/assign_object_field.rb +21 -20
- data/lib/graphql/define/defined_object_proxy.rb +2 -2
- data/lib/graphql/define/instance_definable.rb +13 -3
- data/lib/graphql/field.rb +1 -1
- data/lib/graphql/language/generation.rb +57 -6
- data/lib/graphql/language/lexer.rb +434 -212
- data/lib/graphql/language/lexer.rl +18 -0
- data/lib/graphql/language/nodes.rb +75 -0
- data/lib/graphql/language/parser.rb +853 -341
- data/lib/graphql/language/parser.y +114 -17
- data/lib/graphql/query.rb +15 -1
- data/lib/graphql/relay.rb +13 -0
- data/lib/graphql/relay/array_connection.rb +80 -0
- data/lib/graphql/relay/base_connection.rb +138 -0
- data/lib/graphql/relay/connection_field.rb +54 -0
- data/lib/graphql/relay/connection_type.rb +25 -0
- data/lib/graphql/relay/edge.rb +22 -0
- data/lib/graphql/relay/edge_type.rb +14 -0
- data/lib/graphql/relay/global_id_resolve.rb +15 -0
- data/lib/graphql/relay/global_node_identification.rb +124 -0
- data/lib/graphql/relay/mutation.rb +146 -0
- data/lib/graphql/relay/page_info.rb +13 -0
- data/lib/graphql/relay/relation_connection.rb +98 -0
- data/lib/graphql/schema.rb +3 -0
- data/lib/graphql/schema/printer.rb +12 -2
- data/lib/graphql/static_validation/message.rb +9 -5
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +7 -7
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +6 -6
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +17 -9
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_used.rb +17 -6
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +2 -2
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +5 -5
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
- data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +12 -11
- data/lib/graphql/static_validation/type_stack.rb +33 -2
- data/lib/graphql/static_validation/validation_context.rb +5 -0
- data/lib/graphql/version.rb +1 -1
- data/readme.md +16 -4
- data/spec/graphql/analysis/analyze_query_spec.rb +31 -2
- data/spec/graphql/analysis/query_complexity_spec.rb +24 -0
- data/spec/graphql/argument_spec.rb +1 -1
- data/spec/graphql/define/instance_definable_spec.rb +9 -0
- data/spec/graphql/field_spec.rb +1 -1
- data/spec/graphql/internal_representation/rewrite_spec.rb +3 -3
- data/spec/graphql/language/generation_spec.rb +25 -4
- data/spec/graphql/language/parser_spec.rb +116 -1
- data/spec/graphql/query_spec.rb +10 -0
- data/spec/graphql/relay/array_connection_spec.rb +164 -0
- data/spec/graphql/relay/connection_type_spec.rb +37 -0
- data/spec/graphql/relay/global_node_identification_spec.rb +149 -0
- data/spec/graphql/relay/mutation_spec.rb +55 -0
- data/spec/graphql/relay/page_info_spec.rb +106 -0
- data/spec/graphql/relay/relation_connection_spec.rb +348 -0
- data/spec/graphql/schema/printer_spec.rb +8 -0
- data/spec/graphql/schema/reduce_types_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +12 -6
- data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +8 -4
- data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +7 -2
- data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +5 -3
- data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +5 -2
- data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +10 -2
- data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +8 -4
- data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +8 -4
- data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +6 -3
- data/spec/spec_helper.rb +7 -0
- data/spec/support/dairy_app.rb +11 -10
- data/spec/support/star_wars_data.rb +65 -58
- data/spec/support/star_wars_schema.rb +192 -54
- metadata +84 -2
@@ -11,6 +11,7 @@ rule
|
|
11
11
|
definition:
|
12
12
|
operation_definition
|
13
13
|
| fragment_definition
|
14
|
+
| type_system_definition
|
14
15
|
|
15
16
|
operation_definition:
|
16
17
|
operation_type operation_name_opt variable_definitions_opt directives_list_opt selection_set {
|
@@ -52,7 +53,7 @@ rule
|
|
52
53
|
| variable_definitions_list variable_definition { val[0] << val[1] }
|
53
54
|
|
54
55
|
variable_definition:
|
55
|
-
VAR_SIGN name COLON
|
56
|
+
VAR_SIGN name COLON type default_value_opt {
|
56
57
|
return make_node(:VariableDefinition, {
|
57
58
|
name: val[1],
|
58
59
|
type: val[3],
|
@@ -61,12 +62,12 @@ rule
|
|
61
62
|
})
|
62
63
|
}
|
63
64
|
|
64
|
-
|
65
|
-
name
|
66
|
-
|
|
67
|
-
| RBRACKET
|
65
|
+
type:
|
66
|
+
name { return make_node(:TypeName, name: val[0])}
|
67
|
+
| type BANG { return make_node(:NonNullType, of_type: val[0]) }
|
68
|
+
| RBRACKET type LBRACKET { return make_node(:ListType, of_type: val[1]) }
|
68
69
|
|
69
|
-
|
70
|
+
default_value_opt:
|
70
71
|
/* none */ { return nil }
|
71
72
|
| EQUALS input_value { return val[1] }
|
72
73
|
|
@@ -116,14 +117,38 @@ rule
|
|
116
117
|
name_without_on
|
117
118
|
| ON
|
118
119
|
|
120
|
+
schema_keyword:
|
121
|
+
SCHEMA
|
122
|
+
| SCALAR
|
123
|
+
| TYPE
|
124
|
+
| IMPLEMENTS
|
125
|
+
| INTERFACE
|
126
|
+
| UNION
|
127
|
+
| ENUM
|
128
|
+
| INPUT
|
129
|
+
|
119
130
|
name_without_on:
|
120
131
|
IDENTIFIER
|
121
132
|
| FRAGMENT
|
122
133
|
| TRUE
|
123
134
|
| FALSE
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
135
|
+
| operation_type
|
136
|
+
| schema_keyword
|
137
|
+
|
138
|
+
enum_name: /* any identifier, but not "true", "false" or "null" */
|
139
|
+
IDENTIFIER
|
140
|
+
| FRAGMENT
|
141
|
+
| ON
|
142
|
+
| operation_type
|
143
|
+
| schema_keyword
|
144
|
+
|
145
|
+
name_list:
|
146
|
+
name { return [val[0].to_s]}
|
147
|
+
| name_list name { val[0] << val[1].to_s }
|
148
|
+
|
149
|
+
enum_name_list:
|
150
|
+
enum_name { return [val[0].to_s]}
|
151
|
+
| enum_name_list enum_name { return val[0] << val[1].to_s }
|
127
152
|
|
128
153
|
arguments_opt:
|
129
154
|
/* none */ { return [] }
|
@@ -171,14 +196,6 @@ rule
|
|
171
196
|
|
172
197
|
enum_value: enum_name { return make_node(:Enum, name: val[0], position_source: val[0])}
|
173
198
|
|
174
|
-
enum_name: /* any identifier, but not "true", "false" or "null" */
|
175
|
-
IDENTIFIER
|
176
|
-
| FRAGMENT
|
177
|
-
| ON
|
178
|
-
| QUERY
|
179
|
-
| MUTATION
|
180
|
-
| SUBSCRIPTION
|
181
|
-
|
182
199
|
directives_list_opt:
|
183
200
|
/* none */ { return [] }
|
184
201
|
| directives_list
|
@@ -221,6 +238,86 @@ rule
|
|
221
238
|
}
|
222
239
|
)
|
223
240
|
}
|
241
|
+
|
242
|
+
|
243
|
+
type_system_definition:
|
244
|
+
schema_definition
|
245
|
+
| type_definition
|
246
|
+
|
247
|
+
schema_definition:
|
248
|
+
SCHEMA RCURLY operation_type_definition_list LCURLY { return make_node(:SchemaDefinition, val[2]) }
|
249
|
+
|
250
|
+
operation_type_definition_list:
|
251
|
+
operation_type_definition
|
252
|
+
| operation_type_definition_list operation_type_definition { return val[0].merge(val[1]) }
|
253
|
+
|
254
|
+
operation_type_definition:
|
255
|
+
operation_type COLON name { return { val[0].to_s.to_sym => val[2] } }
|
256
|
+
|
257
|
+
type_definition:
|
258
|
+
scalar_type_definition
|
259
|
+
| object_type_definition
|
260
|
+
| interface_type_definition
|
261
|
+
| union_type_definition
|
262
|
+
| enum_type_definition
|
263
|
+
| input_object_type_definition
|
264
|
+
|
265
|
+
scalar_type_definition: SCALAR name { return make_node(:ScalarTypeDefinition, name: val[1]) }
|
266
|
+
|
267
|
+
object_type_definition:
|
268
|
+
TYPE name implements_opt RCURLY field_definition_list LCURLY {
|
269
|
+
return make_node(:ObjectTypeDefinition, name: val[1], interfaces: val[2], fields: val[4])
|
270
|
+
}
|
271
|
+
|
272
|
+
implements_opt:
|
273
|
+
/* none */ { return [] }
|
274
|
+
| IMPLEMENTS name_list { return val[1] }
|
275
|
+
|
276
|
+
input_value_definition:
|
277
|
+
name COLON type default_value_opt {
|
278
|
+
return make_node(:InputValueDefinition, name: val[0], type: val[2], default_value: val[3])
|
279
|
+
}
|
280
|
+
|
281
|
+
input_value_definition_list:
|
282
|
+
input_value_definition { return [val[0]] }
|
283
|
+
| input_value_definition_list input_value_definition { val[0] << val[1] }
|
284
|
+
|
285
|
+
arguments_definitions_opt:
|
286
|
+
/* none */ { return [] }
|
287
|
+
| RPAREN input_value_definition_list LPAREN { return val[1] }
|
288
|
+
|
289
|
+
field_definition:
|
290
|
+
name arguments_definitions_opt COLON type {
|
291
|
+
return make_node(:FieldDefinition, name: val[0], arguments: val[1], type: val[3])
|
292
|
+
}
|
293
|
+
|
294
|
+
field_definition_list:
|
295
|
+
field_definition { return [val[0]] }
|
296
|
+
| field_definition_list field_definition { val[0] << val[1] }
|
297
|
+
|
298
|
+
interface_type_definition:
|
299
|
+
INTERFACE name RCURLY field_definition_list LCURLY {
|
300
|
+
return make_node(:InterfaceTypeDefinition, name: val[1], fields: val[3])
|
301
|
+
}
|
302
|
+
|
303
|
+
union_members:
|
304
|
+
name { return [val[0].to_s]}
|
305
|
+
| union_members PIPE name { val[0] << val[2].to_s }
|
306
|
+
|
307
|
+
union_type_definition:
|
308
|
+
UNION name EQUALS union_members {
|
309
|
+
return make_node(:UnionTypeDefinition, name: val[1], types: val[3])
|
310
|
+
}
|
311
|
+
|
312
|
+
enum_type_definition:
|
313
|
+
ENUM name RCURLY enum_name_list LCURLY {
|
314
|
+
return make_node(:EnumTypeDefinition, name: val[1], values: val[3])
|
315
|
+
}
|
316
|
+
|
317
|
+
input_object_type_definition:
|
318
|
+
INPUT name RCURLY input_value_definition_list LCURLY {
|
319
|
+
return make_node(:InputObjectTypeDefinition, name: val[1], fields: val[3])
|
320
|
+
}
|
224
321
|
end
|
225
322
|
|
226
323
|
---- header ----
|
data/lib/graphql/query.rb
CHANGED
@@ -48,6 +48,8 @@ module GraphQL
|
|
48
48
|
@fragments[part.name] = part
|
49
49
|
elsif part.is_a?(GraphQL::Language::Nodes::OperationDefinition)
|
50
50
|
@operations[part.name] = part
|
51
|
+
else
|
52
|
+
raise GraphQL::ExecutionError, "GraphQL query cannot contain a schema definition"
|
51
53
|
end
|
52
54
|
end
|
53
55
|
|
@@ -71,7 +73,10 @@ module GraphQL
|
|
71
73
|
# If more than one operation is present, it must be named at runtime.
|
72
74
|
# @return [GraphQL::Language::Nodes::OperationDefinition, nil]
|
73
75
|
def selected_operation
|
74
|
-
@selected_operation ||=
|
76
|
+
@selected_operation ||= begin
|
77
|
+
perform_validation
|
78
|
+
@selected_operation
|
79
|
+
end
|
75
80
|
end
|
76
81
|
|
77
82
|
# Determine the values for variables of this query, using default values
|
@@ -116,9 +121,18 @@ module GraphQL
|
|
116
121
|
private
|
117
122
|
|
118
123
|
def perform_validation
|
124
|
+
@selected_operation = find_operation(@operations, @operation_name)
|
119
125
|
validation_result = schema.static_validator.validate(self)
|
120
126
|
@validation_errors = validation_result[:errors]
|
121
127
|
@internal_representation = validation_result[:irep]
|
128
|
+
if @validation_errors.none?
|
129
|
+
# Accessing variables will raise errors if there are any :S
|
130
|
+
variables
|
131
|
+
end
|
132
|
+
nil
|
133
|
+
rescue GraphQL::Query::OperationNameMissingError, GraphQL::Query::VariableValidationError => err
|
134
|
+
@validation_errors = [err.to_h]
|
135
|
+
@internal_representation = nil
|
122
136
|
nil
|
123
137
|
end
|
124
138
|
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
require 'graphql/relay/global_node_identification'
|
4
|
+
require 'graphql/relay/page_info'
|
5
|
+
require 'graphql/relay/edge'
|
6
|
+
require 'graphql/relay/edge_type'
|
7
|
+
require 'graphql/relay/base_connection'
|
8
|
+
require 'graphql/relay/array_connection'
|
9
|
+
require 'graphql/relay/relation_connection'
|
10
|
+
require 'graphql/relay/global_id_resolve'
|
11
|
+
require 'graphql/relay/mutation'
|
12
|
+
require 'graphql/relay/connection_field'
|
13
|
+
require 'graphql/relay/connection_type'
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Relay
|
3
|
+
class ArrayConnection < BaseConnection
|
4
|
+
def cursor_from_node(item)
|
5
|
+
idx = starting_offset + sliced_nodes.find_index(item) + 1
|
6
|
+
Base64.strict_encode64(idx.to_s)
|
7
|
+
end
|
8
|
+
|
9
|
+
def has_next_page
|
10
|
+
!!(first && sliced_nodes.count > limit)
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_previous_page
|
14
|
+
!!(last && starting_offset > 0)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# apply first / last limit results
|
20
|
+
def paged_nodes
|
21
|
+
@paged_nodes = begin
|
22
|
+
items = sliced_nodes
|
23
|
+
|
24
|
+
if limit
|
25
|
+
items.first(limit)
|
26
|
+
else
|
27
|
+
items
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Apply cursors to edges
|
33
|
+
def sliced_nodes
|
34
|
+
@sliced_nodes ||= begin
|
35
|
+
items = object
|
36
|
+
items[starting_offset..-1]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def index_from_cursor(cursor)
|
41
|
+
Base64.decode64(cursor).to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
def starting_offset
|
45
|
+
@starting_offset = if before
|
46
|
+
[previous_offset, 0].max
|
47
|
+
else
|
48
|
+
previous_offset
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def previous_offset
|
53
|
+
@previous_offset ||= if after
|
54
|
+
index_from_cursor(after)
|
55
|
+
elsif before
|
56
|
+
prev_page_size = [max_page_size, last].compact.min || 0
|
57
|
+
index_from_cursor(before) - prev_page_size - 1
|
58
|
+
else
|
59
|
+
0
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def limit
|
64
|
+
@limit ||= begin
|
65
|
+
limit_from_arguments = if first
|
66
|
+
first
|
67
|
+
else
|
68
|
+
if previous_offset < 0
|
69
|
+
previous_offset + (last ? last : 0)
|
70
|
+
else
|
71
|
+
last
|
72
|
+
end
|
73
|
+
end
|
74
|
+
[limit_from_arguments, max_page_size].compact.min
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
BaseConnection.register_connection_implementation(Array, ArrayConnection)
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Relay
|
3
|
+
# Subclasses must implement:
|
4
|
+
# - {#cursor_from_node}, which returns an opaque cursor for the given item
|
5
|
+
# - {#sliced_nodes}, which slices by `before` & `after`
|
6
|
+
# - {#paged_nodes}, which applies `first` & `last` limits
|
7
|
+
#
|
8
|
+
# In a subclass, you have access to
|
9
|
+
# - {#object}, the object which the connection will wrap
|
10
|
+
# - {#first}, {#after}, {#last}, {#before} (arguments passed to the field)
|
11
|
+
# - {#max_page_size} (the specified maximum page size that can be returned from a connection)
|
12
|
+
#
|
13
|
+
class BaseConnection
|
14
|
+
# Just to encode data in the cursor, use something that won't conflict
|
15
|
+
CURSOR_SEPARATOR = "---"
|
16
|
+
|
17
|
+
# Map of collection class names -> connection_classes
|
18
|
+
# eg {"Array" => ArrayConnection}
|
19
|
+
CONNECTION_IMPLEMENTATIONS = {}
|
20
|
+
|
21
|
+
# Find a connection implementation suitable for exposing `items`
|
22
|
+
#
|
23
|
+
# @param [Object] A collection of items (eg, Array, AR::Relation)
|
24
|
+
# @return [subclass of BaseConnection] a connection Class for wrapping `items`
|
25
|
+
def self.connection_for_items(items)
|
26
|
+
# We check class membership by comparing class names rather than
|
27
|
+
# identity to prevent this from being broken by Rails autoloading.
|
28
|
+
# Changes to the source file for ItemsClass in Rails apps cause it to be
|
29
|
+
# reloaded as a new object, so if we were to use `is_a?` here, it would
|
30
|
+
# no longer match any registered custom connection types.
|
31
|
+
ancestor_names = items.class.ancestors.map(&:name)
|
32
|
+
implementation = CONNECTION_IMPLEMENTATIONS.find do |items_class_name, connection_class|
|
33
|
+
ancestor_names.include? items_class_name
|
34
|
+
end
|
35
|
+
if implementation.nil?
|
36
|
+
raise("No connection implementation to wrap #{items.class} (#{items})")
|
37
|
+
else
|
38
|
+
implementation[1]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Add `connection_class` as the connection wrapper for `items_class`
|
43
|
+
# eg, `RelationConnection` is the implementation for `AR::Relation`
|
44
|
+
# @param [Class] A class representing a collection (eg, Array, AR::Relation)
|
45
|
+
# @param [Class] A class implementing Connection methods
|
46
|
+
def self.register_connection_implementation(items_class, connection_class)
|
47
|
+
CONNECTION_IMPLEMENTATIONS[items_class.name] = connection_class
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :object, :arguments, :max_page_size, :parent
|
51
|
+
|
52
|
+
# Make a connection, wrapping `object`
|
53
|
+
# @param The collection of results
|
54
|
+
# @param Query arguments
|
55
|
+
# @param max_page_size [Int] The maximum number of results to return
|
56
|
+
# @param parent [Object] The object which this collection belongs to
|
57
|
+
def initialize(object, arguments, max_page_size: nil, parent: nil)
|
58
|
+
@object = object
|
59
|
+
@arguments = arguments
|
60
|
+
@max_page_size = max_page_size
|
61
|
+
@parent = parent
|
62
|
+
end
|
63
|
+
|
64
|
+
# Provide easy access to provided arguments:
|
65
|
+
METHODS_FROM_ARGUMENTS = [:first, :after, :last, :before, :order]
|
66
|
+
|
67
|
+
# @!method first
|
68
|
+
# The value passed as `first:`, if there was one
|
69
|
+
# @!method after
|
70
|
+
# The value passed as `after:`, if there was one
|
71
|
+
# @!method last
|
72
|
+
# The value passed as `last:`, if there was one
|
73
|
+
# @!method before
|
74
|
+
# The value passed as `before:`, if there was one
|
75
|
+
# @!method order
|
76
|
+
# The value passed as `order:`, if there was one
|
77
|
+
METHODS_FROM_ARGUMENTS.each do |arg_name|
|
78
|
+
define_method(arg_name) do
|
79
|
+
arguments[arg_name]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# These are the items to render for this connection,
|
84
|
+
# probably wrapped by {GraphQL::Relay::Edge}
|
85
|
+
def edge_nodes
|
86
|
+
@edge_nodes ||= paged_nodes
|
87
|
+
end
|
88
|
+
|
89
|
+
# Support the `pageInfo` field
|
90
|
+
def page_info
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
# Used by `pageInfo`
|
95
|
+
def has_next_page
|
96
|
+
!!(first && sliced_nodes.count > first)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Used by `pageInfo`
|
100
|
+
def has_previous_page
|
101
|
+
!!(last && sliced_nodes.count > last)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Used by `pageInfo`
|
105
|
+
def start_cursor
|
106
|
+
if start_node = (respond_to?(:paged_nodes_array, true) ? paged_nodes_array : paged_nodes).first
|
107
|
+
return cursor_from_node(start_node)
|
108
|
+
else
|
109
|
+
return nil
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Used by `pageInfo`
|
114
|
+
def end_cursor
|
115
|
+
if end_node = (respond_to?(:paged_nodes_array, true) ? paged_nodes_array : paged_nodes).last
|
116
|
+
return cursor_from_node(end_node)
|
117
|
+
else
|
118
|
+
return nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# An opaque operation which returns a connection-specific cursor.
|
123
|
+
def cursor_from_node(object)
|
124
|
+
raise NotImplementedError, "must return a cursor for this object/connection pair"
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def paged_nodes
|
130
|
+
raise NotImplementedError, "must return items for this connection after paging"
|
131
|
+
end
|
132
|
+
|
133
|
+
def sliced_nodes
|
134
|
+
raise NotImplementedError, "must return all items for this connection after chopping off first and last"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|