graphql 0.17.2 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +1 -0
  3. data/lib/graphql/analysis/query_depth.rb +1 -1
  4. data/lib/graphql/base_type.rb +25 -1
  5. data/lib/graphql/define.rb +2 -0
  6. data/lib/graphql/define/assign_connection.rb +11 -0
  7. data/lib/graphql/define/assign_global_id_field.rb +11 -0
  8. data/lib/graphql/define/assign_object_field.rb +21 -20
  9. data/lib/graphql/define/defined_object_proxy.rb +2 -2
  10. data/lib/graphql/define/instance_definable.rb +13 -3
  11. data/lib/graphql/field.rb +1 -1
  12. data/lib/graphql/language/generation.rb +57 -6
  13. data/lib/graphql/language/lexer.rb +434 -212
  14. data/lib/graphql/language/lexer.rl +18 -0
  15. data/lib/graphql/language/nodes.rb +75 -0
  16. data/lib/graphql/language/parser.rb +853 -341
  17. data/lib/graphql/language/parser.y +114 -17
  18. data/lib/graphql/query.rb +15 -1
  19. data/lib/graphql/relay.rb +13 -0
  20. data/lib/graphql/relay/array_connection.rb +80 -0
  21. data/lib/graphql/relay/base_connection.rb +138 -0
  22. data/lib/graphql/relay/connection_field.rb +54 -0
  23. data/lib/graphql/relay/connection_type.rb +25 -0
  24. data/lib/graphql/relay/edge.rb +22 -0
  25. data/lib/graphql/relay/edge_type.rb +14 -0
  26. data/lib/graphql/relay/global_id_resolve.rb +15 -0
  27. data/lib/graphql/relay/global_node_identification.rb +124 -0
  28. data/lib/graphql/relay/mutation.rb +146 -0
  29. data/lib/graphql/relay/page_info.rb +13 -0
  30. data/lib/graphql/relay/relation_connection.rb +98 -0
  31. data/lib/graphql/schema.rb +3 -0
  32. data/lib/graphql/schema/printer.rb +12 -2
  33. data/lib/graphql/static_validation/message.rb +9 -5
  34. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  35. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  36. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  37. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +7 -7
  38. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
  39. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
  40. data/lib/graphql/static_validation/rules/fields_will_merge.rb +6 -6
  41. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +17 -9
  42. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  43. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +1 -1
  44. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  45. data/lib/graphql/static_validation/rules/fragments_are_used.rb +17 -6
  46. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  47. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +2 -2
  48. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +5 -5
  49. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  50. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +12 -11
  51. data/lib/graphql/static_validation/type_stack.rb +33 -2
  52. data/lib/graphql/static_validation/validation_context.rb +5 -0
  53. data/lib/graphql/version.rb +1 -1
  54. data/readme.md +16 -4
  55. data/spec/graphql/analysis/analyze_query_spec.rb +31 -2
  56. data/spec/graphql/analysis/query_complexity_spec.rb +24 -0
  57. data/spec/graphql/argument_spec.rb +1 -1
  58. data/spec/graphql/define/instance_definable_spec.rb +9 -0
  59. data/spec/graphql/field_spec.rb +1 -1
  60. data/spec/graphql/internal_representation/rewrite_spec.rb +3 -3
  61. data/spec/graphql/language/generation_spec.rb +25 -4
  62. data/spec/graphql/language/parser_spec.rb +116 -1
  63. data/spec/graphql/query_spec.rb +10 -0
  64. data/spec/graphql/relay/array_connection_spec.rb +164 -0
  65. data/spec/graphql/relay/connection_type_spec.rb +37 -0
  66. data/spec/graphql/relay/global_node_identification_spec.rb +149 -0
  67. data/spec/graphql/relay/mutation_spec.rb +55 -0
  68. data/spec/graphql/relay/page_info_spec.rb +106 -0
  69. data/spec/graphql/relay/relation_connection_spec.rb +348 -0
  70. data/spec/graphql/schema/printer_spec.rb +8 -0
  71. data/spec/graphql/schema/reduce_types_spec.rb +1 -1
  72. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +12 -6
  73. data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +8 -4
  74. data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +4 -2
  75. data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +4 -2
  76. data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +7 -2
  77. data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +4 -2
  78. data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +6 -3
  79. data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +5 -3
  80. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -2
  81. data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +5 -2
  82. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +10 -2
  83. data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +6 -3
  84. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +8 -4
  85. data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +8 -4
  86. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +6 -3
  87. data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +6 -3
  88. data/spec/spec_helper.rb +7 -0
  89. data/spec/support/dairy_app.rb +11 -10
  90. data/spec/support/star_wars_data.rb +65 -58
  91. data/spec/support/star_wars_schema.rb +192 -54
  92. 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 variable_definition_type_name variable_definition_default_value_opt {
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
- variable_definition_type_name:
65
- name { return make_node(:TypeName, name: val[0])}
66
- | variable_definition_type_name BANG { return make_node(:NonNullType, of_type: val[0]) }
67
- | RBRACKET variable_definition_type_name LBRACKET { return make_node(:ListType, of_type: val[1]) }
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
- variable_definition_default_value_opt:
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
- | QUERY
125
- | MUTATION
126
- | SUBSCRIPTION
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 ----
@@ -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 ||= find_operation(@operations, @operation_name)
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