graphql-rb 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +26 -0
  3. data/lib/graphql/configuration/configurable.rb +46 -0
  4. data/lib/graphql/configuration/configuration.rb +92 -0
  5. data/lib/graphql/configuration/slot.rb +124 -0
  6. data/lib/graphql/configuration.rb +3 -0
  7. data/lib/graphql/errors/error.rb +3 -0
  8. data/lib/graphql/errors.rb +1 -0
  9. data/lib/graphql/executor.rb +83 -0
  10. data/lib/graphql/introspection/meta_fields.rb +54 -0
  11. data/lib/graphql/introspection/query.rb +81 -0
  12. data/lib/graphql/introspection/schema.rb +158 -0
  13. data/lib/graphql/introspection.rb +3 -0
  14. data/lib/graphql/language/argument.rb +11 -0
  15. data/lib/graphql/language/directive.rb +5 -0
  16. data/lib/graphql/language/document.rb +23 -0
  17. data/lib/graphql/language/field.rb +55 -0
  18. data/lib/graphql/language/fragment_definition.rb +23 -0
  19. data/lib/graphql/language/fragment_spread.rb +5 -0
  20. data/lib/graphql/language/inline_fragment.rb +23 -0
  21. data/lib/graphql/language/list_type.rb +15 -0
  22. data/lib/graphql/language/name.rb +5 -0
  23. data/lib/graphql/language/named_type.rb +15 -0
  24. data/lib/graphql/language/non_null_type.rb +15 -0
  25. data/lib/graphql/language/operation_definition.rb +33 -0
  26. data/lib/graphql/language/parser.rb +331 -0
  27. data/lib/graphql/language/selection_set.rb +107 -0
  28. data/lib/graphql/language/transform.rb +101 -0
  29. data/lib/graphql/language/value.rb +24 -0
  30. data/lib/graphql/language/variable.rb +11 -0
  31. data/lib/graphql/language/variable_definition.rb +34 -0
  32. data/lib/graphql/language.rb +40 -0
  33. data/lib/graphql/type/argument.rb +16 -0
  34. data/lib/graphql/type/directive.rb +37 -0
  35. data/lib/graphql/type/directives.rb +25 -0
  36. data/lib/graphql/type/enum_type.rb +100 -0
  37. data/lib/graphql/type/field.rb +50 -0
  38. data/lib/graphql/type/input_object_type.rb +47 -0
  39. data/lib/graphql/type/interface_type.rb +64 -0
  40. data/lib/graphql/type/list.rb +23 -0
  41. data/lib/graphql/type/non_null.rb +25 -0
  42. data/lib/graphql/type/object_type.rb +57 -0
  43. data/lib/graphql/type/scalar_type.rb +137 -0
  44. data/lib/graphql/type/schema.rb +49 -0
  45. data/lib/graphql/type/union_type.rb +39 -0
  46. data/lib/graphql/type.rb +82 -0
  47. data/lib/graphql/validator.rb +43 -0
  48. data/lib/graphql/version.rb +3 -0
  49. data/lib/graphql.rb +21 -0
  50. data/spec/configuration/configuration_spec.rb +4 -0
  51. data/spec/data.rb +89 -0
  52. data/spec/introspection/full_spec.rb +12 -0
  53. data/spec/introspection/simple_spec.rb +153 -0
  54. data/spec/language/parser_spec.rb +73 -0
  55. data/spec/schema.rb +145 -0
  56. data/spec/spec_helper.rb +99 -0
  57. data/spec/type/enum_spec.rb +27 -0
  58. data/spec/type/input_object_spec.rb +21 -0
  59. data/spec/type/list_spec.rb +16 -0
  60. data/spec/type/non_null_spec.rb +22 -0
  61. data/spec/type/scalar_type_spec.rb +117 -0
  62. data/spec/type/schema_spec.rb +13 -0
  63. metadata +202 -0
@@ -0,0 +1,11 @@
1
+ module GraphQL
2
+ module Language
3
+ Argument = Struct.new('Argument', :name, :value) do
4
+
5
+ def materialize(field_argument_type, values)
6
+ value.materialize(field_argument_type, values)
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module GraphQL
2
+ module Language
3
+ Directive = Struct.new('Directive', :name, :arguments)
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ module GraphQL
2
+ module Language
3
+ Document = Struct.new('Document', :definitions) do
4
+
5
+ def operations
6
+ @operations ||= definitions.select { |definition| definition.is_a?(OperationDefinition) }
7
+ end
8
+
9
+ def operation(name)
10
+ operations.find { |operation| operation.name == name }
11
+ end
12
+
13
+ def fragments
14
+ @fragments ||= definitions.select { |definition| definition.is_a?(FragmentDefinition) }
15
+ end
16
+
17
+ def fragment(name)
18
+ fragments.find { |fragment| fragment.name == name }
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,55 @@
1
+ module GraphQL
2
+ module Language
3
+ Field = Struct.new('Field', :alias, :name, :arguments, :directives, :selection_set) do
4
+
5
+ # GraphQL Specification
6
+ # 6.3 Evaluate selection sets
7
+ # CollectFields
8
+ # responseKey implementation
9
+ #
10
+ def key
11
+ self.alias || self.name
12
+ end
13
+
14
+ # GraphQL Specification
15
+ # 6.4.1 Field entries
16
+ # ResolveFieldOnObject implementation
17
+ # objectType, object, firstField = self
18
+ # + context[document, schema, root]
19
+ #
20
+ # TODO: think of should or shouldn't we pass self as fourth parameter
21
+ #
22
+ def resolve(context, object_type, object)
23
+ arguments = [
24
+ object,
25
+ materialize_arguments(object_type, context[:variables]),
26
+ context
27
+ ]
28
+
29
+ resolve = schema_field(object_type).resolve
30
+ arguments = arguments.slice(0, resolve.arity) if resolve.arity >= 0
31
+
32
+ resolve.call(*arguments)
33
+ end
34
+
35
+
36
+ def materialize_arguments(object_type, variables)
37
+ schema_field(object_type).args.reduce({}) do |memo, field_argument|
38
+ argument = arguments.find { |argument| argument.name == field_argument.name }
39
+ memo[argument.name.to_sym] = argument.materialize(field_argument.type, variables) unless argument.nil?
40
+ memo
41
+ end
42
+ end
43
+
44
+ def schema_field(object_type)
45
+ case
46
+ when GraphQL::Introspection.meta_field?(name)
47
+ GraphQL::Introspection.meta_field(name)
48
+ else
49
+ object_type.field(name)
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,23 @@
1
+ module GraphQL
2
+ module Language
3
+ FragmentDefinition = Struct.new('FragmentDefinition', :name, :type_condition, :directives, :selection_set) do
4
+
5
+ # GraphQL Specification
6
+ # 6.3 Evaluating selection sets
7
+ # doesFragmentTypeApply implementation
8
+ # objectType, fragmentType = self.type_condition => Schema.type
9
+ # + context[schema, document]
10
+ #
11
+ def apply?(context, object_type)
12
+ type = context[:schema].type(type_condition.type)
13
+
14
+ return type == object_type if type.is_a?(GraphQLObjectType)
15
+
16
+ return type.possible_type?(object_type) if type.is_a?(GraphQLInterfaceType)
17
+
18
+ return type.possible_type?(object_type) if type.is_a?(GraphQLUnionType)
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ module GraphQL
2
+ module Language
3
+ FragmentSpread = Struct.new('FragmentSpread', :name, :directives)
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ module GraphQL
2
+ module Language
3
+ InlineFragment = Struct.new('InlineFragment', :type_condition, :directives, :selection_set) do
4
+
5
+ # GraphQL Specification
6
+ # 6.3 Evaluating selection sets
7
+ # doesFragmentTypeApply implementation
8
+ # objectType, fragmentType = self.type_condition => Schema.type
9
+ # + context[schema, document]
10
+ #
11
+ def apply?(context, object_type)
12
+ type = context[:schema].type(type_condition.type)
13
+
14
+ return type == object_type if type.is_a?(GraphQLObjectType)
15
+
16
+ return type.possible_type?(object_type) if type.is_a?(GraphQLInterfaceType)
17
+
18
+ return type.possible_type?(object_type) if type.is_a?(GraphQLUnionType)
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ module GraphQL
2
+ module Language
3
+ ListType = Struct.new('ListType', :type) do
4
+
5
+ def named_type
6
+ type.respond_to?(:named_type) ? type.named_type : type
7
+ end
8
+
9
+ def materialize(schema)
10
+ GraphQLList.new(type.materialize(schema))
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ module GraphQL
2
+ module Language
3
+ Name = Struct.new('Name', :value)
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ module GraphQL
2
+ module Language
3
+ NamedType = Struct.new('NamedType', :type) do
4
+
5
+ def named_type
6
+ type
7
+ end
8
+
9
+ def materialize(schema)
10
+ schema.type(type)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module GraphQL
2
+ module Language
3
+ NonNullType = Struct.new('NonNullType', :type) do
4
+
5
+ def named_type
6
+ type.respond_to?(:named_type) ? type.named_type : type
7
+ end
8
+
9
+ def materialize(schema)
10
+ GraphQLNonNull.new(type.materialize(schema))
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ module GraphQL
2
+ module Language
3
+ OperationDefinition = Struct.new('OperationDefinition', :type, :name, :variable_definitions, :directives, :selection_set) do
4
+
5
+
6
+ def evaluate(context)
7
+ context[:variables] = materialize_variables(context[:schema], context[:params])
8
+ type == 'mutation' ? execute_serially(context) : execute(context)
9
+ end
10
+
11
+
12
+ def execute(context)
13
+ selection_set.evaluate(context, context[:schema].query_type, context[:root])
14
+ end
15
+
16
+
17
+ def execute_serially(context)
18
+ raise "Not implemented. Yet."
19
+ end
20
+
21
+
22
+ def materialize_variables(schema, params)
23
+ variable_definitions.reduce({}) do |memo, variable_definition|
24
+ name = variable_definition.variable.name.to_sym
25
+ memo[name] = variable_definition.materialize(schema, params[name])
26
+ memo
27
+ end
28
+ end
29
+
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,331 @@
1
+ require 'parslet'
2
+
3
+ module GraphQL
4
+ module Language
5
+
6
+ class Parser < Parslet::Parser
7
+
8
+ rule(:root) { document }
9
+
10
+ # Source Character
11
+ #
12
+
13
+ rule(:source_character) { any }
14
+
15
+ # Ignored Tokens
16
+ #
17
+
18
+ rule(:ignored) { white_space | line_terminator | comment | comma }
19
+
20
+ rule(:ignored?) { ignored.repeat(0) }
21
+
22
+ rule(:ignored!) { ignored.repeat(1) }
23
+
24
+ rule(:white_space) { match('[\u0009\u000b\u000c\u0020\u00a0]') }
25
+
26
+ rule(:line_terminator) { match('[\u000a\u000d\u2028\u2029]') }
27
+
28
+ rule(:comment) { str('#') >> comment_char.repeat }
29
+
30
+ rule(:comment_char) { line_terminator.absent? >> source_character }
31
+
32
+ rule(:comma) { str(',') }
33
+
34
+ # Lexical Tokens
35
+ #
36
+
37
+ rule(:token) { punctuator | name | int_value | float_value | string_value }
38
+
39
+ rule(:punctuator) { match('[!$():=@[]{|}]') | str('...') }
40
+
41
+ rule(:integer_part) do
42
+ negative_sign.maybe >> str('0') |
43
+ negative_sign.maybe >> non_zero_digit >> digit.repeat
44
+ end
45
+
46
+ rule(:negative_sign) { str('-') }
47
+
48
+ rule(:digit) { match('[0-9]') }
49
+
50
+ rule(:non_zero_digit) { str('0').absent? >> digit }
51
+
52
+ rule(:fractional_part) { str('.') >> digit.repeat(1) }
53
+
54
+ rule(:exponent_part) { exponent_indicator >> sign.maybe >> digit.repeat(1) }
55
+
56
+ rule(:exponent_indicator) { match('[eE]') }
57
+
58
+ rule(:sign) { match('-+') }
59
+
60
+ rule(:string_character) do
61
+ (str('"') | str('\\') | line_terminator).absent? >> source_character |
62
+ str('\\') >> escaped_unicode |
63
+ str('\\') >> escaped_character
64
+ end
65
+
66
+ rule(:escaped_unicode) { str('u') >> match('[a-fA-F0-9]').repeat(4) }
67
+
68
+ rule(:escaped_character) { match('["\\/bfnrt]') }
69
+
70
+ rule(:name) do
71
+ (match('[_a-zA-Z]') >> match('[_a-zA-Z0-9]').repeat).as(:name)
72
+ end
73
+
74
+ rule(:string_value) do
75
+ str('"') >> string_character.repeat.as(:string_value) >> str('"')
76
+ end
77
+
78
+ rule(:int_value) do
79
+ integer_part.as(:int_value)
80
+ end
81
+
82
+ rule(:float_value) do
83
+ (
84
+ integer_part >> fractional_part >> exponent_part |
85
+ integer_part >> fractional_part |
86
+ integer_part >> exponent_part
87
+ ).as(:float_value)
88
+ end
89
+
90
+ # Query Document
91
+ #
92
+
93
+ rule(:document) do
94
+ ignored? >> definition.repeat(1).as(:document) >> ignored?
95
+ end
96
+
97
+ rule(:definition) do
98
+ ignored? >> (operation_definition | fragment_definition) >> ignored?
99
+ end
100
+
101
+ rule(:operation_definition) do
102
+ ignored? >> (
103
+ selection_set.as(:selection_set) |
104
+ operation_type.as(:type) >>
105
+ name.as(:name) >>
106
+ variable_definitions.maybe.as(:variable_definitions) >>
107
+ directives.maybe.as(:directives) >>
108
+ selection_set.as(:selection_set)
109
+ ).as(:operation_definition) >> ignored?
110
+ end
111
+
112
+ rule(:operation_type) do
113
+ ignored? >> (str('query') | str('mutation')).as(:name) >> ignored?
114
+ end
115
+
116
+ rule(:selection_set) do
117
+ ignored? >> (str('{') >> selection.repeat(1).as(:selections) >> str('}')).as(:selection_set) >> ignored?
118
+ end
119
+
120
+ rule(:selection) do
121
+ ignored? >> (field | fragment_spread | inline_fragment) >> ignored?
122
+ end
123
+
124
+ rule(:field) do
125
+ ignored? >> (
126
+ field_alias.maybe.as(:alias) >>
127
+ name.as(:name) >>
128
+ arguments.maybe.as(:arguments) >>
129
+ directives.maybe.as(:directives) >>
130
+ selection_set.maybe.as(:selection_set)
131
+ ).as(:field) >> ignored?
132
+ end
133
+
134
+ rule(:field_alias) do
135
+ ignored? >> name >> str(':') >> ignored?
136
+ end
137
+
138
+ rule(:arguments) do
139
+ ignored? >> (
140
+ str('(') >> argument.repeat(1) >> str(')')
141
+ ) >> ignored?
142
+ end
143
+
144
+ rule(:argument) do
145
+ ignored? >> (
146
+ name.as(:name) >>
147
+ str(':') >>
148
+ value.as(:value)
149
+ ).as(:argument) >> ignored?
150
+ end
151
+
152
+ rule(:fragment_spread) do
153
+ ignored? >> (
154
+ str('...') >>
155
+ fragment_name.as(:name) >>
156
+ directives.maybe.as(:directives)
157
+ ).as(:fragment_spread) >> ignored?
158
+ end
159
+
160
+ rule(:inline_fragment) do
161
+ ignored? >> (
162
+ str('...') >>
163
+ ignored? >>
164
+ str('on') >>
165
+ ignored? >>
166
+ type_condition.as(:type_condition) >>
167
+ directives.maybe.as(:directives) >>
168
+ selection_set.as(:selection_set)
169
+ ).as(:inline_fragment) >> ignored?
170
+ end
171
+
172
+ rule(:fragment_definition) do
173
+ ignored? >> (
174
+ str('fragment') >>
175
+ fragment_name.as(:name) >>
176
+ str('on') >>
177
+ type_condition.as(:type_condition) >>
178
+ directives.maybe.as(:directives) >>
179
+ selection_set.as(:selection_set)
180
+ ).as(:fragment_definition) >> ignored?
181
+ end
182
+
183
+ rule(:fragment_name) do
184
+ ignored? >> (str('on') >> ignored).absent? >> name.as(:name) >> ignored?
185
+ end
186
+
187
+ rule(:type_condition) do
188
+ ignored? >> named_type >> ignored?
189
+ end
190
+
191
+ rule(:value) do
192
+ ignored? >> (
193
+ variable |
194
+ (
195
+ float_value |
196
+ int_value |
197
+ string_value |
198
+ boolean_value |
199
+ enum_value |
200
+ list_value |
201
+ object_value
202
+ ).as(:value)
203
+ ) >> ignored?
204
+ end
205
+
206
+ rule(:value_const) do
207
+ ignored? >> (
208
+ float_value |
209
+ int_value |
210
+ string_value |
211
+ boolean_value |
212
+ enum_value |
213
+ list_value_const |
214
+ object_value_const
215
+ ).as(:value) >> ignored?
216
+ end
217
+
218
+ rule(:boolean_value) do
219
+ ignored? >> (
220
+ str('true') |
221
+ str('false')
222
+ ).as(:boolean_value) >> ignored?
223
+ end
224
+
225
+ rule(:enum_value) do
226
+ ignored? >> (
227
+ (
228
+ str('true') |
229
+ str('false') |
230
+ str('null')
231
+ ).absent? >> name
232
+ ).as(:enum_value) >> ignored?
233
+ end
234
+
235
+ rule(:list_value) do
236
+ ignored? >> (
237
+ str('[') >>
238
+ value.repeat(1) >>
239
+ str(']')
240
+ ).as(:list_value) >> ignored?
241
+ end
242
+
243
+ rule(:list_value_const) do
244
+ ignored? >> (
245
+ str('[') >>
246
+ value_const.repeat(1) >>
247
+ str(']')
248
+ ).as(:list_value) >> ignored?
249
+ end
250
+
251
+ rule(:object_value) do
252
+ ignored? >> (
253
+ str('{') >>
254
+ object_field.repeat(1) >>
255
+ str('}')
256
+ ).as(:object_value) >> ignored?
257
+ end
258
+
259
+ rule(:object_value_const) do
260
+ ignored? >> (
261
+ str('{') >>
262
+ object_field_const.repeat(1) >>
263
+ str('}')
264
+ ).as(:object_value) >> ignored?
265
+ end
266
+
267
+ rule(:object_field) do
268
+ ignored? >> name.as(:name) >> str(':') >> value.as(:value) >> ignored?
269
+ end
270
+
271
+ rule(:object_field_const) do
272
+ ignored? >> name.as(:name) >> str(':') >> value_const.as(:value) >> ignored?
273
+ end
274
+
275
+ rule(:variable_definitions) do
276
+ ignored? >> str('(') >> variable_definition.repeat(1) >> str(')') >> ignored?
277
+ end
278
+
279
+ rule(:variable_definition) do
280
+ ignored? >> (
281
+ variable.as(:variable) >>
282
+ str(':') >>
283
+ type.as(:type) >>
284
+ default_value.maybe.as(:default_value)
285
+ ).as(:variable_definition) >> ignored?
286
+ end
287
+
288
+ rule(:variable) do
289
+ ignored? >> (
290
+ str('$') >> name
291
+ ).as(:variable) >> ignored?
292
+ end
293
+
294
+ rule(:default_value) do
295
+ ignored? >> str('=') >> value_const >> ignored?
296
+ end
297
+
298
+ rule(:type) do
299
+ ignored? >> (non_null_type | list_type | named_type) >> ignored?
300
+ end
301
+
302
+ rule(:named_type) do
303
+ ignored? >> name.as(:named_type) >> ignored?
304
+ end
305
+
306
+ rule(:list_type) do
307
+ ignored? >> (
308
+ str('[') >> type >> str(']')
309
+ ).as(:list_type) >> ignored?
310
+ end
311
+
312
+ rule(:non_null_type) do
313
+ ignored? >> (
314
+ list_type >> str('!') | named_type >> str('!')
315
+ ).as(:non_null_type) >> ignored?
316
+ end
317
+
318
+ rule(:directives) do
319
+ ignored? >> directive.repeat(1) >> ignored?
320
+ end
321
+
322
+ rule(:directive) do
323
+ ignored? >> (
324
+ str('@') >> name.as(:name) >> arguments.maybe.as(:arguments)
325
+ ).as(:directive) >> ignored?
326
+ end
327
+
328
+ end
329
+
330
+ end
331
+ end
@@ -0,0 +1,107 @@
1
+ module GraphQL
2
+ module Language
3
+ SelectionSet = Struct.new("SelectionSet", :selections) do
4
+
5
+ def empty?
6
+ selections.empty?
7
+ end
8
+
9
+ # GraphQL Specification
10
+ # 6.4.1 Field entries
11
+ # GetFieldEntry implementation
12
+ # objectType, object, - fields
13
+ # + context[schema, document]
14
+ #
15
+ # TODO: think of way to have error accessor at this point. Executor?
16
+ #
17
+ def evaluate(context, object_type, object)
18
+ grouped_fields = collect_fields(context, object_type)
19
+
20
+ #
21
+ # TODO: Merge equal fields
22
+ #
23
+
24
+ grouped_fields.reduce({}) do |memo, (key, fields)|
25
+ field = fields.first
26
+ field_definition = case
27
+ when GraphQL::Introspection.meta_field?(field.name)
28
+ GraphQL::Introspection.meta_field(field.name)
29
+ else
30
+ object_type.field(field.name)
31
+ end
32
+
33
+ unless field_definition.nil?
34
+ resolve_context = context.merge({ parent_type: object_type })
35
+
36
+ resolved_object = field.resolve(context, object_type, object)
37
+ selection_set = merge_selection_sets(fields)
38
+ memo[key.to_sym] = Executor::FutureCompleter.complete_value(context, field_definition.type, resolved_object, selection_set)
39
+ end
40
+
41
+ memo
42
+ end
43
+ end
44
+
45
+ # GraphQL Specification
46
+ # 6.3 Evaluate selection sets
47
+ # CollectFields implementation
48
+ # objectType, selectionSet = self, visitedFragments = []
49
+ # + context[schema, document]
50
+ #
51
+ def collect_fields(context, object_type, visited_fragments = [])
52
+ memo = {}
53
+
54
+ selections.each do |selection|
55
+
56
+ case selection
57
+
58
+ when Field
59
+ # TODO: Directives
60
+ (memo[selection.key] ||= []) << selection
61
+
62
+ when FragmentSpread
63
+ next if visited_fragments.include?(selection.name)
64
+
65
+ visited_fragments << selection.name
66
+
67
+ fragment = context[:document].fragment(selection.name)
68
+
69
+ next if fragment.nil?
70
+
71
+ next unless fragment.apply?(context, object_type)
72
+
73
+ fragment.selection_set.collect_fields(context, object_type).each do |key, fields|
74
+ memo[key] = (memo[key] ||= []).concat(fields)
75
+ end
76
+
77
+ when InlineFragment
78
+ next unless selection.apply?(context, object_type)
79
+
80
+ selection.selection_set.collect_fields(context, object_type).each do |key, fields|
81
+ memo[key] = (memo[key] ||= []).concat(fields)
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+
88
+ memo
89
+ end
90
+
91
+ # GraphQL Specification
92
+ # 6.4.1 Field entries
93
+ # MergeSelectionSets implementations
94
+ # fields
95
+ #
96
+ def merge_selection_sets(fields)
97
+ selections = fields.reduce([]) do |memo, field|
98
+ memo.concat field.selection_set.selections unless field.selection_set.nil? || field.selection_set.empty?
99
+ memo
100
+ end
101
+
102
+ SelectionSet.new(selections)
103
+ end
104
+
105
+ end
106
+ end
107
+ end