graphql 1.13.4 → 1.13.8

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 +4 -1
  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 +4 -4
  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 +17 -5
@@ -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
@@ -31,8 +31,10 @@ module GraphQL
31
31
  # If any jobs were enqueued, run them now,
32
32
  # since this might have been called outside of execution.
33
33
  # (The jobs are responsible for updating `result` in-place.)
34
- @dataloader.run_isolated do
35
- @storage[ast_node][argument_owner][parent_object]
34
+ if !@storage.key?(ast_node) || !@storage[ast_node].key?(argument_owner)
35
+ @dataloader.run_isolated do
36
+ @storage[ast_node][argument_owner][parent_object]
37
+ end
36
38
  end
37
39
  # Ack, the _hash_ is updated, but the key is eventually
38
40
  # overridden with an immutable arguments instance.
@@ -159,7 +159,8 @@ module GraphQL
159
159
  # Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
160
160
  @runtime_directive_names = []
161
161
  noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
162
- schema.directives.each do |name, dir_defn|
162
+ @schema_directives = schema.directives
163
+ @schema_directives.each do |name, dir_defn|
163
164
  if dir_defn.method(:resolve).owner != noop_resolve_owner
164
165
  @runtime_directive_names << name
165
166
  end
@@ -206,7 +207,7 @@ module GraphQL
206
207
  # Root .authorized? returned false.
207
208
  @response = nil
208
209
  else
209
- 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
210
211
  gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
211
212
  # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
212
213
  # require isolation during execution (because of runtime directives). In that case,
@@ -226,7 +227,7 @@ module GraphQL
226
227
 
227
228
  @dataloader.append_job {
228
229
  set_all_interpreter_context(query.root_value, nil, nil, path)
229
- resolve_with_directives(object_proxy, selections.graphql_directives) do
230
+ call_method_on_directives(:resolve, object_proxy, selections.graphql_directives) do
230
231
  evaluate_selections(
231
232
  path,
232
233
  context.scoped_context,
@@ -502,7 +503,7 @@ module GraphQL
502
503
  }
503
504
  end
504
505
 
505
- field_result = resolve_with_directives(object, directives) do
506
+ field_result = call_method_on_directives(:resolve, object, directives) do
506
507
  # Actually call the field resolver and capture the result
507
508
  app_result = begin
508
509
  query.with_error_handling do
@@ -734,7 +735,7 @@ module GraphQL
734
735
  final_result = nil
735
736
  end
736
737
  set_all_interpreter_context(continue_value, nil, nil, path) # reset this mutable state
737
- resolve_with_directives(continue_value, selections.graphql_directives) do
738
+ call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
738
739
  evaluate_selections(
739
740
  path,
740
741
  context.scoped_context,
@@ -753,6 +754,8 @@ module GraphQL
753
754
  end
754
755
  when "LIST"
755
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?
756
759
  response_list = GraphQLResultArray.new(result_name, selection_result)
757
760
  response_list.graphql_non_null_list_items = inner_type.non_null?
758
761
  set_result(selection_result, result_name, response_list)
@@ -767,12 +770,12 @@ module GraphQL
767
770
  this_idx = idx
768
771
  next_path.freeze
769
772
  idx += 1
770
- # This will update `response_list` with the lazy
771
- 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|
772
- continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node, this_idx, response_list)
773
- if HALT != continue_value
774
- 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)
775
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)
776
779
  end
777
780
  end
778
781
  rescue NoMethodError => err
@@ -792,17 +795,30 @@ module GraphQL
792
795
  end
793
796
  end
794
797
 
795
- 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)
796
812
  return yield if directives.nil? || directives.empty?
797
- run_directive(object, directives, 0, &block)
813
+ run_directive(method_name, object, directives, 0, &block)
798
814
  end
799
815
 
800
- def run_directive(object, directives, idx, &block)
816
+ def run_directive(method_name, object, directives, idx, &block)
801
817
  dir_node = directives[idx]
802
818
  if !dir_node
803
819
  yield
804
820
  else
805
- dir_defn = schema.directives.fetch(dir_node.name)
821
+ dir_defn = @schema_directives.fetch(dir_node.name)
806
822
  if !dir_defn.is_a?(Class)
807
823
  dir_defn = dir_defn.type_class || raise("Only class-based directives are supported (not `@#{dir_node.name}`)")
808
824
  end
@@ -821,8 +837,8 @@ module GraphQL
821
837
  if dir_args == HALT
822
838
  nil
823
839
  else
824
- dir_defn.resolve(object, dir_args, context) do
825
- 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)
826
842
  end
827
843
  end
828
844
  end
@@ -831,7 +847,7 @@ module GraphQL
831
847
  # Check {Schema::Directive.include?} for each directive that's present
832
848
  def directives_include?(node, graphql_object, parent_type)
833
849
  node.directives.each do |dir_node|
834
- dir_defn = schema.directives.fetch(dir_node.name).type_class || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
850
+ dir_defn = @schema_directives.fetch(dir_node.name).type_class || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
835
851
  args = arguments(graphql_object, dir_defn, dir_node)
836
852
  if !dir_defn.include?(graphql_object, args, context)
837
853
  return false
@@ -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
@@ -152,6 +152,7 @@ module GraphQL
152
152
  def build_directive_node(directive)
153
153
  GraphQL::Language::Nodes::DirectiveDefinition.new(
154
154
  name: directive.graphql_name,
155
+ repeatable: directive.repeatable?,
155
156
  arguments: build_argument_nodes(warden.arguments(directive)),
156
157
  locations: build_directive_location_nodes(directive.locations),
157
158
  description: directive.description,