graphql 2.0.5 → 2.0.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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

@@ -321,13 +321,13 @@ rule
321
321
 
322
322
  object_type_extension:
323
323
  /* TODO - This first one shouldn't be necessary but parser is getting confused */
324
- EXTEND TYPE name implements LCURLY field_definition_list RCURLY { result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: [], fields: val[5], position_source: val[0]) }
325
- | EXTEND TYPE name implements_opt directives_list_opt LCURLY field_definition_list RCURLY { result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: val[6], position_source: val[0]) }
324
+ EXTEND TYPE name implements field_definition_list_opt { result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: [], fields: val[4], position_source: val[0]) }
325
+ | EXTEND TYPE name implements_opt directives_list_opt field_definition_list_opt { result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: val[5], position_source: val[0]) }
326
326
  | EXTEND TYPE name implements_opt directives_list { result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: [], position_source: val[0]) }
327
327
  | EXTEND TYPE name implements { result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: [], fields: [], position_source: val[0]) }
328
328
 
329
329
  interface_type_extension:
330
- EXTEND INTERFACE name implements_opt directives_list_opt LCURLY field_definition_list RCURLY { result = make_node(:InterfaceTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: val[6], position_source: val[0]) }
330
+ EXTEND INTERFACE name implements_opt directives_list_opt field_definition_list_opt { result = make_node(:InterfaceTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: val[5], position_source: val[0]) }
331
331
  | EXTEND INTERFACE name implements_opt directives_list { result = make_node(:InterfaceTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: [], position_source: val[0]) }
332
332
  | EXTEND INTERFACE name implements { result = make_node(:InterfaceTypeExtension, name: val[2], interfaces: val[3], directives: [], fields: [], position_source: val[0]) }
333
333
 
@@ -355,8 +355,8 @@ rule
355
355
  }
356
356
 
357
357
  object_type_definition:
358
- description_opt TYPE name implements_opt directives_list_opt LCURLY field_definition_list RCURLY {
359
- result = make_node(:ObjectTypeDefinition, name: val[2], interfaces: val[3], directives: val[4], fields: val[6], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1])
358
+ description_opt TYPE name implements_opt directives_list_opt field_definition_list_opt {
359
+ result = make_node(:ObjectTypeDefinition, name: val[2], interfaces: val[3], directives: val[4], fields: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1])
360
360
  }
361
361
 
362
362
  implements_opt:
@@ -394,14 +394,18 @@ rule
394
394
  result = make_node(:FieldDefinition, name: val[1], arguments: val[2], type: val[4], directives: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1])
395
395
  }
396
396
 
397
- field_definition_list:
397
+ field_definition_list_opt:
398
398
  /* none */ { result = EMPTY_ARRAY }
399
+ | LCURLY field_definition_list RCURLY { result = val[1] }
400
+
401
+ field_definition_list:
402
+ /* none - this is not actually valid but graphql-ruby used to print this */ { result = EMPTY_ARRAY }
399
403
  | field_definition { result = [val[0]] }
400
404
  | field_definition_list field_definition { val[0] << val[1] }
401
405
 
402
406
  interface_type_definition:
403
- description_opt INTERFACE name implements_opt directives_list_opt LCURLY field_definition_list RCURLY {
404
- result = make_node(:InterfaceTypeDefinition, name: val[2], interfaces: val[3], directives: val[4], fields: val[6], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1])
407
+ description_opt INTERFACE name implements_opt directives_list_opt field_definition_list_opt {
408
+ result = make_node(:InterfaceTypeDefinition, name: val[2], interfaces: val[3], directives: val[4], fields: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1])
405
409
  }
406
410
 
407
411
  union_members:
@@ -267,12 +267,16 @@ module GraphQL
267
267
  end
268
268
 
269
269
  def print_field_definitions(fields)
270
- out = " {\n".dup
271
- fields.each.with_index do |field, i|
272
- out << print_description(field, indent: ' ', first_in_block: i == 0)
273
- out << " #{print_field_definition(field)}\n"
270
+ if fields.empty?
271
+ ""
272
+ else
273
+ out = " {\n".dup
274
+ fields.each.with_index do |field, i|
275
+ out << print_description(field, indent: ' ', first_in_block: i == 0)
276
+ out << " #{print_field_definition(field)}\n"
277
+ end
278
+ out << "}"
274
279
  end
275
- out << "}"
276
280
  end
277
281
 
278
282
  def print_directives(directives)
@@ -35,9 +35,11 @@ module GraphQL
35
35
  def load_nodes
36
36
  @nodes ||= begin
37
37
  sliced_nodes = if before && after
38
- items[index_from_cursor(after)..index_from_cursor(before)-1] || []
38
+ end_idx = index_from_cursor(before)-1
39
+ end_idx < 0 ? [] : items[index_from_cursor(after)..end_idx] || []
39
40
  elsif before
40
- items[0..index_from_cursor(before)-2] || []
41
+ end_idx = index_from_cursor(before)-2
42
+ end_idx < 0 ? [] : items[0..end_idx] || []
41
43
  elsif after
42
44
  items[index_from_cursor(after)..-1] || []
43
45
  else
@@ -28,6 +28,9 @@ module GraphQL
28
28
  # @return [String] Method or hash key on the underlying object to look up
29
29
  attr_reader :method_str
30
30
 
31
+ attr_reader :hash_key
32
+ attr_reader :dig_keys
33
+
31
34
  # @return [Symbol] The method on the type to look up
32
35
  def resolver_method
33
36
  if @resolver_class
@@ -211,7 +214,8 @@ module GraphQL
211
214
  # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
212
215
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
213
216
  # @param validates [Array<Hash>] Configurations for validating this field
214
- def initialize(type: nil, name: nil, owner: nil, null: nil, description: :not_given, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, &definition_block)
217
+ # @fallback_value [Object] A fallback value if the method is not defined
218
+ def initialize(type: nil, name: nil, owner: nil, null: nil, description: :not_given, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: :not_given, &definition_block)
215
219
  if name.nil?
216
220
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
217
221
  end
@@ -243,14 +247,16 @@ module GraphQL
243
247
  end
244
248
  end
245
249
 
246
- # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work)
247
250
  method_name = method || hash_key || name_s
248
251
  @dig_keys = dig
249
- resolver_method ||= name_s.to_sym
252
+ if hash_key
253
+ @hash_key = hash_key
254
+ @hash_key_str = hash_key.to_s
255
+ end
250
256
 
251
257
  @method_str = -method_name.to_s
252
258
  @method_sym = method_name.to_sym
253
- @resolver_method = resolver_method
259
+ @resolver_method = (resolver_method || name_s).to_sym
254
260
  @complexity = complexity
255
261
  @return_type_expr = type
256
262
  @return_type_null = if !null.nil?
@@ -275,6 +281,7 @@ module GraphQL
275
281
  @relay_nodes_field = relay_nodes_field
276
282
  @ast_node = ast_node
277
283
  @method_conflict_warning = method_conflict_warning
284
+ @fallback_value = fallback_value
278
285
 
279
286
  arguments.each do |name, arg|
280
287
  case arg
@@ -635,14 +642,11 @@ module GraphQL
635
642
  obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
636
643
  end
637
644
 
638
- # Find a way to resolve this field, checking:
639
- #
640
- # - A method on the type instance;
641
- # - Hash keys, if the wrapped object is a hash;
642
- # - A method on the wrapped object;
643
- # - Or, raise not implemented.
644
- #
645
- if obj.respond_to?(resolver_method)
645
+ inner_object = obj.object
646
+
647
+ if defined?(@hash_key)
648
+ inner_object[@hash_key] || inner_object[@hash_key_str] || (@fallback_value != :not_given ? @fallback_value : nil)
649
+ elsif obj.respond_to?(resolver_method)
646
650
  method_to_call = resolver_method
647
651
  method_receiver = obj
648
652
  # Call the method with kwargs, if there are any
@@ -651,32 +655,47 @@ module GraphQL
651
655
  else
652
656
  obj.public_send(resolver_method)
653
657
  end
654
- elsif obj.object.is_a?(Hash)
655
- inner_object = obj.object
658
+ elsif inner_object.is_a?(Hash)
656
659
  if @dig_keys
657
660
  inner_object.dig(*@dig_keys)
661
+ elsif defined?(@hash_key)
662
+ if inner_object.key?(@hash_key)
663
+ inner_object[@hash_key]
664
+ elsif inner_object.key?(@hash_key_str)
665
+ inner_object[@hash_key_str]
666
+ elsif @fallback_value != :not_given
667
+ @fallback_value
668
+ else
669
+ nil
670
+ end
658
671
  elsif inner_object.key?(@method_sym)
659
672
  inner_object[@method_sym]
660
- else
673
+ elsif inner_object.key?(@method_str)
661
674
  inner_object[@method_str]
675
+ elsif @fallback_value != :not_given
676
+ @fallback_value
677
+ else
678
+ nil
662
679
  end
663
- elsif obj.object.respond_to?(@method_sym)
680
+ elsif inner_object.respond_to?(@method_sym)
664
681
  method_to_call = @method_sym
665
682
  method_receiver = obj.object
666
683
  if ruby_kwargs.any?
667
- obj.object.public_send(@method_sym, **ruby_kwargs)
684
+ inner_object.public_send(@method_sym, **ruby_kwargs)
668
685
  else
669
- obj.object.public_send(@method_sym)
686
+ inner_object.public_send(@method_sym)
670
687
  end
688
+ elsif @fallback_value != :not_given
689
+ @fallback_value
671
690
  else
672
691
  raise <<-ERR
673
692
  Failed to implement #{@owner.graphql_name}.#{@name}, tried:
674
693
 
675
694
  - `#{obj.class}##{resolver_method}`, which did not exist
676
- - `#{obj.object.class}##{@method_sym}`, which did not exist
677
- - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
695
+ - `#{inner_object.class}##{@method_sym}`, which did not exist
696
+ - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{inner_object}`, but it wasn't a Hash
678
697
 
679
- To implement this field, define one of the methods above (and check for typos)
698
+ To implement this field, define one of the methods above (and check for typos), or supply a `fallback_value`.
680
699
  ERR
681
700
  end
682
701
  end
@@ -748,7 +767,7 @@ module GraphQL
748
767
 
749
768
  if unsatisfied_ruby_kwargs.any? || unsatisfied_method_params.any?
750
769
  raise FieldImplementationFailed.new, <<-ERR
751
- Failed to call #{method_name} on #{receiver.inspect} because the Ruby method params were incompatible with the GraphQL arguments:
770
+ Failed to call `#{method_name.inspect}` on #{receiver.inspect} because the Ruby method params were incompatible with the GraphQL arguments:
752
771
 
753
772
  #{ unsatisfied_ruby_kwargs
754
773
  .map { |key, value| "- `#{key}: #{value}` was given by GraphQL but not defined in the Ruby method. Add `#{key}:` to the method parameters." }
@@ -142,7 +142,7 @@ module GraphQL
142
142
  Class.new(GraphQL::Schema::Scalar) do
143
143
  graphql_name(type["name"])
144
144
  description(type["description"])
145
- specified_by_url(type["specifiedByUrl"])
145
+ specified_by_url(type["specifiedByURL"])
146
146
  end
147
147
  end
148
148
  when "UNION"
@@ -72,7 +72,7 @@ module GraphQL
72
72
  def add_field(field_defn, method_conflict_warning: field_defn.method_conflict_warning?)
73
73
  # Check that `field_defn.original_name` equals `resolver_method` and `method_sym` --
74
74
  # that shows that no override value was given manually.
75
- if method_conflict_warning && CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) && field_defn.original_name == field_defn.resolver_method && field_defn.original_name == field_defn.method_sym
75
+ if method_conflict_warning && CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) && field_defn.original_name == field_defn.resolver_method && field_defn.original_name == field_defn.method_sym && field_defn.hash_key.nil? && field_defn.dig_keys.nil?
76
76
  warn(conflict_field_name_warning(field_defn))
77
77
  end
78
78
  prev_defn = own_fields[field_defn.name]
@@ -73,7 +73,7 @@ module GraphQL
73
73
  context.schema.after_lazy(ready_val) do |is_ready, ready_early_return|
74
74
  if ready_early_return
75
75
  if is_ready != false
76
- raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{authorized_result.inspect}, #{ready_early_return.inspect}]"
76
+ raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{is_ready.inspect}, #{ready_early_return.inspect}]"
77
77
  else
78
78
  ready_early_return
79
79
  end
@@ -55,7 +55,9 @@ module GraphQL
55
55
  if visible_item.nil?
56
56
  visible_item = item
57
57
  else
58
- raise Schema::DuplicateNamesError, "Found two visible definitions for `#{item.path}`: #{visible_item.inspect}, #{item.inspect}"
58
+ raise DuplicateNamesError.new(
59
+ duplicated_name: item.path, duplicated_definition_1: visible_item.inspect, duplicated_definition_2: item.inspect
60
+ )
59
61
  end
60
62
  end
61
63
  end
@@ -362,7 +364,9 @@ module GraphQL
362
364
  if @reachable_type_set.add?(type)
363
365
  type_by_name = rt_hash[type.graphql_name] ||= type
364
366
  if type_by_name != type
365
- raise DuplicateNamesError, "Found two visible type definitions for `#{type.graphql_name}`: #{type.inspect}, #{type_by_name.inspect}"
367
+ raise DuplicateNamesError.new(
368
+ duplicated_name: type.graphql_name, duplicated_definition_1: type.inspect, duplicated_definition_2: type_by_name.inspect
369
+ )
366
370
  end
367
371
  if type.kind.input_object?
368
372
  # recurse into visible arguments
@@ -73,14 +73,16 @@ module GraphQL
73
73
  extend GraphQL::Schema::Member::HasAstNode
74
74
  extend GraphQL::Schema::FindInheritedValue
75
75
 
76
- class DuplicateTypeNamesError < GraphQL::Error
77
- def initialize(type_name:, first_definition:, second_definition:, path:)
78
- super("Multiple definitions for `#{type_name}`. Previously found #{first_definition.inspect} (#{first_definition.class}), then found #{second_definition.inspect} (#{second_definition.class}) at #{path.join(".")}")
76
+ class DuplicateNamesError < GraphQL::Error
77
+ attr_reader :duplicated_name
78
+ def initialize(duplicated_name:, duplicated_definition_1:, duplicated_definition_2:)
79
+ @duplicated_name = duplicated_name
80
+ super(
81
+ "Found two visible definitions for `#{duplicated_name}`: #{duplicated_definition_1}, #{duplicated_definition_2}"
82
+ )
79
83
  end
80
84
  end
81
85
 
82
- class DuplicateNamesError < GraphQL::Error; end
83
-
84
86
  class UnresolvedLateBoundTypeError < GraphQL::Error
85
87
  attr_reader :type
86
88
  def initialize(type:)
@@ -225,7 +227,9 @@ module GraphQL
225
227
  if visible_t.nil?
226
228
  visible_t = t
227
229
  else
228
- raise DuplicateNamesError, "Found two visible type definitions for `#{k}`: #{visible_t.inspect}, #{t.inspect}"
230
+ raise DuplicateNamesError.new(
231
+ duplicated_name: k, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
232
+ )
229
233
  end
230
234
  end
231
235
  end
@@ -252,7 +256,9 @@ module GraphQL
252
256
  if visible_t.nil?
253
257
  visible_t = t
254
258
  else
255
- raise DuplicateNamesError, "Found two visible type definitions for `#{type_name}`: #{visible_t.inspect}, #{t.inspect}"
259
+ raise DuplicateNamesError.new(
260
+ duplicated_name: type_name, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
261
+ )
256
262
  end
257
263
  end
258
264
  end
@@ -932,6 +938,7 @@ module GraphQL
932
938
  {
933
939
  backtrace: ctx[:backtrace],
934
940
  tracers: ctx[:tracers],
941
+ dataloader: ctx[:dataloader],
935
942
  }
936
943
  else
937
944
  {}
@@ -976,7 +983,9 @@ module GraphQL
976
983
  if !defined?(@subscription_extension_added) && subscription && self.subscriptions
977
984
  @subscription_extension_added = true
978
985
  subscription.all_field_definitions.each do |field|
979
- field.extension(Subscriptions::DefaultSubscriptionResolveExtension)
986
+ if !field.extensions.any? { |ext| ext.is_a?(Subscriptions::DefaultSubscriptionResolveExtension) }
987
+ field.extension(Subscriptions::DefaultSubscriptionResolveExtension)
988
+ end
980
989
  end
981
990
  end
982
991
  end
@@ -39,12 +39,14 @@ module GraphQL
39
39
  end
40
40
 
41
41
  # @param schema [Class] the GraphQL schema this manager belongs to
42
- def initialize(schema:, broadcast: false, default_broadcastable: false, **rest)
42
+ # @param validate_update [Boolean] If false, then validation is skipped when executing updates
43
+ def initialize(schema:, validate_update: true, broadcast: false, default_broadcastable: false, **rest)
43
44
  if broadcast
44
45
  schema.query_analyzer(Subscriptions::BroadcastAnalyzer)
45
46
  end
46
47
  @default_broadcastable = default_broadcastable
47
48
  @schema = schema
49
+ @validate_update = validate_update
48
50
  end
49
51
 
50
52
  # @return [Boolean] Used when fields don't have `broadcastable:` explicitly set
@@ -117,14 +119,16 @@ module GraphQL
117
119
  variables = query_data.fetch(:variables)
118
120
  context = query_data.fetch(:context)
119
121
  operation_name = query_data.fetch(:operation_name)
120
- result = @schema.execute(
122
+ execute_options = {
121
123
  query: query_string,
122
124
  context: context,
123
125
  subscription_topic: event.topic,
124
126
  operation_name: operation_name,
125
127
  variables: variables,
126
128
  root_value: object,
127
- )
129
+ }
130
+ execute_options[:validate] = validate_update?(**execute_options)
131
+ result = @schema.execute(**execute_options)
128
132
  subscriptions_context = result.context.namespace(:subscriptions)
129
133
  if subscriptions_context[:no_update]
130
134
  result = nil
@@ -142,6 +146,14 @@ module GraphQL
142
146
  result
143
147
  end
144
148
 
149
+ # Define this method to customize whether to validate
150
+ # this subscription when executing an update.
151
+ #
152
+ # @return [Boolean] defaults to `true`, or false if `validate: false` is provided.
153
+ def validate_update?(query:, context:, root_value:, subscription_topic:, operation_name:, variables:)
154
+ @validate_update
155
+ end
156
+
145
157
  # Run the update query for this subscription and deliver it
146
158
  # @see {#execute_update}
147
159
  # @see {#deliver}
@@ -17,15 +17,21 @@ module GraphQL
17
17
  def platform_trace(platform_key, key, data)
18
18
  tracer.trace(platform_key, service: service_name) do |span|
19
19
  span.span_type = 'custom'
20
+ if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
21
+ span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
22
+ span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, key)
23
+ end
20
24
 
21
25
  if key == 'execute_multiplex'
22
26
  operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ')
23
- span.resource = if operations.empty?
27
+
28
+ resource = if operations.empty?
24
29
  first_query = data[:multiplex].queries.first
25
30
  fallback_transaction_name(first_query && first_query.context)
26
31
  else
27
32
  operations
28
33
  end
34
+ span.resource = resource if resource
29
35
 
30
36
  # For top span of query, set the analytics sample rate tag, if available.
31
37
  if analytics_enabled?
@@ -39,6 +45,8 @@ module GraphQL
39
45
  span.set_tag(:query_string, data[:query].query_string)
40
46
  end
41
47
 
48
+ prepare_span(key, data, span)
49
+
42
50
  yield
43
51
  end
44
52
  end
@@ -47,6 +55,13 @@ module GraphQL
47
55
  options.fetch(:service, 'ruby-graphql')
48
56
  end
49
57
 
58
+ # Implement this method in a subclass to apply custom tags to datadog spans
59
+ # @param key [String] The event being traced
60
+ # @param data [Hash] The runtime data for this event (@see GraphQL::Tracing for keys for each event)
61
+ # @param span [Datadog::Tracing::SpanOperation] The datadog span for this event
62
+ def prepare_span(key, data, span)
63
+ end
64
+
50
65
  def tracer
51
66
  default_tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer
52
67
 
@@ -10,6 +10,10 @@ module GraphQL
10
10
  class PlatformTracing
11
11
  class << self
12
12
  attr_accessor :platform_keys
13
+
14
+ def inherited(child_class)
15
+ child_class.platform_keys = self.platform_keys
16
+ end
13
17
  end
14
18
 
15
19
  def initialize(options = {})
@@ -14,6 +14,7 @@ module GraphQL
14
14
  # own Date type.
15
15
  class ISO8601Date < GraphQL::Schema::Scalar
16
16
  description "An ISO 8601-encoded date"
17
+ specified_by_url "https://tools.ietf.org/html/rfc3339"
17
18
 
18
19
  # @param value [Date,Time,DateTime,String]
19
20
  # @return [String]
@@ -22,7 +23,7 @@ module GraphQL
22
23
  end
23
24
 
24
25
  # @param str_value [String, Date, DateTime, Time]
25
- # @return [Date]
26
+ # @return [Date, nil]
26
27
  def self.coerce_input(value, ctx)
27
28
  if value.is_a?(::Date)
28
29
  value
@@ -30,6 +31,8 @@ module GraphQL
30
31
  value.to_date
31
32
  elsif value.is_a?(::Time)
32
33
  value.to_date
34
+ elsif value.nil?
35
+ nil
33
36
  else
34
37
  Date.iso8601(value)
35
38
  end
@@ -17,6 +17,7 @@ module GraphQL
17
17
  # own DateTime type.
18
18
  class ISO8601DateTime < GraphQL::Schema::Scalar
19
19
  description "An ISO 8601-encoded datetime"
20
+ specified_by_url "https://tools.ietf.org/html/rfc3339"
20
21
 
21
22
  # It's not compatible with Rails' default,
22
23
  # i.e. ActiveSupport::JSON::Encoder.time_precision (3 by default)
@@ -59,6 +60,9 @@ module GraphQL
59
60
  # But without this, it would zero out given any time part of `str_value` (hours and/or minutes)
60
61
  if dt.iso8601.start_with?(str_value)
61
62
  dt
63
+ elsif str_value.length == 8 && str_value.match?(/\A\d{8}\Z/)
64
+ # Allow dates that are missing the "-". eg. "20220404"
65
+ dt
62
66
  else
63
67
  nil
64
68
  end
@@ -10,6 +10,8 @@ module GraphQL
10
10
  # so you can extend your own `BaseObject` instead of `GraphQL::Schema::Object`.
11
11
  #
12
12
  # @example Implementation a connection and edge
13
+ # class BaseObject < GraphQL::Schema::Object; end
14
+ #
13
15
  # # Given some object in your app ...
14
16
  # class Types::Post < BaseObject
15
17
  # end
@@ -20,14 +22,22 @@ module GraphQL
20
22
  #
21
23
  # # Then extend them for the object in your app
22
24
  # class Types::PostEdge < Types::BaseEdge
23
- # node_type(Types::Post)
25
+ # node_type Types::Post
24
26
  # end
27
+ #
25
28
  # class Types::PostConnection < Types::BaseConnection
26
- # edge_type(Types::PostEdge)
27
- # edges_nullable(true)
28
- # edge_nullable(true)
29
- # node_nullable(true)
30
- # has_nodes_field(true)
29
+ # edge_type Types::PostEdge,
30
+ # edges_nullable: true,
31
+ # edge_nullable: true,
32
+ # node_nullable: true,
33
+ # nodes_field: true
34
+ #
35
+ # # Alternatively, you can call the class methods followed by your edge type
36
+ # # edges_nullable true
37
+ # # edge_nullable true
38
+ # # nodes_nullable true
39
+ # # has_nodes_field true
40
+ # # edge_type Types::PostEdge
31
41
  # end
32
42
  #
33
43
  # @see Relay::BaseEdge for edge types
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.0.5"
3
+ VERSION = "2.0.8"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.5
4
+ version: 2.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-28 00:00:00.000000000 Z
11
+ date: 2022-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -314,7 +314,6 @@ files:
314
314
  - lib/graphql/execution/interpreter/runtime.rb
315
315
  - lib/graphql/execution/lazy.rb
316
316
  - lib/graphql/execution/lazy/lazy_method_map.rb
317
- - lib/graphql/execution/lazy/resolve.rb
318
317
  - lib/graphql/execution/lookahead.rb
319
318
  - lib/graphql/execution/multiplex.rb
320
319
  - lib/graphql/execution_error.rb
@@ -1,91 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Execution
4
- class Lazy
5
- # Helpers for dealing with data structures containing {Lazy} instances
6
- # @api private
7
- module Resolve
8
- # Mutate `value`, replacing {Lazy} instances in place with their resolved values
9
- # @return [void]
10
-
11
- # This object can be passed like an array, but it doesn't allocate an
12
- # array until it's used.
13
- #
14
- # There's one crucial difference: you have to _capture_ the result
15
- # of `#<<`. (This _works_ with arrays but isn't required, since it has a side-effect.)
16
- # @api private
17
- module NullAccumulator
18
- def self.<<(item)
19
- [item]
20
- end
21
-
22
- def self.empty?
23
- true
24
- end
25
- end
26
-
27
- def self.resolve(value)
28
- lazies = resolve_in_place(value)
29
- deep_sync(lazies)
30
- end
31
-
32
- def self.resolve_in_place(value)
33
- acc = each_lazy(NullAccumulator, value)
34
-
35
- if acc.empty?
36
- Lazy::NullResult
37
- else
38
- Lazy.new {
39
- acc.each_with_index { |ctx, idx|
40
- acc[idx] = ctx.value.value
41
- }
42
- resolve_in_place(acc)
43
- }
44
- end
45
- end
46
-
47
- # If `value` is a collection,
48
- # add any {Lazy} instances in the collection
49
- # to `acc`
50
- # @return [void]
51
- def self.each_lazy(acc, value)
52
- case value
53
- when Hash
54
- value.each do |key, field_result|
55
- acc = each_lazy(acc, field_result)
56
- end
57
- when Array
58
- value.each do |field_result|
59
- acc = each_lazy(acc, field_result)
60
- end
61
- when Query::Context::SharedMethods
62
- field_value = value.value
63
- case field_value
64
- when Lazy
65
- acc = acc << value
66
- when Enumerable # shortcut for Hash & Array
67
- acc = each_lazy(acc, field_value)
68
- end
69
- end
70
-
71
- acc
72
- end
73
-
74
- # Traverse `val`, triggering resolution for each {Lazy}.
75
- # These {Lazy}s are expected to mutate their owner data structures
76
- # during resolution! (They're created with the `.then` calls in `resolve_in_place`).
77
- # @return [void]
78
- def self.deep_sync(val)
79
- case val
80
- when Lazy
81
- deep_sync(val.value)
82
- when Array
83
- val.each { |v| deep_sync(v.value) }
84
- when Hash
85
- val.each { |k, v| deep_sync(v.value) }
86
- end
87
- end
88
- end
89
- end
90
- end
91
- end