graphql 2.0.5 → 2.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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