graphql 1.12.4 → 1.12.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +2 -1
  3. data/lib/generators/graphql/loader_generator.rb +1 -0
  4. data/lib/generators/graphql/mutation_generator.rb +1 -0
  5. data/lib/generators/graphql/relay_generator.rb +1 -0
  6. data/lib/generators/graphql/templates/graphql_controller.erb +2 -2
  7. data/lib/generators/graphql/type_generator.rb +1 -0
  8. data/lib/graphql.rb +13 -11
  9. data/lib/graphql/backtrace/tracer.rb +2 -2
  10. data/lib/graphql/dataloader.rb +45 -14
  11. data/lib/graphql/execution/errors.rb +109 -11
  12. data/lib/graphql/execution/interpreter.rb +1 -1
  13. data/lib/graphql/execution/interpreter/runtime.rb +17 -24
  14. data/lib/graphql/introspection.rb +1 -1
  15. data/lib/graphql/introspection/directive_type.rb +7 -3
  16. data/lib/graphql/language.rb +1 -0
  17. data/lib/graphql/language/cache.rb +37 -0
  18. data/lib/graphql/language/parser.rb +15 -5
  19. data/lib/graphql/language/parser.y +15 -5
  20. data/lib/graphql/pagination/active_record_relation_connection.rb +7 -0
  21. data/lib/graphql/pagination/connection.rb +15 -1
  22. data/lib/graphql/pagination/connections.rb +2 -1
  23. data/lib/graphql/pagination/relation_connection.rb +12 -1
  24. data/lib/graphql/query.rb +1 -3
  25. data/lib/graphql/query/validation_pipeline.rb +1 -1
  26. data/lib/graphql/railtie.rb +9 -1
  27. data/lib/graphql/relay/range_add.rb +10 -5
  28. data/lib/graphql/schema.rb +32 -45
  29. data/lib/graphql/schema/field/connection_extension.rb +1 -0
  30. data/lib/graphql/schema/input_object.rb +2 -2
  31. data/lib/graphql/schema/member/base_dsl_methods.rb +3 -15
  32. data/lib/graphql/schema/object.rb +19 -5
  33. data/lib/graphql/schema/resolver.rb +28 -1
  34. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +3 -1
  35. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +6 -2
  36. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
  37. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  38. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -1
  39. data/lib/graphql/static_validation/rules/fields_will_merge.rb +17 -8
  40. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  41. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +4 -3
  42. data/lib/graphql/subscriptions/broadcast_analyzer.rb +0 -3
  43. data/lib/graphql/subscriptions/serialize.rb +3 -0
  44. data/lib/graphql/tracing/active_support_notifications_tracing.rb +2 -1
  45. data/lib/graphql/types/relay/base_connection.rb +4 -0
  46. data/lib/graphql/types/relay/connection_behaviors.rb +38 -5
  47. data/lib/graphql/types/relay/edge_behaviors.rb +12 -1
  48. data/lib/graphql/version.rb +1 -1
  49. data/readme.md +1 -1
  50. metadata +3 -2
@@ -17,7 +17,7 @@ query IntrospectionQuery {
17
17
  name
18
18
  description
19
19
  locations
20
- args {
20
+ args#{include_deprecated_args ? '(includeDeprecated: true)' : ''} {
21
21
  ...InputValue
22
22
  }
23
23
  }
@@ -12,13 +12,17 @@ module GraphQL
12
12
  field :name, String, null: false, method: :graphql_name
13
13
  field :description, String, null: true
14
14
  field :locations, [GraphQL::Schema::LateBoundType.new("__DirectiveLocation")], null: false
15
- field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false
15
+ field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false do
16
+ argument :include_deprecated, Boolean, required: false, default_value: false
17
+ end
16
18
  field :on_operation, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_operation?
17
19
  field :on_fragment, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_fragment?
18
20
  field :on_field, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_field?
19
21
 
20
- def args
21
- @context.warden.arguments(@object)
22
+ def args(include_deprecated:)
23
+ args = @context.warden.arguments(@object)
24
+ args = args.reject(&:deprecation_reason) unless include_deprecated
25
+ args
22
26
  end
23
27
  end
24
28
  end
@@ -6,6 +6,7 @@ require "graphql/language/document_from_schema_definition"
6
6
  require "graphql/language/generation"
7
7
  require "graphql/language/lexer"
8
8
  require "graphql/language/nodes"
9
+ require "graphql/language/cache"
9
10
  require "graphql/language/parser"
10
11
  require "graphql/language/token"
11
12
  require "graphql/language/visitor"
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql/version'
4
+ require 'digest/sha2'
5
+
6
+ module GraphQL
7
+ module Language
8
+ class Cache
9
+ def initialize(path)
10
+ @path = path
11
+ end
12
+
13
+ DIGEST = Digest::SHA256.new << GraphQL::VERSION
14
+ def fetch(filename)
15
+ hash = DIGEST.dup << filename
16
+ begin
17
+ hash << File.mtime(filename).to_i.to_s
18
+ rescue SystemCallError
19
+ return yield
20
+ end
21
+ cache_path = @path.join(hash.to_s)
22
+
23
+ if cache_path.exist?
24
+ Marshal.load(cache_path.read)
25
+ else
26
+ payload = yield
27
+ tmp_path = "#{cache_path}.#{rand}"
28
+
29
+ @path.mkpath
30
+ File.binwrite(tmp_path, Marshal.dump(payload))
31
+ File.rename(tmp_path, cache_path.to_s)
32
+ payload
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -41,12 +41,22 @@ def parse_document
41
41
  end
42
42
  end
43
43
 
44
- def self.parse(query_string, filename: nil, tracer: GraphQL::Tracing::NullTracer)
45
- self.new(query_string, filename: filename, tracer: tracer).parse_document
46
- end
44
+ class << self
45
+ attr_accessor :cache
46
+
47
+ def parse(query_string, filename: nil, tracer: GraphQL::Tracing::NullTracer)
48
+ new(query_string, filename: filename, tracer: tracer).parse_document
49
+ end
47
50
 
48
- def self.parse_file(filename, tracer: GraphQL::Tracing::NullTracer)
49
- self.parse(File.read(filename), filename: filename, tracer: tracer)
51
+ def parse_file(filename, tracer: GraphQL::Tracing::NullTracer)
52
+ if cache
53
+ cache.fetch(filename) do
54
+ parse(File.read(filename), filename: filename, tracer: tracer)
55
+ end
56
+ else
57
+ parse(File.read(filename), filename: filename, tracer: tracer)
58
+ end
59
+ end
50
60
  end
51
61
 
52
62
  private
@@ -462,12 +462,22 @@ def parse_document
462
462
  end
463
463
  end
464
464
 
465
- def self.parse(query_string, filename: nil, tracer: GraphQL::Tracing::NullTracer)
466
- self.new(query_string, filename: filename, tracer: tracer).parse_document
467
- end
465
+ class << self
466
+ attr_accessor :cache
467
+
468
+ def parse(query_string, filename: nil, tracer: GraphQL::Tracing::NullTracer)
469
+ new(query_string, filename: filename, tracer: tracer).parse_document
470
+ end
468
471
 
469
- def self.parse_file(filename, tracer: GraphQL::Tracing::NullTracer)
470
- self.parse(File.read(filename), filename: filename, tracer: tracer)
472
+ def parse_file(filename, tracer: GraphQL::Tracing::NullTracer)
473
+ if cache
474
+ cache.fetch(filename) do
475
+ parse(File.read(filename), filename: filename, tracer: tracer)
476
+ end
477
+ else
478
+ parse(File.read(filename), filename: filename, tracer: tracer)
479
+ end
480
+ end
471
481
  end
472
482
 
473
483
  private
@@ -5,6 +5,13 @@ module GraphQL
5
5
  module Pagination
6
6
  # Customizes `RelationConnection` to work with `ActiveRecord::Relation`s.
7
7
  class ActiveRecordRelationConnection < Pagination::RelationConnection
8
+ private
9
+
10
+ def relation_larger_than(relation, size)
11
+ initial_offset = relation.offset_value || 0
12
+ relation.offset(initial_offset + size).exists?
13
+ end
14
+
8
15
  def relation_count(relation)
9
16
  int_or_hash = if relation.respond_to?(:unscope)
10
17
  relation.unscope(:order).count(:all)
@@ -45,6 +45,9 @@ module GraphQL
45
45
  end
46
46
  end
47
47
 
48
+ # @return [Hash<Symbol => Object>] The field arguments from the field that returned this connection
49
+ attr_accessor :arguments
50
+
48
51
  # @param items [Object] some unpaginated collection item, like an `Array` or `ActiveRecord::Relation`
49
52
  # @param context [Query::Context]
50
53
  # @param parent [Object] The object this collection belongs to
@@ -52,8 +55,9 @@ module GraphQL
52
55
  # @param after [String, nil] A cursor for pagination, if the client provided one
53
56
  # @param last [Integer, nil] Limit parameter from the client, if provided
54
57
  # @param before [String, nil] A cursor for pagination, if the client provided one.
58
+ # @param arguments [Hash] The arguments to the field that returned the collection wrapped by this connection
55
59
  # @param max_page_size [Integer, nil] A configured value to cap the result size. Applied as `first` if neither first or last are given.
56
- def initialize(items, parent: nil, field: nil, context: nil, first: nil, after: nil, max_page_size: :not_given, last: nil, before: nil, edge_class: nil)
60
+ def initialize(items, parent: nil, field: nil, context: nil, first: nil, after: nil, max_page_size: :not_given, last: nil, before: nil, edge_class: nil, arguments: nil)
57
61
  @items = items
58
62
  @parent = parent
59
63
  @context = context
@@ -62,6 +66,7 @@ module GraphQL
62
66
  @after_value = after
63
67
  @last_value = last
64
68
  @before_value = before
69
+ @arguments = arguments
65
70
  @edge_class = edge_class || self.class::Edge
66
71
  # This is only true if the object was _initialized_ with an override
67
72
  # or if one is assigned later.
@@ -105,6 +110,15 @@ module GraphQL
105
110
  end
106
111
  end
107
112
 
113
+ # This is called by `Relay::RangeAdd` -- it can be overridden
114
+ # when `item` needs some modifications based on this connection's state.
115
+ #
116
+ # @param item [Object] An item newly added to `items`
117
+ # @return [Edge]
118
+ def range_add_edge(item)
119
+ edge_class.new(item, self)
120
+ end
121
+
108
122
  attr_writer :last
109
123
  # @return [Integer, nil] A clamped `last` value. (The underlying instance variable doesn't have limits on it)
110
124
  def last
@@ -79,11 +79,12 @@ module GraphQL
79
79
  context: context,
80
80
  parent: parent,
81
81
  field: field,
82
- max_page_size: field.max_page_size || context.schema.default_max_page_size,
82
+ max_page_size: field.has_max_page_size? ? field.max_page_size : context.schema.default_max_page_size,
83
83
  first: arguments[:first],
84
84
  after: arguments[:after],
85
85
  last: arguments[:last],
86
86
  before: arguments[:before],
87
+ arguments: arguments,
87
88
  edge_class: edge_class_for_field(field),
88
89
  )
89
90
  end
@@ -32,7 +32,11 @@ module GraphQL
32
32
  @has_next_page = if before_offset && before_offset > 0
33
33
  true
34
34
  elsif first
35
- relation_count(set_limit(sliced_nodes, first + 1)) == first + 1
35
+ if @nodes && @nodes.count < first
36
+ false
37
+ else
38
+ relation_larger_than(sliced_nodes, first)
39
+ end
36
40
  else
37
41
  false
38
42
  end
@@ -49,6 +53,13 @@ module GraphQL
49
53
 
50
54
  private
51
55
 
56
+ # @param relation [Object] A database query object
57
+ # @param size [Integer] The value against which we check the relation size
58
+ # @return [Boolean] True if the number of items in this relation is larger than `size`
59
+ def relation_larger_than(relation, size)
60
+ relation_count(set_limit(relation, size + 1)) == size + 1
61
+ end
62
+
52
63
  # @param relation [Object] A database query object
53
64
  # @return [Integer, nil] The offset value, or nil if there isn't one
54
65
  def relation_offset(relation)
data/lib/graphql/query.rb CHANGED
@@ -195,9 +195,7 @@ module GraphQL
195
195
  # @return [Hash] A GraphQL response, with `"data"` and/or `"errors"` keys
196
196
  def result
197
197
  if !@executed
198
- with_prepared_ast {
199
- Execution::Multiplex.run_queries(@schema, [self], context: @context)
200
- }
198
+ Execution::Multiplex.run_queries(@schema, [self], context: @context)
201
199
  end
202
200
  @result ||= Query::Result.new(query: self, values: @result_values)
203
201
  end
@@ -36,7 +36,7 @@ module GraphQL
36
36
  @valid
37
37
  end
38
38
 
39
- # @return [Array<GraphQL::StaticValidation::Error >] Static validation errors for the query string
39
+ # @return [Array<GraphQL::StaticValidation::Error, GraphQL::Query::VariableValidationError>] Static validation errors for the query string
40
40
  def validation_errors
41
41
  ensure_has_validated
42
42
  @validation_errors
@@ -1,8 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
-
4
3
  module GraphQL
5
4
  class Railtie < Rails::Railtie
5
+ config.before_configuration do
6
+ # Bootsnap compile cache has similar expiration properties,
7
+ # so we assume that if the user has bootsnap setup it's ok
8
+ # to piggy back on it.
9
+ if ::Object.const_defined?("Bootsnap::CompileCache::ISeq") && Bootsnap::CompileCache::ISeq.cache_dir
10
+ Language::Parser.cache ||= Language::Cache.new(Pathname.new(Bootsnap::CompileCache::ISeq.cache_dir).join('graphql'))
11
+ end
12
+ end
13
+
6
14
  rake_tasks do
7
15
  # Defer this so that you only need the `parser` gem when you _run_ the upgrader
8
16
  def load_upgraders
@@ -9,7 +9,7 @@ module GraphQL
9
9
  # should be ordered and paginated before providing it here.
10
10
  #
11
11
  # @example Adding a comment to list of comments
12
- # post = Post.find(args[:postId])
12
+ # post = Post.find(args[:post_id])
13
13
  # comments = post.comments
14
14
  # new_comment = comments.build(body: args[:body])
15
15
  # new_comment.save!
@@ -18,13 +18,13 @@ module GraphQL
18
18
  # parent: post,
19
19
  # collection: comments,
20
20
  # item: new_comment,
21
- # context: ctx,
21
+ # context: context,
22
22
  # )
23
23
  #
24
24
  # response = {
25
25
  # post: post,
26
- # commentsConnection: range_add.connection,
27
- # newCommentEdge: range_add.edge,
26
+ # comments_connection: range_add.connection,
27
+ # new_comment_edge: range_add.edge,
28
28
  # }
29
29
  class RangeAdd
30
30
  attr_reader :edge, :connection, :parent
@@ -39,7 +39,12 @@ module GraphQL
39
39
  conn_class = context.schema.connections.wrapper_for(collection)
40
40
  # The rest will be added by ConnectionExtension
41
41
  @connection = conn_class.new(collection, parent: parent, context: context, edge_class: edge_class)
42
- @edge = @connection.edge_class.new(item, @connection)
42
+ # Check if this connection supports it, to support old versions of GraphQL-Pro
43
+ @edge = if @connection.respond_to?(:range_add_edge)
44
+ @connection.range_add_edge(item)
45
+ else
46
+ @connection.edge_class.new(item, @connection)
47
+ end
43
48
  else
44
49
  connection_class = BaseConnection.connection_for_nodes(collection)
45
50
  @connection = connection_class.new(collection, {}, parent: parent, context: context)
@@ -355,23 +355,6 @@ module GraphQL
355
355
  # For forwards-compatibility with Schema classes
356
356
  alias :graphql_definition :itself
357
357
 
358
- # Validate a query string according to this schema.
359
- # @param string_or_document [String, GraphQL::Language::Nodes::Document]
360
- # @return [Array<GraphQL::StaticValidation::Error >]
361
- def validate(string_or_document, rules: nil, context: nil)
362
- doc = if string_or_document.is_a?(String)
363
- GraphQL.parse(string_or_document)
364
- else
365
- string_or_document
366
- end
367
- query = GraphQL::Query.new(self, document: doc, context: context)
368
- validator_opts = { schema: self }
369
- rules && (validator_opts[:rules] = rules)
370
- validator = GraphQL::StaticValidation::Validator.new(**validator_opts)
371
- res = validator.validate(query, timeout: validate_timeout)
372
- res[:errors]
373
- end
374
-
375
358
  def deprecated_define(**kwargs, &block)
376
359
  super
377
360
  ensure_defined
@@ -711,7 +694,8 @@ module GraphQL
711
694
  alias :_schema_class :class
712
695
  def_delegators :_schema_class, :unauthorized_object, :unauthorized_field, :inaccessible_fields
713
696
  def_delegators :_schema_class, :directive
714
- def_delegators :_schema_class, :error_handler, :rescues
697
+ def_delegators :_schema_class, :error_handler
698
+ def_delegators :_schema_class, :validate
715
699
 
716
700
 
717
701
  # Given this schema member, find the class-based definition object
@@ -861,7 +845,6 @@ module GraphQL
861
845
  def_delegators :graphql_definition,
862
846
  # Execution
863
847
  :execution_strategy_for_operation,
864
- :validate,
865
848
  # Configuration
866
849
  :metadata, :redefine,
867
850
  :id_from_object_proc, :object_from_id_proc,
@@ -989,7 +972,7 @@ module GraphQL
989
972
  schema_defn.lazy_methods.set(lazy_class, value_method)
990
973
  end
991
974
 
992
- rescues.each do |err_class, handler|
975
+ error_handler.each_rescue do |err_class, handler|
993
976
  schema_defn.rescue_from(err_class, &handler)
994
977
  end
995
978
 
@@ -1118,14 +1101,15 @@ module GraphQL
1118
1101
  type.possible_types(context: context)
1119
1102
  else
1120
1103
  stored_possible_types = own_possible_types[type.graphql_name]
1121
- visible_possible_types = stored_possible_types.select do |possible_type|
1122
- next true unless type.kind.interface?
1123
- next true unless possible_type.kind.object?
1124
-
1125
- # Use `.graphql_name` comparison to match legacy vs class-based types.
1126
- # When we don't need to support legacy `.define` types, use `.include?(type)` instead.
1127
- possible_type.interfaces(context).any? { |interface| interface.graphql_name == type.graphql_name }
1128
- end if stored_possible_types
1104
+ visible_possible_types = if stored_possible_types && type.kind.interface?
1105
+ stored_possible_types.select do |possible_type|
1106
+ # Use `.graphql_name` comparison to match legacy vs class-based types.
1107
+ # When we don't need to support legacy `.define` types, use `.include?(type)` instead.
1108
+ possible_type.interfaces(context).any? { |interface| interface.graphql_name == type.graphql_name }
1109
+ end
1110
+ else
1111
+ stored_possible_types
1112
+ end
1129
1113
  visible_possible_types ||
1130
1114
  introspection_system.possible_types[type.graphql_name] ||
1131
1115
  (
@@ -1292,6 +1276,23 @@ module GraphQL
1292
1276
  end
1293
1277
  end
1294
1278
 
1279
+ # Validate a query string according to this schema.
1280
+ # @param string_or_document [String, GraphQL::Language::Nodes::Document]
1281
+ # @return [Array<GraphQL::StaticValidation::Error >]
1282
+ def validate(string_or_document, rules: nil, context: nil)
1283
+ doc = if string_or_document.is_a?(String)
1284
+ GraphQL.parse(string_or_document)
1285
+ else
1286
+ string_or_document
1287
+ end
1288
+ query = GraphQL::Query.new(self, document: doc, context: context)
1289
+ validator_opts = { schema: self }
1290
+ rules && (validator_opts[:rules] = rules)
1291
+ validator = GraphQL::StaticValidation::Validator.new(**validator_opts)
1292
+ res = validator.validate(query, timeout: validate_timeout)
1293
+ res[:errors]
1294
+ end
1295
+
1295
1296
  attr_writer :max_complexity
1296
1297
 
1297
1298
  def max_complexity(max_complexity = nil)
@@ -1423,7 +1424,7 @@ module GraphQL
1423
1424
 
1424
1425
  def rescue_from(*err_classes, &handler_block)
1425
1426
  err_classes.each do |err_class|
1426
- own_rescues[err_class] = handler_block
1427
+ error_handler.rescue_from(err_class, handler_block)
1427
1428
  end
1428
1429
  end
1429
1430
 
@@ -1467,10 +1468,6 @@ module GraphQL
1467
1468
  super
1468
1469
  end
1469
1470
 
1470
- def rescues
1471
- find_inherited_value(:rescues, EMPTY_HASH).merge(own_rescues)
1472
- end
1473
-
1474
1471
  def object_from_id(node_id, ctx)
1475
1472
  raise GraphQL::RequiredImplementationMissingError, "#{self.name}.object_from_id(node_id, ctx) must be implemented to load by ID (tried to load from id `#{node_id}`)"
1476
1473
  end
@@ -1547,15 +1544,10 @@ module GraphQL
1547
1544
  def parse_error(parse_err, ctx)
1548
1545
  ctx.errors.push(parse_err)
1549
1546
  end
1550
- attr_writer :error_handler
1551
1547
 
1552
- # @return [GraphQL::Execution::Errors, Class<GraphQL::Execution::Errors::NullErrorHandler>]
1548
+ # @return [GraphQL::Execution::Errors]
1553
1549
  def error_handler
1554
- if defined?(@error_handler)
1555
- @error_handler
1556
- else
1557
- find_inherited_value(:error_handler, GraphQL::Execution::Errors::NullErrorHandler)
1558
- end
1550
+ @error_handler ||= GraphQL::Execution::Errors.new(self)
1559
1551
  end
1560
1552
 
1561
1553
  def lazy_resolve(lazy_class, value_method)
@@ -1743,10 +1735,6 @@ module GraphQL
1743
1735
  @own_plugins ||= []
1744
1736
  end
1745
1737
 
1746
- def own_rescues
1747
- @own_rescues ||= {}
1748
- end
1749
-
1750
1738
  def own_orphan_types
1751
1739
  @own_orphan_types ||= []
1752
1740
  end
@@ -1989,7 +1977,6 @@ module GraphQL
1989
1977
  end
1990
1978
 
1991
1979
  # Install these here so that subclasses will also install it.
1992
- use(GraphQL::Execution::Errors)
1993
1980
  use(GraphQL::Pagination::Connections)
1994
1981
 
1995
1982
  protected