graphql 1.13.3 → 1.13.7

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.

Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +0 -7
  3. data/lib/generators/graphql/enum_generator.rb +4 -10
  4. data/lib/generators/graphql/field_extractor.rb +31 -0
  5. data/lib/generators/graphql/input_generator.rb +50 -0
  6. data/lib/generators/graphql/install/mutation_root_generator.rb +34 -0
  7. data/lib/generators/graphql/{templates → install/templates}/base_mutation.erb +0 -0
  8. data/lib/generators/graphql/{templates → install/templates}/mutation_type.erb +0 -0
  9. data/lib/generators/graphql/install_generator.rb +1 -1
  10. data/lib/generators/graphql/interface_generator.rb +7 -7
  11. data/lib/generators/graphql/mutation_create_generator.rb +22 -0
  12. data/lib/generators/graphql/mutation_delete_generator.rb +22 -0
  13. data/lib/generators/graphql/mutation_generator.rb +5 -30
  14. data/lib/generators/graphql/mutation_update_generator.rb +22 -0
  15. data/lib/generators/graphql/object_generator.rb +8 -37
  16. data/lib/generators/graphql/orm_mutations_base.rb +40 -0
  17. data/lib/generators/graphql/scalar_generator.rb +4 -2
  18. data/lib/generators/graphql/templates/enum.erb +5 -1
  19. data/lib/generators/graphql/templates/input.erb +9 -0
  20. data/lib/generators/graphql/templates/interface.erb +4 -2
  21. data/lib/generators/graphql/templates/mutation.erb +1 -1
  22. data/lib/generators/graphql/templates/mutation_create.erb +20 -0
  23. data/lib/generators/graphql/templates/mutation_delete.erb +20 -0
  24. data/lib/generators/graphql/templates/mutation_update.erb +21 -0
  25. data/lib/generators/graphql/templates/object.erb +4 -2
  26. data/lib/generators/graphql/templates/scalar.erb +3 -1
  27. data/lib/generators/graphql/templates/union.erb +4 -2
  28. data/lib/generators/graphql/type_generator.rb +46 -9
  29. data/lib/generators/graphql/union_generator.rb +5 -5
  30. data/lib/graphql/analysis/ast/visitor.rb +2 -1
  31. data/lib/graphql/dataloader/source.rb +2 -2
  32. data/lib/graphql/date_encoding_error.rb +16 -0
  33. data/lib/graphql/execution/interpreter/arguments_cache.rb +4 -2
  34. data/lib/graphql/execution/interpreter/runtime.rb +33 -17
  35. data/lib/graphql/introspection/directive_location_enum.rb +2 -2
  36. data/lib/graphql/introspection/directive_type.rb +2 -0
  37. data/lib/graphql/introspection/schema_type.rb +5 -0
  38. data/lib/graphql/introspection/type_type.rb +9 -3
  39. data/lib/graphql/introspection.rb +3 -0
  40. data/lib/graphql/language/document_from_schema_definition.rb +1 -0
  41. data/lib/graphql/language/lexer.rb +50 -25
  42. data/lib/graphql/language/lexer.rl +2 -0
  43. data/lib/graphql/language/nodes.rb +1 -1
  44. data/lib/graphql/language/parser.rb +829 -816
  45. data/lib/graphql/language/parser.y +8 -2
  46. data/lib/graphql/language/printer.rb +4 -0
  47. data/lib/graphql/pagination/relation_connection.rb +8 -5
  48. data/lib/graphql/rubocop/graphql/default_required_true.rb +4 -4
  49. data/lib/graphql/schema/argument.rb +18 -1
  50. data/lib/graphql/schema/build_from_definition.rb +1 -0
  51. data/lib/graphql/schema/directive.rb +15 -0
  52. data/lib/graphql/schema/field.rb +13 -7
  53. data/lib/graphql/schema/loader.rb +3 -0
  54. data/lib/graphql/schema/scalar.rb +12 -0
  55. data/lib/graphql/schema.rb +12 -5
  56. data/lib/graphql/static_validation/base_visitor.rb +1 -1
  57. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  58. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +1 -1
  59. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  60. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  61. data/lib/graphql/static_validation/validation_context.rb +4 -0
  62. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  63. data/lib/graphql/tracing/data_dog_tracing.rb +6 -1
  64. data/lib/graphql/tracing/platform_tracing.rb +11 -6
  65. data/lib/graphql/types/iso_8601_date.rb +13 -5
  66. data/lib/graphql/types/iso_8601_date_time.rb +8 -1
  67. data/lib/graphql/types/relay/connection_behaviors.rb +2 -1
  68. data/lib/graphql/types/relay/node_field.rb +0 -12
  69. data/lib/graphql/types/relay/nodes_field.rb +6 -0
  70. data/lib/graphql/version.rb +1 -1
  71. data/lib/graphql.rb +12 -0
  72. metadata +20 -8
@@ -147,6 +147,7 @@ rule
147
147
  name_without_on:
148
148
  IDENTIFIER
149
149
  | FRAGMENT
150
+ | REPEATABLE
150
151
  | TRUE
151
152
  | FALSE
152
153
  | operation_type
@@ -155,6 +156,7 @@ rule
155
156
  enum_name: /* any identifier, but not "true", "false" or "null" */
156
157
  IDENTIFIER
157
158
  | FRAGMENT
159
+ | REPEATABLE
158
160
  | ON
159
161
  | operation_type
160
162
  | schema_keyword
@@ -422,10 +424,14 @@ rule
422
424
  }
423
425
 
424
426
  directive_definition:
425
- description_opt DIRECTIVE DIR_SIGN name arguments_definitions_opt ON directive_locations {
426
- result = make_node(:DirectiveDefinition, name: val[3], arguments: val[4], locations: val[6], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1])
427
+ description_opt DIRECTIVE DIR_SIGN name arguments_definitions_opt directive_repeatable_opt ON directive_locations {
428
+ result = make_node(:DirectiveDefinition, name: val[3], arguments: val[4], locations: val[7], repeatable: !!val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1])
427
429
  }
428
430
 
431
+ directive_repeatable_opt:
432
+ /* nothing */
433
+ | REPEATABLE
434
+
429
435
  directive_locations:
430
436
  name { result = [make_node(:DirectiveLocation, name: val[0].to_s, position_source: val[0])] }
431
437
  | directive_locations PIPE name { val[0] << make_node(:DirectiveLocation, name: val[2].to_s, position_source: val[2]) }
@@ -252,6 +252,10 @@ module GraphQL
252
252
  out << print_arguments(directive.arguments)
253
253
  end
254
254
 
255
+ if directive.repeatable
256
+ out << " repeatable"
257
+ end
258
+
255
259
  out << " on #{directive.locations.map(&:name).join(' | ')}"
256
260
  end
257
261
 
@@ -47,7 +47,7 @@ module GraphQL
47
47
  def cursor_for(item)
48
48
  load_nodes
49
49
  # index in nodes + existing offset + 1 (because it's offset, not index)
50
- offset = nodes.index(item) + 1 + (@paged_nodes_offset || 0) + (relation_offset(items) || 0)
50
+ offset = nodes.index(item) + 1 + (@paged_nodes_offset || 0) - (relation_offset(items) || 0)
51
51
  encode(offset.to_s)
52
52
  end
53
53
 
@@ -116,9 +116,9 @@ module GraphQL
116
116
  if defined?(@sliced_nodes_limit)
117
117
  return
118
118
  else
119
+ next_offset = relation_offset(items) || 0
119
120
  if after_offset
120
- previous_offset = relation_offset(items) || 0
121
- relation_offset = previous_offset + after_offset
121
+ next_offset += after_offset
122
122
  end
123
123
 
124
124
  if before_offset && after_offset
@@ -136,7 +136,7 @@ module GraphQL
136
136
  end
137
137
 
138
138
  @sliced_nodes_limit = relation_limit
139
- @sliced_nodes_offset = relation_offset || 0
139
+ @sliced_nodes_offset = next_offset
140
140
  end
141
141
  end
142
142
 
@@ -208,7 +208,10 @@ module GraphQL
208
208
  @paged_nodes_offset = relation_offset
209
209
  paginated_nodes = items
210
210
  paginated_nodes = set_offset(paginated_nodes, relation_offset)
211
- set_limit(paginated_nodes, relation_limit)
211
+ if relation_limit
212
+ paginated_nodes = set_limit(paginated_nodes, relation_limit)
213
+ end
214
+ paginated_nodes
212
215
  end
213
216
  end
214
217
 
@@ -25,16 +25,16 @@ module GraphQL
25
25
 
26
26
  def_node_matcher :argument_config_with_required_true?, <<-Pattern
27
27
  (
28
- send nil? :argument ... (hash <$(pair (sym :required) (true)) ...>)
28
+ send {nil? _} :argument ... (hash <$(pair (sym :required) (true)) ...>)
29
29
  )
30
30
  Pattern
31
31
 
32
32
  def on_send(node)
33
33
  argument_config_with_required_true?(node) do |required_config|
34
34
  add_offense(required_config) do |corrector|
35
- cleaned_node_source = source_without_keyword_argument(node, required_config)
36
- corrector.replace(node, cleaned_node_source)
37
- end
35
+ cleaned_node_source = source_without_keyword_argument(node, required_config)
36
+ corrector.replace(node, cleaned_node_source)
37
+ end
38
38
  end
39
39
  end
40
40
  end
@@ -48,13 +48,21 @@ module GraphQL
48
48
  # @param directives [Hash{Class => Hash}]
49
49
  # @param deprecation_reason [String]
50
50
  # @param validates [Hash, nil] Options for building validators, if any should be applied
51
- def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NO_DEFAULT, as: nil, from_resolver: false, camelize: true, prepare: nil, method_access: true, owner:, validates: nil, directives: nil, deprecation_reason: nil, &definition_block)
51
+ # @param replace_null_with_default [Boolean] if `true`, incoming values of `null` will be replaced with the configured `default_value`
52
+ def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NO_DEFAULT, as: nil, from_resolver: false, camelize: true, prepare: nil, method_access: true, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
52
53
  arg_name ||= name
53
54
  @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
54
55
  @type_expr = type_expr || type
55
56
  @description = desc || description
56
57
  @null = required != true
57
58
  @default_value = default_value
59
+ if replace_null_with_default
60
+ if !default_value?
61
+ raise ArgumentError, "`replace_null_with_default: true` requires a default value, please provide one with `default_value: ...`"
62
+ end
63
+ @replace_null_with_default = true
64
+ end
65
+
58
66
  @owner = owner
59
67
  @as = as
60
68
  @loads = loads
@@ -97,6 +105,10 @@ module GraphQL
97
105
  @default_value != NO_DEFAULT
98
106
  end
99
107
 
108
+ def replace_null_with_default?
109
+ @replace_null_with_default
110
+ end
111
+
100
112
  attr_writer :description
101
113
 
102
114
  # @return [String] Documentation for this argument
@@ -253,6 +265,11 @@ module GraphQL
253
265
  return
254
266
  end
255
267
 
268
+ if value.nil? && replace_null_with_default?
269
+ value = default_value
270
+ default_used = true
271
+ end
272
+
256
273
  loaded_value = nil
257
274
  coerced_value = context.schema.error_handler.with_error_handling(context) do
258
275
  type.coerce_input(value, context)
@@ -377,6 +377,7 @@ module GraphQL
377
377
  Class.new(GraphQL::Schema::Directive) do
378
378
  graphql_name(directive_definition.name)
379
379
  description(directive_definition.description)
380
+ repeatable(directive_definition.repeatable)
380
381
  locations(*directive_definition.locations.map { |location| location.name.to_sym })
381
382
  ast_node(directive_definition)
382
383
  builder.build_arguments(self, directive_definition.arguments, type_resolver)
@@ -90,6 +90,11 @@ module GraphQL
90
90
  yield
91
91
  end
92
92
 
93
+ # Continuing is passed as a block, yield to continue.
94
+ def resolve_each(object, arguments, context)
95
+ yield
96
+ end
97
+
93
98
  def on_field?
94
99
  locations.include?(FIELD)
95
100
  end
@@ -101,6 +106,14 @@ module GraphQL
101
106
  def on_operation?
102
107
  locations.include?(QUERY) && locations.include?(MUTATION) && locations.include?(SUBSCRIPTION)
103
108
  end
109
+
110
+ def repeatable?
111
+ !!@repeatable
112
+ end
113
+
114
+ def repeatable(new_value)
115
+ @repeatable = new_value
116
+ end
104
117
  end
105
118
 
106
119
  # @return [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class, Module]
@@ -139,6 +152,7 @@ module GraphQL
139
152
  ENUM_VALUE = :ENUM_VALUE,
140
153
  INPUT_OBJECT = :INPUT_OBJECT,
141
154
  INPUT_FIELD_DEFINITION = :INPUT_FIELD_DEFINITION,
155
+ VARIABLE_DEFINITION = :VARIABLE_DEFINITION,
142
156
  ]
143
157
 
144
158
  DEFAULT_DEPRECATION_REASON = 'No longer supported'
@@ -161,6 +175,7 @@ module GraphQL
161
175
  ENUM_VALUE: 'Location adjacent to an enum value definition.',
162
176
  INPUT_OBJECT: 'Location adjacent to an input object type definition.',
163
177
  INPUT_FIELD_DEFINITION: 'Location adjacent to an input object field definition.',
178
+ VARIABLE_DEFINITION: 'Location adjacent to a variable definition.',
164
179
  }
165
180
 
166
181
  private
@@ -38,7 +38,9 @@ module GraphQL
38
38
 
39
39
  # @return [Class] The GraphQL type this field belongs to. (For fields defined on mutations, it's the payload type)
40
40
  def owner_type
41
- @owner_type ||= if owner < GraphQL::Schema::Mutation
41
+ @owner_type ||= if owner.nil?
42
+ raise GraphQL::InvariantError, "Field #{original_name.inspect} (graphql name: #{graphql_name.inspect}) has no owner, but all fields should have an owner. How did this happen?!"
43
+ elsif owner < GraphQL::Schema::Mutation
42
44
  owner.payload_type
43
45
  else
44
46
  owner
@@ -188,6 +190,7 @@ module GraphQL
188
190
  # @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
189
191
  # @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`)
190
192
  # @param hash_key [String, Symbol] The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`)
193
+ # @param dig [Array<String, Symbol>] The nested hash keys to lookup on the underlying hash to resolve this field using dig
191
194
  # @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`)
192
195
  # @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
193
196
  # @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
@@ -210,7 +213,7 @@ module GraphQL
210
213
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
211
214
  # @param validates [Array<Hash>] Configurations for validating this field
212
215
  # @param legacy_edge_class [Class, nil] (DEPRECATED) If present, pass this along to the legacy field definition
213
- def initialize(type: nil, name: nil, owner: nil, null: true, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, 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, legacy_edge_class: nil, &definition_block)
216
+ def initialize(type: nil, name: nil, owner: nil, null: true, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, 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, legacy_edge_class: nil, &definition_block)
214
217
  if name.nil?
215
218
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
216
219
  end
@@ -236,8 +239,8 @@ module GraphQL
236
239
  @resolve = resolve
237
240
  self.deprecation_reason = deprecation_reason
238
241
 
239
- if method && hash_key
240
- raise ArgumentError, "Provide `method:` _or_ `hash_key:`, not both. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}`)"
242
+ if method && hash_key && dig
243
+ raise ArgumentError, "Provide `method:`, `hash_key:` _or_ `dig:`, not multiple. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}, dig: #{dig.inspect}`)"
241
244
  end
242
245
 
243
246
  if resolver_method
@@ -245,13 +248,14 @@ module GraphQL
245
248
  raise ArgumentError, "Provide `method:` _or_ `resolver_method:`, not both. (called with: `method: #{method.inspect}, resolver_method: #{resolver_method.inspect}`)"
246
249
  end
247
250
 
248
- if hash_key
249
- raise ArgumentError, "Provide `hash_key:` _or_ `resolver_method:`, not both. (called with: `hash_key: #{hash_key.inspect}, resolver_method: #{resolver_method.inspect}`)"
251
+ if hash_key || dig
252
+ raise ArgumentError, "Provide `hash_key:`, `dig:`, _or_ `resolver_method:`, not multiple. (called with: `hash_key: #{hash_key.inspect}, dig: #{dig.inspect}, resolver_method: #{resolver_method.inspect}`)"
250
253
  end
251
254
  end
252
255
 
253
256
  # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work)
254
257
  method_name = method || hash_key || name_s
258
+ @dig_keys = dig
255
259
  resolver_method ||= name_s.to_sym
256
260
 
257
261
  @method_str = -method_name.to_s
@@ -822,7 +826,9 @@ module GraphQL
822
826
  end
823
827
  elsif obj.object.is_a?(Hash)
824
828
  inner_object = obj.object
825
- if inner_object.key?(@method_sym)
829
+ if @dig_keys
830
+ inner_object.dig(*@dig_keys)
831
+ elsif inner_object.key?(@method_sym)
826
832
  inner_object[@method_sym]
827
833
  else
828
834
  inner_object[@method_str]
@@ -34,6 +34,7 @@ module GraphQL
34
34
  Class.new(GraphQL::Schema) do
35
35
  orphan_types(types.values)
36
36
  directives(directives)
37
+ description(schema["description"])
37
38
 
38
39
  def self.resolve_type(*)
39
40
  raise(GraphQL::RequiredImplementationMissingError, "This schema was loaded from string, so it can't resolve types for objects")
@@ -141,6 +142,7 @@ module GraphQL
141
142
  Class.new(GraphQL::Schema::Scalar) do
142
143
  graphql_name(type["name"])
143
144
  description(type["description"])
145
+ specified_by_url(type["specifiedByUrl"])
144
146
  end
145
147
  end
146
148
  when "UNION"
@@ -160,6 +162,7 @@ module GraphQL
160
162
  graphql_name(directive["name"])
161
163
  description(directive["description"])
162
164
  locations(*directive["locations"].map(&:to_sym))
165
+ repeatable(directive["isRepeatable"])
163
166
  loader.build_arguments(self, directive["args"], type_resolver)
164
167
  end
165
168
  end
@@ -32,6 +32,18 @@ module GraphQL
32
32
  GraphQL::TypeKinds::SCALAR
33
33
  end
34
34
 
35
+ def specified_by_url(new_url = nil)
36
+ if new_url
37
+ @specified_by_url = new_url
38
+ elsif defined?(@specified_by_url)
39
+ @specified_by_url
40
+ elsif superclass.respond_to?(:specified_by_url)
41
+ superclass.specified_by_url
42
+ else
43
+ nil
44
+ end
45
+ end
46
+
35
47
  def default_scalar(is_default = nil)
36
48
  if !is_default.nil?
37
49
  @default_scalar = is_default
@@ -892,6 +892,17 @@ module GraphQL
892
892
  GraphQL::Language::DocumentFromSchemaDefinition.new(self).document
893
893
  end
894
894
 
895
+ # @return [String, nil]
896
+ def description(new_description = nil)
897
+ if new_description
898
+ @description = new_description
899
+ elsif defined?(@description)
900
+ @description
901
+ else
902
+ find_inherited_value(:description, nil)
903
+ end
904
+ end
905
+
895
906
  def find(path)
896
907
  if !@finder
897
908
  @find_cache = {}
@@ -1247,11 +1258,7 @@ module GraphQL
1247
1258
  when Module
1248
1259
  type_or_name
1249
1260
  else
1250
- raise ArgumentError, <<-ERR
1251
- Invariant: unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})
1252
-
1253
- This is probably a bug in GraphQL-Ruby, please report this error on GitHub: https://github.com/rmosolgo/graphql-ruby/issues/new?template=bug_report.md
1254
- ERR
1261
+ raise GraphQL::InvariantError, "Unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})"
1255
1262
  end
1256
1263
 
1257
1264
  if parent_type.kind.fields? && (field = parent_type.get_field(field_name, context))
@@ -110,7 +110,7 @@ module GraphQL
110
110
  end
111
111
 
112
112
  def on_directive(node, parent)
113
- directive_defn = @schema.directives[node.name]
113
+ directive_defn = @context.schema_directives[node.name]
114
114
  @directive_definitions.push(directive_defn)
115
115
  super
116
116
  @directive_definitions.pop
@@ -59,7 +59,7 @@ module GraphQL
59
59
  end
60
60
  end
61
61
  when GraphQL::Language::Nodes::Directive
62
- context.schema.directives[parent.name]
62
+ context.schema_directives[parent.name]
63
63
  when GraphQL::Language::Nodes::Field
64
64
  context.field_definition
65
65
  else
@@ -5,7 +5,7 @@ module GraphQL
5
5
  include GraphQL::Language
6
6
 
7
7
  def on_directive(node, parent)
8
- validate_location(node, parent, context.schema.directives)
8
+ validate_location(node, parent, context.schema_directives)
9
9
  super
10
10
  end
11
11
 
@@ -8,7 +8,7 @@ module GraphQL
8
8
  end
9
9
 
10
10
  def on_directive(node, _parent)
11
- directive_defn = context.schema.directives[node.name]
11
+ directive_defn = context.schema_directives[node.name]
12
12
  assert_required_args(node, directive_defn)
13
13
  super
14
14
  end
@@ -40,7 +40,7 @@ module GraphQL
40
40
  nodes: [used_directives[directive_name], ast_directive],
41
41
  directive: directive_name,
42
42
  ))
43
- else
43
+ elsif !((dir_defn = context.schema_directives[directive_name]) && dir_defn.repeatable?)
44
44
  used_directives[directive_name] = ast_directive
45
45
  end
46
46
  end
@@ -44,6 +44,10 @@ module GraphQL
44
44
  def too_many_errors?
45
45
  @errors.length >= @max_errors
46
46
  end
47
+
48
+ def schema_directives
49
+ @schema_directives ||= schema.directives
50
+ end
47
51
  end
48
52
  end
49
53
  end
@@ -11,7 +11,7 @@ module GraphQL
11
11
  module ActiveSupportNotificationsTracing
12
12
  # A cache of frequently-used keys to avoid needless string allocations
13
13
  KEYS = NotificationsTracing::KEYS
14
- NOTIFICATIONS_ENGINE = NotificationsTracing.new(ActiveSupport::Notifications) if defined?(ActiveSupport)
14
+ NOTIFICATIONS_ENGINE = NotificationsTracing.new(ActiveSupport::Notifications) if defined?(ActiveSupport::Notifications)
15
15
 
16
16
  def self.trace(key, metadata, &blk)
17
17
  NOTIFICATIONS_ENGINE.trace(key, metadata, &blk)
@@ -20,7 +20,12 @@ module GraphQL
20
20
 
21
21
  if key == 'execute_multiplex'
22
22
  operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ')
23
- span.resource = operations unless operations.empty?
23
+ span.resource = if operations.empty?
24
+ first_query = data[:multiplex].queries.first
25
+ fallback_transaction_name(first_query && first_query.context)
26
+ else
27
+ operations
28
+ end
24
29
 
25
30
  # For top span of query, set the analytics sample rate tag, if available.
26
31
  if analytics_enabled?
@@ -104,17 +104,22 @@ module GraphQL
104
104
 
105
105
  private
106
106
 
107
- # Get the transaction name based on the operation type and name
107
+ # Get the transaction name based on the operation type and name if possible, or fall back to a user provided
108
+ # one. Useful for anonymous queries.
108
109
  def transaction_name(query)
109
110
  selected_op = query.selected_operation
110
- if selected_op
111
+ txn_name = if selected_op
111
112
  op_type = selected_op.operation_type
112
- op_name = selected_op.name || "anonymous"
113
+ op_name = selected_op.name || fallback_transaction_name(query.context) || "anonymous"
114
+ "#{op_type}.#{op_name}"
113
115
  else
114
- op_type = "query"
115
- op_name = "anonymous"
116
+ "query.anonymous"
116
117
  end
117
- "GraphQL/#{op_type}.#{op_name}"
118
+ "GraphQL/#{txn_name}"
119
+ end
120
+
121
+ def fallback_transaction_name(context)
122
+ context[:tracing_fallback_transaction_name]
118
123
  end
119
124
 
120
125
  attr_reader :options
@@ -21,13 +21,21 @@ module GraphQL
21
21
  Date.parse(value.to_s).iso8601
22
22
  end
23
23
 
24
- # @param str_value [String]
24
+ # @param str_value [String, Date, DateTime, Time]
25
25
  # @return [Date]
26
- def self.coerce_input(str_value, _ctx)
27
- Date.iso8601(str_value)
26
+ def self.coerce_input(value, ctx)
27
+ if value.is_a?(::Date)
28
+ value
29
+ elsif value.is_a?(::DateTime)
30
+ value.to_date
31
+ elsif value.is_a?(::Time)
32
+ value.to_date
33
+ else
34
+ Date.iso8601(value)
35
+ end
28
36
  rescue ArgumentError, TypeError
29
- # Invalid input
30
- nil
37
+ err = GraphQL::DateEncodingError.new(value)
38
+ ctx.schema.type_error(err, ctx)
31
39
  end
32
40
  end
33
41
  end
@@ -54,7 +54,14 @@ module GraphQL
54
54
  Time.iso8601(str_value)
55
55
  rescue ArgumentError, TypeError
56
56
  begin
57
- Date.iso8601(str_value).to_time
57
+ dt = Date.iso8601(str_value).to_time
58
+ # For compatibility, continue accepting dates given without times
59
+ # But without this, it would zero out given any time part of `str_value` (hours and/or minutes)
60
+ if dt.iso8601.start_with?(str_value)
61
+ dt
62
+ else
63
+ nil
64
+ end
58
65
  rescue ArgumentError, TypeError
59
66
  # Invalid input
60
67
  nil
@@ -83,7 +83,8 @@ module GraphQL
83
83
  end
84
84
 
85
85
  def visible?(ctx)
86
- node_type.visible?(ctx)
86
+ # if this is an abstract base class, there may be no `node_type`
87
+ node_type ? node_type.visible?(ctx) : super
87
88
  end
88
89
 
89
90
  # Set the default `node_nullable` for this class and its child classes. (Defaults to `true`.)
@@ -18,18 +18,6 @@ module GraphQL
18
18
  # context.schema.object_from_id(id, context)
19
19
  # end
20
20
  #
21
- def self.const_missing(const_name)
22
- if const_name == :NodeField
23
- message = "NodeField is deprecated, use `include GraphQL::Types::Relay::HasNodeField` instead."
24
- message += "\n(referenced from #{caller(1, 1).first})"
25
- GraphQL::Deprecation.warn(message)
26
-
27
- DeprecatedNodeField
28
- else
29
- super
30
- end
31
- end
32
-
33
21
  DeprecatedNodeField = GraphQL::Schema::Field.new(owner: nil, **HasNodeField.field_options, &HasNodeField.field_block)
34
22
  end
35
23
  end
@@ -27,6 +27,12 @@ module GraphQL
27
27
  GraphQL::Deprecation.warn(message)
28
28
 
29
29
  DeprecatedNodesField
30
+ elsif const_name == :NodeField
31
+ message = "NodeField is deprecated, use `include GraphQL::Types::Relay::HasNodeField` instead."
32
+ message += "\n(referenced from #{caller(1, 1).first})"
33
+ GraphQL::Deprecation.warn(message)
34
+
35
+ DeprecatedNodeField
30
36
  else
31
37
  super
32
38
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.13.3"
3
+ VERSION = "1.13.7"
4
4
  end
data/lib/graphql.rb CHANGED
@@ -17,6 +17,17 @@ module GraphQL
17
17
  class Error < StandardError
18
18
  end
19
19
 
20
+ # This error is raised when GraphQL-Ruby encounters a situation
21
+ # that it *thought* would never happen. Please report this bug!
22
+ class InvariantError < Error
23
+ def initialize(message)
24
+ message += "
25
+
26
+ This is probably a bug in GraphQL-Ruby, please report this error on GitHub: https://github.com/rmosolgo/graphql-ruby/issues/new?template=bug_report.md"
27
+ super(message)
28
+ end
29
+ end
30
+
20
31
  class RequiredImplementationMissingError < Error
21
32
  end
22
33
 
@@ -69,6 +80,7 @@ require "graphql/invalid_name_error"
69
80
  require "graphql/integer_decoding_error"
70
81
  require "graphql/integer_encoding_error"
71
82
  require "graphql/string_encoding_error"
83
+ require "graphql/date_encoding_error"
72
84
 
73
85
  require "graphql/define"
74
86
  require "graphql/base_type"