graphql 1.7.7 → 1.7.8

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