graphql 1.13.5 → 1.13.9

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 (53) 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/runtime.rb +29 -14
  34. data/lib/graphql/introspection/directive_location_enum.rb +2 -2
  35. data/lib/graphql/introspection/directive_type.rb +2 -0
  36. data/lib/graphql/introspection/schema_type.rb +5 -0
  37. data/lib/graphql/introspection/type_type.rb +9 -3
  38. data/lib/graphql/introspection.rb +4 -1
  39. data/lib/graphql/pagination/relation_connection.rb +4 -4
  40. data/lib/graphql/rubocop/graphql/default_required_true.rb +4 -4
  41. data/lib/graphql/schema/argument.rb +18 -1
  42. data/lib/graphql/schema/directive.rb +11 -0
  43. data/lib/graphql/schema/field.rb +21 -12
  44. data/lib/graphql/schema/input_object.rb +1 -1
  45. data/lib/graphql/schema/loader.rb +3 -0
  46. data/lib/graphql/schema/scalar.rb +12 -0
  47. data/lib/graphql/schema.rb +12 -5
  48. data/lib/graphql/types/iso_8601_date.rb +13 -5
  49. data/lib/graphql/types/iso_8601_date_time.rb +8 -1
  50. data/lib/graphql/types/relay/connection_behaviors.rb +2 -1
  51. data/lib/graphql/version.rb +1 -1
  52. data/lib/graphql.rb +12 -0
  53. metadata +20 -8
@@ -8,13 +8,28 @@ require_relative 'core'
8
8
 
9
9
  module Graphql
10
10
  module Generators
11
- class TypeGeneratorBase < Rails::Generators::Base
11
+ class TypeGeneratorBase < Rails::Generators::NamedBase
12
12
  include Core
13
13
 
14
- argument :type_name,
15
- type: :string,
16
- banner: "TypeName",
17
- desc: "Name of this object type (expressed as Ruby or GraphQL)"
14
+ class_option 'namespaced_types',
15
+ type: :boolean,
16
+ required: false,
17
+ default: false,
18
+ banner: "Namespaced",
19
+ desc: "If the generated types will be namespaced"
20
+
21
+ argument :custom_fields,
22
+ type: :array,
23
+ default: [],
24
+ banner: "name:type name:type ...",
25
+ desc: "Fields for this object (type may be expressed as Ruby or GraphQL)"
26
+
27
+
28
+ attr_accessor :graphql_type
29
+
30
+ def create_type_file
31
+ template "#{graphql_type}.erb", "#{options[:directory]}/types#{subdirectory}/#{type_file_name}.rb"
32
+ end
18
33
 
19
34
  # Take a type expression in any combination of GraphQL or Ruby styles
20
35
  # and return it in a specified output style
@@ -60,12 +75,12 @@ module Graphql
60
75
 
61
76
  # @return [String] The user-provided type name, normalized to Ruby code
62
77
  def type_ruby_name
63
- @type_ruby_name ||= self.class.normalize_type_expression(type_name, mode: :ruby)[0]
78
+ @type_ruby_name ||= self.class.normalize_type_expression(name, mode: :ruby)[0]
64
79
  end
65
80
 
66
81
  # @return [String] The user-provided type name, as a GraphQL name
67
82
  def type_graphql_name
68
- @type_graphql_name ||= self.class.normalize_type_expression(type_name, mode: :graphql)[0]
83
+ @type_graphql_name ||= self.class.normalize_type_expression(name, mode: :graphql)[0]
69
84
  end
70
85
 
71
86
  # @return [String] The user-provided type name, as a file name (without extension)
@@ -82,6 +97,24 @@ module Graphql
82
97
  }
83
98
  end
84
99
 
100
+ def ruby_class_name
101
+ class_prefix =
102
+ if options[:namespaced_types]
103
+ "#{graphql_type.pluralize.camelize}::"
104
+ else
105
+ ""
106
+ end
107
+ @ruby_class_name || class_prefix + type_ruby_name.sub(/^Types::/, "")
108
+ end
109
+
110
+ def subdirectory
111
+ if options[:namespaced_types]
112
+ "/#{graphql_type.pluralize}"
113
+ else
114
+ ""
115
+ end
116
+ end
117
+
85
118
  class NormalizedField
86
119
  def initialize(name, type_expr, null)
87
120
  @name = name
@@ -89,8 +122,12 @@ module Graphql
89
122
  @null = null
90
123
  end
91
124
 
92
- def to_ruby
93
- "field :#{@name}, #{@type_expr}, null: #{@null}"
125
+ def to_object_field
126
+ "field :#{@name}, #{@type_expr}#{@null ? '' : ', null: false'}"
127
+ end
128
+
129
+ def to_input_argument
130
+ "argument :#{@name}, #{@type_expr}, required: false"
94
131
  end
95
132
  end
96
133
  end
@@ -19,14 +19,14 @@ module Graphql
19
19
  banner: "type type ...",
20
20
  desc: "Possible types for this union (expressed as Ruby or GraphQL)"
21
21
 
22
- def create_type_file
23
- template "union.erb", "#{options[:directory]}/types/#{type_file_name}.rb"
24
- end
25
-
26
22
  private
27
23
 
24
+ def graphql_type
25
+ "union"
26
+ end
27
+
28
28
  def normalized_possible_types
29
- possible_types.map { |t| self.class.normalize_type_expression(t, mode: :ruby)[0] }
29
+ custom_fields.map { |t| self.class.normalize_type_expression(t, mode: :ruby)[0] }
30
30
  end
31
31
  end
32
32
  end
@@ -100,7 +100,8 @@ module GraphQL
100
100
  def on_field(node, parent)
101
101
  @response_path.push(node.alias || node.name)
102
102
  parent_type = @object_types.last
103
- field_definition = @schema.get_field(parent_type, node.name, @query.context)
103
+ # This could be nil if the previous field wasn't found:
104
+ field_definition = parent_type && @schema.get_field(parent_type, node.name, @query.context)
104
105
  @field_definitions.push(field_definition)
105
106
  if !field_definition.nil?
106
107
  next_object_type = field_definition.type.unwrap
@@ -138,8 +138,8 @@ module GraphQL
138
138
  # @api private
139
139
  def result_for(key)
140
140
  if !@results.key?(key)
141
- raise <<-ERR
142
- Invariant: fetching result for a key on #{self.class} that hasn't been loaded yet (#{key.inspect}, loaded: #{@results.keys})
141
+ raise GraphQL::InvariantError, <<-ERR
142
+ Fetching result for a key on #{self.class} that hasn't been loaded yet (#{key.inspect}, loaded: #{@results.keys})
143
143
 
144
144
  This key should have been loaded already. This is a bug in GraphQL::Dataloader, please report it on GitHub: https://github.com/rmosolgo/graphql-ruby/issues/new.
145
145
  ERR
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ # This error is raised when `Types::ISO8601Date` is asked to return a value
4
+ # that cannot be parsed to a Ruby Date.
5
+ #
6
+ # @see GraphQL::Types::ISO8601Date which raises this error
7
+ class DateEncodingError < GraphQL::RuntimeTypeError
8
+ # The value which couldn't be encoded
9
+ attr_reader :date_value
10
+
11
+ def initialize(value)
12
+ @date_value = value
13
+ super("Date cannot be parsed: #{value}. \nDate must be be able to be parsed as a Ruby Date object.")
14
+ end
15
+ end
16
+ end
@@ -207,7 +207,7 @@ module GraphQL
207
207
  # Root .authorized? returned false.
208
208
  @response = nil
209
209
  else
210
- resolve_with_directives(object_proxy, root_operation.directives) do # execute query level directives
210
+ call_method_on_directives(:resolve, object_proxy, root_operation.directives) do # execute query level directives
211
211
  gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
212
212
  # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
213
213
  # require isolation during execution (because of runtime directives). In that case,
@@ -227,7 +227,7 @@ module GraphQL
227
227
 
228
228
  @dataloader.append_job {
229
229
  set_all_interpreter_context(query.root_value, nil, nil, path)
230
- resolve_with_directives(object_proxy, selections.graphql_directives) do
230
+ call_method_on_directives(:resolve, object_proxy, selections.graphql_directives) do
231
231
  evaluate_selections(
232
232
  path,
233
233
  context.scoped_context,
@@ -503,7 +503,7 @@ module GraphQL
503
503
  }
504
504
  end
505
505
 
506
- field_result = resolve_with_directives(object, directives) do
506
+ field_result = call_method_on_directives(:resolve, object, directives) do
507
507
  # Actually call the field resolver and capture the result
508
508
  app_result = begin
509
509
  query.with_error_handling do
@@ -735,7 +735,7 @@ module GraphQL
735
735
  final_result = nil
736
736
  end
737
737
  set_all_interpreter_context(continue_value, nil, nil, path) # reset this mutable state
738
- resolve_with_directives(continue_value, selections.graphql_directives) do
738
+ call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
739
739
  evaluate_selections(
740
740
  path,
741
741
  context.scoped_context,
@@ -754,6 +754,8 @@ module GraphQL
754
754
  end
755
755
  when "LIST"
756
756
  inner_type = current_type.of_type
757
+ # This is true for objects, unions, and interfaces
758
+ use_dataloader_job = !inner_type.unwrap.kind.input?
757
759
  response_list = GraphQLResultArray.new(result_name, selection_result)
758
760
  response_list.graphql_non_null_list_items = inner_type.non_null?
759
761
  set_result(selection_result, result_name, response_list)
@@ -768,12 +770,12 @@ module GraphQL
768
770
  this_idx = idx
769
771
  next_path.freeze
770
772
  idx += 1
771
- # This will update `response_list` with the lazy
772
- after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
773
- continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node, this_idx, response_list)
774
- if HALT != continue_value
775
- continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
773
+ if use_dataloader_job
774
+ @dataloader.append_job do
775
+ resolve_list_item(inner_value, inner_type, next_path, ast_node, scoped_context, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
776
776
  end
777
+ else
778
+ resolve_list_item(inner_value, inner_type, next_path, ast_node, scoped_context, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
777
779
  end
778
780
  end
779
781
  rescue NoMethodError => err
@@ -793,12 +795,25 @@ module GraphQL
793
795
  end
794
796
  end
795
797
 
796
- def resolve_with_directives(object, directives, &block)
798
+ def resolve_list_item(inner_value, inner_type, next_path, ast_node, scoped_context, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type) # rubocop:disable Metrics/ParameterLists
799
+ set_all_interpreter_context(nil, nil, nil, next_path)
800
+ call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
801
+ # This will update `response_list` with the lazy
802
+ after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
803
+ continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node, this_idx, response_list)
804
+ if HALT != continue_value
805
+ continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
806
+ end
807
+ end
808
+ end
809
+ end
810
+
811
+ def call_method_on_directives(method_name, object, directives, &block)
797
812
  return yield if directives.nil? || directives.empty?
798
- run_directive(object, directives, 0, &block)
813
+ run_directive(method_name, object, directives, 0, &block)
799
814
  end
800
815
 
801
- def run_directive(object, directives, idx, &block)
816
+ def run_directive(method_name, object, directives, idx, &block)
802
817
  dir_node = directives[idx]
803
818
  if !dir_node
804
819
  yield
@@ -822,8 +837,8 @@ module GraphQL
822
837
  if dir_args == HALT
823
838
  nil
824
839
  else
825
- dir_defn.resolve(object, dir_args, context) do
826
- run_directive(object, directives, idx + 1, &block)
840
+ dir_defn.public_send(method_name, object, dir_args, context) do
841
+ run_directive(method_name, object, directives, idx + 1, &block)
827
842
  end
828
843
  end
829
844
  end
@@ -6,8 +6,8 @@ module GraphQL
6
6
  description "A Directive can be adjacent to many parts of the GraphQL language, "\
7
7
  "a __DirectiveLocation describes one such possible adjacencies."
8
8
 
9
- GraphQL::Directive::LOCATIONS.each do |location|
10
- value(location.to_s, GraphQL::Directive::LOCATION_DESCRIPTIONS[location], value: location)
9
+ GraphQL::Schema::Directive::LOCATIONS.each do |location|
10
+ value(location.to_s, GraphQL::Schema::Directive::LOCATION_DESCRIPTIONS[location], value: location)
11
11
  end
12
12
  introspection true
13
13
  end
@@ -19,6 +19,8 @@ module GraphQL
19
19
  field :on_fragment, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_fragment?
20
20
  field :on_field, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_field?
21
21
 
22
+ field :is_repeatable, Boolean, method: :repeatable?
23
+
22
24
  def args(include_deprecated:)
23
25
  args = @context.warden.arguments(@object)
24
26
  args = args.reject(&:deprecation_reason) unless include_deprecated
@@ -13,6 +13,11 @@ module GraphQL
13
13
  field :mutation_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server supports mutation, the type that mutation operations will be rooted at."
14
14
  field :subscription_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server support subscription, the type that subscription operations will be rooted at."
15
15
  field :directives, [GraphQL::Schema::LateBoundType.new("__Directive")], "A list of all directives supported by this server.", null: false
16
+ field :description, String, resolver_method: :schema_description
17
+
18
+ def schema_description
19
+ context.schema.description
20
+ end
16
21
 
17
22
  def types
18
23
  @context.warden.reachable_types.sort_by(&:graphql_name)
@@ -12,7 +12,7 @@ module GraphQL
12
12
  "possible at runtime. List and NonNull types compose other types."
13
13
 
14
14
  field :kind, GraphQL::Schema::LateBoundType.new("__TypeKind"), null: false
15
- field :name, String
15
+ field :name, String, method: :graphql_name
16
16
  field :description, String
17
17
  field :fields, [GraphQL::Schema::LateBoundType.new("__Field")] do
18
18
  argument :include_deprecated, Boolean, required: false, default_value: false
@@ -27,8 +27,14 @@ module GraphQL
27
27
  end
28
28
  field :of_type, GraphQL::Schema::LateBoundType.new("__Type")
29
29
 
30
- def name
31
- object.graphql_name
30
+ field :specified_by_url, String
31
+
32
+ def specified_by_url
33
+ if object.kind.scalar?
34
+ object.specified_by_url
35
+ else
36
+ nil
37
+ end
32
38
  end
33
39
 
34
40
  def kind
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  module Introspection
4
- def self.query(include_deprecated_args: false)
4
+ def self.query(include_deprecated_args: false, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false)
5
5
  # The introspection query to end all introspection queries, copied from
6
6
  # https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js
7
7
  <<-QUERY
8
8
  query IntrospectionQuery {
9
9
  __schema {
10
+ #{include_schema_description ? "description" : ""}
10
11
  queryType { name }
11
12
  mutationType { name }
12
13
  subscriptionType { name }
@@ -17,6 +18,7 @@ query IntrospectionQuery {
17
18
  name
18
19
  description
19
20
  locations
21
+ #{include_is_repeatable ? "isRepeatable" : ""}
20
22
  args#{include_deprecated_args ? '(includeDeprecated: true)' : ''} {
21
23
  ...InputValue
22
24
  }
@@ -27,6 +29,7 @@ fragment FullType on __Type {
27
29
  kind
28
30
  name
29
31
  description
32
+ #{include_specified_by_url ? "specifiedByUrl" : ""}
30
33
  fields(includeDeprecated: true) {
31
34
  name
32
35
  description
@@ -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
 
@@ -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)
@@ -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
@@ -128,6 +133,10 @@ module GraphQL
128
133
  @arguments = self.class.coerce_arguments(nil, arguments, Query::NullContext)
129
134
  end
130
135
 
136
+ def graphql_name
137
+ self.class.graphql_name
138
+ end
139
+
131
140
  LOCATIONS = [
132
141
  QUERY = :QUERY,
133
142
  MUTATION = :MUTATION,
@@ -147,6 +156,7 @@ module GraphQL
147
156
  ENUM_VALUE = :ENUM_VALUE,
148
157
  INPUT_OBJECT = :INPUT_OBJECT,
149
158
  INPUT_FIELD_DEFINITION = :INPUT_FIELD_DEFINITION,
159
+ VARIABLE_DEFINITION = :VARIABLE_DEFINITION,
150
160
  ]
151
161
 
152
162
  DEFAULT_DEPRECATION_REASON = 'No longer supported'
@@ -169,6 +179,7 @@ module GraphQL
169
179
  ENUM_VALUE: 'Location adjacent to an enum value definition.',
170
180
  INPUT_OBJECT: 'Location adjacent to an input object type definition.',
171
181
  INPUT_FIELD_DEFINITION: 'Location adjacent to an input object field definition.',
182
+ VARIABLE_DEFINITION: 'Location adjacent to a variable definition.',
172
183
  }
173
184
 
174
185
  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
@@ -424,11 +428,14 @@ module GraphQL
424
428
  elsif connection?
425
429
  arguments = query.arguments_for(nodes.first, self)
426
430
  max_possible_page_size = nil
427
- if arguments[:first]
428
- max_possible_page_size = arguments[:first]
429
- end
430
- if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size)
431
- max_possible_page_size = arguments[:last]
431
+ if arguments.respond_to?(:[]) # It might have been an error
432
+ if arguments[:first]
433
+ max_possible_page_size = arguments[:first]
434
+ end
435
+
436
+ if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size)
437
+ max_possible_page_size = arguments[:last]
438
+ end
432
439
  end
433
440
 
434
441
  if max_possible_page_size.nil?
@@ -822,7 +829,9 @@ module GraphQL
822
829
  end
823
830
  elsif obj.object.is_a?(Hash)
824
831
  inner_object = obj.object
825
- if inner_object.key?(@method_sym)
832
+ if @dig_keys
833
+ inner_object.dig(*@dig_keys)
834
+ elsif inner_object.key?(@method_sym)
826
835
  inner_object[@method_sym]
827
836
  else
828
837
  inner_object[@method_str]
@@ -81,7 +81,7 @@ module GraphQL
81
81
 
82
82
  def self.authorized?(obj, value, ctx)
83
83
  # Authorize each argument (but this doesn't apply if `prepare` is implemented):
84
- if value.is_a?(InputObject)
84
+ if value.respond_to?(:key?)
85
85
  arguments(ctx).each do |_name, input_obj_arg|
86
86
  input_obj_arg = input_obj_arg.type_class
87
87
  if value.key?(input_obj_arg.keyword) &&
@@ -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))