graphql-rb 0.0.2

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 (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