graphql 1.13.3 → 1.13.7

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