graphql 1.12.5 → 1.12.10

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +1 -1
  3. data/lib/generators/graphql/templates/graphql_controller.erb +2 -2
  4. data/lib/graphql.rb +13 -11
  5. data/lib/graphql/dataloader.rb +36 -5
  6. data/lib/graphql/execution/errors.rb +109 -11
  7. data/lib/graphql/execution/interpreter/runtime.rb +32 -36
  8. data/lib/graphql/introspection.rb +1 -1
  9. data/lib/graphql/introspection/directive_type.rb +7 -3
  10. data/lib/graphql/language.rb +1 -0
  11. data/lib/graphql/language/cache.rb +37 -0
  12. data/lib/graphql/language/parser.rb +15 -5
  13. data/lib/graphql/language/parser.y +15 -5
  14. data/lib/graphql/pagination/active_record_relation_connection.rb +7 -0
  15. data/lib/graphql/pagination/connection.rb +6 -1
  16. data/lib/graphql/pagination/connections.rb +2 -1
  17. data/lib/graphql/pagination/relation_connection.rb +12 -1
  18. data/lib/graphql/query.rb +1 -3
  19. data/lib/graphql/query/null_context.rb +7 -1
  20. data/lib/graphql/query/validation_pipeline.rb +1 -1
  21. data/lib/graphql/railtie.rb +9 -1
  22. data/lib/graphql/rake_task.rb +3 -0
  23. data/lib/graphql/schema.rb +23 -37
  24. data/lib/graphql/schema/argument.rb +3 -1
  25. data/lib/graphql/schema/field/connection_extension.rb +1 -0
  26. data/lib/graphql/schema/input_object.rb +2 -2
  27. data/lib/graphql/schema/loader.rb +8 -0
  28. data/lib/graphql/schema/member/base_dsl_methods.rb +3 -15
  29. data/lib/graphql/schema/object.rb +19 -5
  30. data/lib/graphql/schema/resolver.rb +24 -22
  31. data/lib/graphql/schema/scalar.rb +3 -1
  32. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +3 -1
  33. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +6 -2
  34. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
  35. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  36. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -1
  37. data/lib/graphql/static_validation/rules/fields_will_merge.rb +17 -8
  38. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  39. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  40. data/lib/graphql/static_validation/validator.rb +5 -0
  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 +11 -1
  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. metadata +7 -6
@@ -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.
@@ -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
@@ -9,10 +9,16 @@ module GraphQL
9
9
  def visible_type?(t); true; end
10
10
  end
11
11
 
12
+ class NullQuery
13
+ def with_error_handling
14
+ yield
15
+ end
16
+ end
17
+
12
18
  attr_reader :schema, :query, :warden, :dataloader
13
19
 
14
20
  def initialize
15
- @query = nil
21
+ @query = NullQuery.new
16
22
  @dataloader = GraphQL::Dataloader::NullDataloader.new
17
23
  @schema = GraphQL::Schema.new
18
24
  @warden = NullWarden.new(
@@ -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
@@ -98,6 +98,9 @@ module GraphQL
98
98
  result = schema.public_send(method_name, only: @only, except: @except, context: context)
99
99
  dir = File.dirname(file)
100
100
  FileUtils.mkdir_p(dir)
101
+ if !result.end_with?("\n")
102
+ result += "\n"
103
+ end
101
104
  File.write(file, result)
102
105
  end
103
106
 
@@ -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
 
@@ -1293,6 +1276,23 @@ module GraphQL
1293
1276
  end
1294
1277
  end
1295
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
+
1296
1296
  attr_writer :max_complexity
1297
1297
 
1298
1298
  def max_complexity(max_complexity = nil)
@@ -1424,7 +1424,7 @@ module GraphQL
1424
1424
 
1425
1425
  def rescue_from(*err_classes, &handler_block)
1426
1426
  err_classes.each do |err_class|
1427
- own_rescues[err_class] = handler_block
1427
+ error_handler.rescue_from(err_class, handler_block)
1428
1428
  end
1429
1429
  end
1430
1430
 
@@ -1468,10 +1468,6 @@ module GraphQL
1468
1468
  super
1469
1469
  end
1470
1470
 
1471
- def rescues
1472
- find_inherited_value(:rescues, EMPTY_HASH).merge(own_rescues)
1473
- end
1474
-
1475
1471
  def object_from_id(node_id, ctx)
1476
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}`)"
1477
1473
  end
@@ -1548,15 +1544,10 @@ module GraphQL
1548
1544
  def parse_error(parse_err, ctx)
1549
1545
  ctx.errors.push(parse_err)
1550
1546
  end
1551
- attr_writer :error_handler
1552
1547
 
1553
- # @return [GraphQL::Execution::Errors, Class<GraphQL::Execution::Errors::NullErrorHandler>]
1548
+ # @return [GraphQL::Execution::Errors]
1554
1549
  def error_handler
1555
- if defined?(@error_handler)
1556
- @error_handler
1557
- else
1558
- find_inherited_value(:error_handler, GraphQL::Execution::Errors::NullErrorHandler)
1559
- end
1550
+ @error_handler ||= GraphQL::Execution::Errors.new(self)
1560
1551
  end
1561
1552
 
1562
1553
  def lazy_resolve(lazy_class, value_method)
@@ -1744,10 +1735,6 @@ module GraphQL
1744
1735
  @own_plugins ||= []
1745
1736
  end
1746
1737
 
1747
- def own_rescues
1748
- @own_rescues ||= {}
1749
- end
1750
-
1751
1738
  def own_orphan_types
1752
1739
  @own_orphan_types ||= []
1753
1740
  end
@@ -1990,7 +1977,6 @@ module GraphQL
1990
1977
  end
1991
1978
 
1992
1979
  # Install these here so that subclasses will also install it.
1993
- use(GraphQL::Execution::Errors)
1994
1980
  use(GraphQL::Pagination::Connections)
1995
1981
 
1996
1982
  protected
@@ -266,7 +266,9 @@ module GraphQL
266
266
  loaded_values = coerced_value.map { |val| owner.load_application_object(self, loads, val, context) }
267
267
  context.schema.after_any_lazies(loaded_values) { |result| result }
268
268
  else
269
- owner.load_application_object(self, loads, coerced_value, context)
269
+ context.query.with_error_handling do
270
+ owner.load_application_object(self, loads, coerced_value, context)
271
+ end
270
272
  end
271
273
  end
272
274
 
@@ -42,6 +42,7 @@ module GraphQL
42
42
  value.after_value ||= original_arguments[:after]
43
43
  value.last_value ||= original_arguments[:last]
44
44
  value.before_value ||= original_arguments[:before]
45
+ value.arguments ||= original_arguments
45
46
  value.field ||= field
46
47
  if field.has_max_page_size? && !value.has_max_page_size_override?
47
48
  value.max_page_size = field.max_page_size
@@ -226,8 +226,8 @@ module GraphQL
226
226
  # It's funny to think of a _result_ of an input object.
227
227
  # This is used for rendering the default value in introspection responses.
228
228
  def coerce_result(value, ctx)
229
- # Allow the application to provide values as :symbols, and convert them to the strings
230
- value = value.reduce({}) { |memo, (k, v)| memo[k.to_s] = v; memo }
229
+ # Allow the application to provide values as :snake_symbols, and convert them to the camelStrings
230
+ value = value.reduce({}) { |memo, (k, v)| memo[Member::BuildType.camelize(k.to_s)] = v; memo }
231
231
 
232
232
  result = {}
233
233