graphql 1.7.7 → 1.7.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/enum_type.rb +1 -1
  3. data/lib/graphql/field.rb +10 -1
  4. data/lib/graphql/input_object_type.rb +3 -1
  5. data/lib/graphql/introspection/arguments_field.rb +1 -0
  6. data/lib/graphql/introspection/enum_values_field.rb +1 -0
  7. data/lib/graphql/introspection/fields_field.rb +1 -0
  8. data/lib/graphql/introspection/input_fields_field.rb +1 -0
  9. data/lib/graphql/introspection/interfaces_field.rb +1 -0
  10. data/lib/graphql/introspection/of_type_field.rb +1 -0
  11. data/lib/graphql/introspection/possible_types_field.rb +1 -0
  12. data/lib/graphql/introspection/schema_field.rb +1 -0
  13. data/lib/graphql/introspection/type_by_name_field.rb +1 -0
  14. data/lib/graphql/introspection/typename_field.rb +1 -0
  15. data/lib/graphql/language.rb +1 -0
  16. data/lib/graphql/language/document_from_schema_definition.rb +129 -37
  17. data/lib/graphql/language/generation.rb +3 -182
  18. data/lib/graphql/language/nodes.rb +12 -2
  19. data/lib/graphql/language/parser.rb +63 -55
  20. data/lib/graphql/language/parser.y +2 -1
  21. data/lib/graphql/language/printer.rb +351 -0
  22. data/lib/graphql/object_type.rb +1 -1
  23. data/lib/graphql/query/arguments.rb +27 -9
  24. data/lib/graphql/query/literal_input.rb +4 -1
  25. data/lib/graphql/schema/printer.rb +33 -266
  26. data/lib/graphql/tracing/scout_tracing.rb +2 -2
  27. data/lib/graphql/version.rb +1 -1
  28. data/spec/graphql/language/document_from_schema_definition_spec.rb +729 -296
  29. data/spec/graphql/language/generation_spec.rb +21 -186
  30. data/spec/graphql/language/nodes_spec.rb +21 -0
  31. data/spec/graphql/language/printer_spec.rb +203 -0
  32. data/spec/graphql/query/arguments_spec.rb +33 -11
  33. data/spec/graphql/schema/build_from_definition_spec.rb +13 -4
  34. data/spec/graphql/schema/printer_spec.rb +14 -14
  35. metadata +5 -2
@@ -32,7 +32,7 @@ module GraphQL
32
32
  # @return [Hash<String => GraphQL::Field>] Map String fieldnames to their {GraphQL::Field} implementations
33
33
 
34
34
  # @!attribute mutation
35
- # @return [GraphQL::Relay::Mutation, nil] The mutation this field was derived from, if it was derived from a mutation
35
+ # @return [GraphQL::Relay::Mutation, nil] The mutation this object type was derived from, if it is an auto-generated payload type.
36
36
 
37
37
  def initialize
38
38
  super
@@ -12,8 +12,8 @@ module GraphQL
12
12
  argument_owner.arguments_class = Class.new(self) do
13
13
  self.argument_definitions = argument_definitions
14
14
 
15
- def initialize(values)
16
- super(values, argument_definitions: self.class.argument_definitions)
15
+ def initialize(values, defaults_used)
16
+ super(values, argument_definitions: self.class.argument_definitions, defaults_used: defaults_used)
17
17
  end
18
18
 
19
19
  argument_definitions.each do |_arg_name, arg_definition|
@@ -36,13 +36,16 @@ module GraphQL
36
36
  end
37
37
  end
38
38
 
39
- def initialize(values, argument_definitions:)
39
+ def initialize(values, argument_definitions:, defaults_used:)
40
40
  @argument_values = values.inject({}) do |memo, (inner_key, inner_value)|
41
- arg_defn = argument_definitions[inner_key.to_s]
41
+ arg_name = inner_key.to_s
42
+
43
+ arg_defn = argument_definitions[arg_name]
44
+ arg_default_used = defaults_used.include?(arg_name)
42
45
 
43
46
  arg_value = wrap_value(inner_value, arg_defn.type)
44
47
  string_key = arg_defn.expose_as
45
- memo[string_key] = ArgumentValue.new(string_key, arg_value, arg_defn)
48
+ memo[string_key] = ArgumentValue.new(string_key, arg_value, arg_defn, arg_default_used)
46
49
  memo
47
50
  end
48
51
  end
@@ -61,6 +64,13 @@ module GraphQL
61
64
  @argument_values.key?(key_s)
62
65
  end
63
66
 
67
+ # @param key [String, Symbol] name of value to access
68
+ # @return [Boolean] true if the argument default was passed as the argument value to the resolver
69
+ def default_used?(key)
70
+ key_s = key.is_a?(String) ? key : key.to_s
71
+ @argument_values.fetch(key_s, NULL_ARGUMENT_VALUE).default_used?
72
+ end
73
+
64
74
  # Get the hash of all values, with stringified keys
65
75
  # @return [Hash] the stringified hash
66
76
  def to_h
@@ -85,7 +95,7 @@ module GraphQL
85
95
  end
86
96
  end
87
97
 
88
- NO_ARGS = self.new({}, argument_definitions: [])
98
+ NO_ARGS = self.new({}, argument_definitions: [], defaults_used: Set.new)
89
99
 
90
100
  class << self
91
101
  attr_accessor :argument_definitions
@@ -95,14 +105,22 @@ module GraphQL
95
105
 
96
106
  class ArgumentValue
97
107
  attr_reader :key, :value, :definition
98
- def initialize(key, value, definition)
108
+ attr_writer :default_used
109
+
110
+ def initialize(key, value, definition, default_used)
99
111
  @key = key
100
112
  @value = value
101
113
  @definition = definition
114
+ @default_used = default_used
115
+ end
116
+
117
+ # @return [Boolean] true if the argument default was passed as the argument value to the resolver
118
+ def default_used?
119
+ @default_used
102
120
  end
103
121
  end
104
122
 
105
- NULL_ARGUMENT_VALUE = ArgumentValue.new(nil, nil, nil)
123
+ NULL_ARGUMENT_VALUE = ArgumentValue.new(nil, nil, nil, nil)
106
124
 
107
125
  def wrap_value(value, arg_defn_type)
108
126
  if value.nil?
@@ -115,7 +133,7 @@ module GraphQL
115
133
  wrap_value(value, arg_defn_type.of_type)
116
134
  when GraphQL::InputObjectType
117
135
  if value.is_a?(Hash)
118
- arg_defn_type.arguments_class.new(value)
136
+ arg_defn_type.arguments_class.new(value, Set.new)
119
137
  else
120
138
  value
121
139
  end
@@ -49,6 +49,8 @@ module GraphQL
49
49
  def self.from_arguments(ast_arguments, argument_owner, variables)
50
50
  context = variables ? variables.context : nil
51
51
  values_hash = {}
52
+ defaults_used = Set.new
53
+
52
54
  indexed_arguments = case ast_arguments
53
55
  when Hash
54
56
  ast_arguments
@@ -93,6 +95,7 @@ module GraphQL
93
95
  # then add the default value.
94
96
  if arg_defn.default_value? && !values_hash.key?(arg_name)
95
97
  value = arg_defn.default_value
98
+ defaults_used << arg_name
96
99
  # `context` isn't present when pre-calculating defaults
97
100
  if context
98
101
  value = arg_defn.prepare(value, context)
@@ -105,7 +108,7 @@ module GraphQL
105
108
  end
106
109
  end
107
110
 
108
- argument_owner.arguments_class.new(values_hash)
111
+ argument_owner.arguments_class.new(values_hash, defaults_used)
109
112
  end
110
113
  end
111
114
  end
@@ -36,7 +36,7 @@ module GraphQL
36
36
  # printer = GraphQL::Schema::Printer.new(MySchema)
37
37
  # puts printer.print_type(post_type)
38
38
  #
39
- class Printer
39
+ class Printer < GraphQL::Language::Printer
40
40
  attr_reader :schema, :warden
41
41
 
42
42
  # @param schema [GraphQL::Schema]
@@ -45,21 +45,32 @@ module GraphQL
45
45
  # @param except [<#call(member, ctx)>]
46
46
  # @param introspection [Boolean] Should include the introspection types in the string?
47
47
  def initialize(schema, context: nil, only: nil, except: nil, introspection: false)
48
- @schema = schema
49
- @context = context
48
+ @document_from_schema = GraphQL::Language::DocumentFromSchemaDefinition.new(
49
+ schema,
50
+ context: context,
51
+ only: only,
52
+ except: except,
53
+ include_introspection_types: introspection,
54
+ )
55
+
56
+ @document = @document_from_schema.document
50
57
 
51
- blacklist = build_blacklist(only, except, introspection: introspection)
52
- filter = GraphQL::Filter.new(except: blacklist)
53
- @warden = GraphQL::Schema::Warden.new(filter, schema: @schema, context: @context)
58
+ @schema = schema
54
59
  end
55
60
 
56
61
  # Return the GraphQL schema string for the introspection type system
57
62
  def self.print_introspection_schema
58
63
  query_root = ObjectType.define(name: "Root")
59
64
  schema = GraphQL::Schema.define(query: query_root)
60
- blacklist = ->(m, ctx) { m == query_root }
61
- printer = new(schema, except: blacklist, introspection: true)
62
- printer.print_schema
65
+
66
+ introspection_schema_ast = GraphQL::Language::DocumentFromSchemaDefinition.new(
67
+ schema,
68
+ except: ->(member, _) { member.name == "Root" },
69
+ include_introspection_types: true,
70
+ include_built_in_directives: true,
71
+ ).document
72
+
73
+ introspection_schema_ast.to_query_string(printer: IntrospectionPrinter.new)
63
74
  end
64
75
 
65
76
  # Return a GraphQL schema string for the defined types in the schema
@@ -74,277 +85,33 @@ module GraphQL
74
85
 
75
86
  # Return a GraphQL schema string for the defined types in the schema
76
87
  def print_schema
77
- directive_definitions = warden.directives.map { |directive| print_directive(directive) }
78
-
79
- printable_types = warden.types.reject(&:default_scalar?)
80
-
81
- type_definitions = printable_types
82
- .sort_by(&:name)
83
- .map { |type| print_type(type) }
84
-
85
- [print_schema_definition].compact
86
- .concat(directive_definitions)
87
- .concat(type_definitions).join("\n\n")
88
+ print(@document)
88
89
  end
89
90
 
90
91
  def print_type(type)
91
- TypeKindPrinters::STRATEGIES.fetch(type.kind).print(warden, type)
92
+ node = @document_from_schema.build_object_type_node(type)
93
+ print(node)
92
94
  end
93
95
 
94
- private
95
-
96
- # By default, these are included in a schema printout
97
- IS_USER_DEFINED_MEMBER = ->(member) {
98
- case member
99
- when GraphQL::BaseType
100
- !member.introspection?
101
- when GraphQL::Directive
102
- !member.default_directive?
103
- else
104
- true
105
- end
106
- }
107
-
108
- private_constant :IS_USER_DEFINED_MEMBER
96
+ def print_directive(directive)
97
+ if directive.name == "deprecated"
98
+ reason = directive.arguments.find { |arg| arg.name == "reason" }
109
99
 
110
- def build_blacklist(only, except, introspection:)
111
- if introspection
112
- if only
113
- ->(m, ctx) { !only.call(m, ctx) }
114
- elsif except
115
- except
100
+ if reason.value == GraphQL::Directive::DEFAULT_DEPRECATION_REASON
101
+ "@deprecated"
116
102
  else
117
- ->(m, ctx) { false }
103
+ "@deprecated(reason: \"#{reason.value}\")"
118
104
  end
119
105
  else
120
- if only
121
- ->(m, ctx) { !(IS_USER_DEFINED_MEMBER.call(m) && only.call(m, ctx)) }
122
- elsif except
123
- ->(m, ctx) { !IS_USER_DEFINED_MEMBER.call(m) || except.call(m, ctx) }
124
- else
125
- ->(m, ctx) { !IS_USER_DEFINED_MEMBER.call(m) }
126
- end
106
+ super
127
107
  end
128
108
  end
129
109
 
130
- def print_schema_definition
131
- if (schema.query.nil? || schema.query.name == 'Query') &&
132
- (schema.mutation.nil? || schema.mutation.name == 'Mutation') &&
133
- (schema.subscription.nil? || schema.subscription.name == 'Subscription')
134
- return
110
+ class IntrospectionPrinter < GraphQL::Language::Printer
111
+ def print_schema_definition(schema)
112
+ "schema {\n query: Root\n}"
135
113
  end
136
-
137
- operations = [:query, :mutation, :subscription].map do |operation_type|
138
- object_type = schema.public_send(operation_type)
139
- # Special treatment for the introspection schema, which prints `{ query: "Root" }`
140
- if object_type && (warden.get_type(object_type.name) || (object_type.name == "Root" && schema.query == object_type))
141
- " #{operation_type}: #{object_type.name}\n"
142
- else
143
- nil
144
- end
145
- end.compact.join
146
- "schema {\n#{operations}}"
147
- end
148
-
149
- def print_directive(directive)
150
- TypeKindPrinters::DirectivePrinter.print(warden, directive)
151
- end
152
-
153
- module TypeKindPrinters
154
- module DeprecatedPrinter
155
- def print_deprecated(field_or_enum_value)
156
- return unless field_or_enum_value.deprecation_reason
157
-
158
- case field_or_enum_value.deprecation_reason
159
- when nil
160
- ''
161
- when '', GraphQL::Directive::DEFAULT_DEPRECATION_REASON
162
- ' @deprecated'
163
- else
164
- " @deprecated(reason: #{field_or_enum_value.deprecation_reason.to_s.inspect})"
165
- end
166
- end
167
- end
168
-
169
- module DescriptionPrinter
170
- def print_description(definition, indentation='', first_in_block=true)
171
- return '' unless definition.description
172
-
173
- description = indentation != '' && !first_in_block ? "\n".dup : "".dup
174
- description << GraphQL::Language::Comments.commentize(definition.description, indent: indentation)
175
- end
176
- end
177
-
178
- module ArgsPrinter
179
- include DescriptionPrinter
180
- def print_args(warden, field, indentation = '')
181
- arguments = warden.arguments(field)
182
- return if arguments.empty?
183
-
184
- if arguments.all?{ |arg| !arg.description }
185
- return "(#{arguments.map{ |arg| print_input_value(arg) }.join(", ")})"
186
- end
187
-
188
- out = "(\n".dup
189
- out << arguments.sort_by(&:name).map.with_index{ |arg, i|
190
- "#{print_description(arg, " #{indentation}", i == 0)} #{indentation}"\
191
- "#{print_input_value(arg)}"
192
- }.join("\n")
193
- out << "\n#{indentation})"
194
- end
195
-
196
- def print_input_value(arg)
197
- if arg.default_value?
198
- default_string = " = #{print_value(arg.default_value, arg.type)}"
199
- else
200
- default_string = nil
201
- end
202
-
203
- "#{arg.name}: #{arg.type.to_s}#{default_string}"
204
- end
205
-
206
- def print_value(value, type)
207
- case type
208
- when FLOAT_TYPE
209
- return 'null' if value.nil?
210
- value.to_f.inspect
211
- when INT_TYPE
212
- return 'null' if value.nil?
213
- value.to_i.inspect
214
- when BOOLEAN_TYPE
215
- return 'null' if value.nil?
216
- (!!value).inspect
217
- when ScalarType, ID_TYPE, STRING_TYPE
218
- return 'null' if value.nil?
219
- value.to_s.inspect
220
- when EnumType
221
- return 'null' if value.nil?
222
- type.coerce_isolated_result(value)
223
- when InputObjectType
224
- return 'null' if value.nil?
225
- fields = value.to_h.map{ |field_name, field_value|
226
- field_type = type.input_fields.fetch(field_name.to_s).type
227
- "#{field_name}: #{print_value(field_value, field_type)}"
228
- }.join(", ")
229
- "{#{fields}}"
230
- when NonNullType
231
- print_value(value, type.of_type)
232
- when ListType
233
- return 'null' if value.nil?
234
- "[#{value.to_a.map{ |v| print_value(v, type.of_type) }.join(", ")}]"
235
- else
236
- raise NotImplementedError, "Unexpected value type #{type.inspect}"
237
- end
238
- end
239
- end
240
-
241
- module FieldPrinter
242
- include DeprecatedPrinter
243
- include ArgsPrinter
244
- include DescriptionPrinter
245
- def print_fields(warden, type)
246
- fields = warden.fields(type)
247
- fields.sort_by(&:name).map.with_index { |field, i|
248
- "#{print_description(field, ' ', i == 0)}"\
249
- " #{field.name}#{print_args(warden, field, ' ')}: #{field.type}#{print_deprecated(field)}"
250
- }.join("\n")
251
- end
252
- end
253
-
254
- class DirectivePrinter
255
- extend ArgsPrinter
256
- extend DescriptionPrinter
257
- def self.print(warden, directive)
258
- "#{print_description(directive)}"\
259
- "directive @#{directive.name}#{print_args(warden, directive)} "\
260
- "on #{directive.locations.join(' | ')}"
261
- end
262
- end
263
-
264
- class ScalarPrinter
265
- extend DescriptionPrinter
266
- def self.print(warden, type)
267
- "#{print_description(type)}"\
268
- "scalar #{type.name}"
269
- end
270
- end
271
-
272
- class ObjectPrinter
273
- extend FieldPrinter
274
- extend DescriptionPrinter
275
- def self.print(warden, type)
276
- interfaces = warden.interfaces(type)
277
- if interfaces.any?
278
- implementations = " implements #{interfaces.sort_by(&:name).map(&:to_s).join(", ")}"
279
- else
280
- implementations = nil
281
- end
282
-
283
- "#{print_description(type)}"\
284
- "type #{type.name}#{implementations} {\n"\
285
- "#{print_fields(warden, type)}\n"\
286
- "}"
287
- end
288
- end
289
-
290
- class InterfacePrinter
291
- extend FieldPrinter
292
- extend DescriptionPrinter
293
- def self.print(warden, type)
294
- "#{print_description(type)}"\
295
- "interface #{type.name} {\n#{print_fields(warden, type)}\n}"
296
- end
297
- end
298
-
299
- class UnionPrinter
300
- extend DescriptionPrinter
301
- def self.print(warden, type)
302
- possible_types = warden.possible_types(type)
303
- "#{print_description(type)}"\
304
- "union #{type.name} = #{possible_types.sort_by(&:name).map(&:to_s).join(" | ")}"
305
- end
306
- end
307
-
308
- class EnumPrinter
309
- extend DeprecatedPrinter
310
- extend DescriptionPrinter
311
- def self.print(warden, type)
312
- enum_values = warden.enum_values(type)
313
-
314
- values = enum_values.sort_by(&:name).map.with_index { |v, i|
315
- "#{print_description(v, ' ', i == 0)}"\
316
- " #{v.name}#{print_deprecated(v)}"
317
- }.join("\n")
318
-
319
- "#{print_description(type)}"\
320
- "enum #{type.name} {\n#{values}\n}"
321
- end
322
- end
323
-
324
- class InputObjectPrinter
325
- extend FieldPrinter
326
- extend DescriptionPrinter
327
- def self.print(warden, type)
328
- arguments = warden.arguments(type)
329
- fields = arguments.sort_by(&:name).map.with_index{ |field, i|
330
- "#{print_description(field, " ", i == 0)}"\
331
- " #{print_input_value(field)}"
332
- }.join("\n")
333
- "#{print_description(type)}"\
334
- "input #{type.name} {\n#{fields}\n}"
335
- end
336
- end
337
-
338
- STRATEGIES = {
339
- GraphQL::TypeKinds::SCALAR => ScalarPrinter,
340
- GraphQL::TypeKinds::OBJECT => ObjectPrinter,
341
- GraphQL::TypeKinds::INTERFACE => InterfacePrinter,
342
- GraphQL::TypeKinds::UNION => UnionPrinter,
343
- GraphQL::TypeKinds::ENUM => EnumPrinter,
344
- GraphQL::TypeKinds::INPUT_OBJECT => InputObjectPrinter,
345
- }
346
114
  end
347
- private_constant :TypeKindPrinters
348
115
  end
349
116
  end
350
117
  end