graphql 1.7.0 → 1.7.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 143f5c1d1fd648361bc54f779e9e160d5953a439
4
- data.tar.gz: a17369262755499d3adfa17be3465ac4d68e6c97
3
+ metadata.gz: d524553e65b5b66f89705c436377523f7d4ac976
4
+ data.tar.gz: 8cb44e1162bc0b1aa1be8fb8c78665f5bd8f20e9
5
5
  SHA512:
6
- metadata.gz: d9d383e0fcb02bd527c830c157ce0e6b97a8f3b552f75d6e6be28f51f82296168bfea5e81f4e06463d706d2dc264095aa38b048d35e3afc432379b5482d6afd7
7
- data.tar.gz: a22957f865c6145c04af2f68b1810f799caa873a60cc3788607505c63be52fbaa9f393a9bf2809a85e64e5625efbe83ceeab2642f7c29ef4d952c1391c9ac3e7
6
+ metadata.gz: 8eb7ed959efa258714dab38486844d32c75a17a4040c89db3478c2b151e4222982ecfcc41a48b82b0a08df1b54aa871a1567bfcdd043ede9abba1b69b31fe3c8
7
+ data.tar.gz: 9f753512aec476ebeab407d159c80b75d94aedfb74d8d266910e73c7a88b1934f53bf1f643498eb8bb32798b500c3cfb086a6d9e7f928d993b04a650b5fde0d9
@@ -10,7 +10,7 @@ module GraphQL
10
10
  include GraphQL::Define::InstanceDefinable
11
11
  accepts_definitions :locations, :name, :description, :arguments, :default_directive, argument: GraphQL::Define::AssignArgument
12
12
 
13
- attr_accessor :locations, :arguments, :name, :description
13
+ attr_accessor :locations, :arguments, :name, :description, :arguments_class
14
14
  # @api private
15
15
  attr_writer :default_directive
16
16
  ensure_defined(:locations, :arguments, :name, :description, :default_directive?)
@@ -179,6 +179,8 @@ module GraphQL
179
179
  # @return [Object, GraphQL::Function] The function used to derive this field
180
180
  attr_accessor :function
181
181
 
182
+ attr_accessor :arguments_class
183
+
182
184
  attr_writer :connection
183
185
 
184
186
  # @return [nil, String] Prefix for subscription names from this field
@@ -30,7 +30,7 @@ module GraphQL
30
30
  argument: GraphQL::Define::AssignArgument
31
31
  )
32
32
 
33
- attr_accessor :mutation, :arguments
33
+ attr_accessor :mutation, :arguments, :arguments_class
34
34
  ensure_defined(:mutation, :arguments, :input_fields)
35
35
  alias :input_fields :arguments
36
36
 
@@ -96,7 +96,7 @@ module GraphQL
96
96
  end
97
97
  end
98
98
 
99
- GraphQL::Query::Arguments.new(input_values, argument_definitions: arguments)
99
+ arguments_class.instantiate_arguments(input_values)
100
100
  end
101
101
 
102
102
  # @api private
@@ -1,12 +1,59 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  class Query
4
+ class StaticArguments
5
+ attr_reader :argument_definitions
6
+
7
+ def initialize(argument_definitions:)
8
+ @argument_definitions = argument_definitions
9
+ end
10
+
11
+ def instantiate_arguments(values)
12
+ arg_class.new(values, argument_definitions: argument_definitions)
13
+ end
14
+
15
+ private
16
+
17
+ def arg_class
18
+ @arg_class ||= begin
19
+ klass = Class.new(GraphQL::Query::Arguments).instance_exec(self) do |static_arguments|
20
+ static_arguments.argument_definitions.each do |_arg_name, arg_definition|
21
+ expose_as = arg_definition.expose_as.to_s
22
+
23
+ # Don't define a helper method if it would override something.
24
+ if instance_methods.include?(expose_as)
25
+ warn(
26
+ "Unable to define a helper for argument with name '#{expose_as}' "\
27
+ "as this is a reserved name. If you're using an argument such as "\
28
+ "`argument #{expose_as}`, consider renaming this argument.`"
29
+ )
30
+ next
31
+ end
32
+
33
+ define_method(expose_as) do
34
+ self[expose_as]
35
+ end
36
+ end
37
+
38
+ self
39
+ end
40
+
41
+ klass
42
+ end
43
+ end
44
+ end
45
+
4
46
  # Read-only access to values, normalizing all keys to strings
5
47
  #
6
48
  # {Arguments} recursively wraps the input in {Arguments} instances.
7
49
  class Arguments
8
50
  extend GraphQL::Delegate
9
51
 
52
+ def self.construct_arguments_class(argument_owner)
53
+ arguments_class = GraphQL::Query::StaticArguments.new(argument_definitions: argument_owner.arguments)
54
+ argument_owner.arguments_class = arguments_class
55
+ end
56
+
10
57
  def initialize(values, argument_definitions:)
11
58
  @argument_values = values.inject({}) do |memo, (inner_key, inner_value)|
12
59
  arg_defn = argument_definitions[inner_key.to_s]
@@ -8,13 +8,12 @@ module GraphQL
8
8
  Hash.new do |h1, irep_or_ast_node|
9
9
  h1[irep_or_ast_node] = Hash.new do |h2, definition|
10
10
  ast_node = irep_or_ast_node.is_a?(GraphQL::InternalRepresentation::Node) ? irep_or_ast_node.ast_node : irep_or_ast_node
11
- ast_arguments = ast_node.arguments
12
11
  h2[definition] = if definition.arguments.none?
13
12
  GraphQL::Query::Arguments::NO_ARGS
14
13
  else
15
14
  GraphQL::Query::LiteralInput.from_arguments(
16
- ast_arguments,
17
- definition.arguments,
15
+ ast_node.arguments,
16
+ definition,
18
17
  query.variables,
19
18
  )
20
19
  end
@@ -41,21 +41,12 @@ module GraphQL
41
41
  when GraphQL::InputObjectType
42
42
  # TODO smell: handling AST vs handling plain Ruby
43
43
  next_args = ast_node.is_a?(Hash) ? ast_node : ast_node.arguments
44
- from_arguments(next_args, type.arguments, variables)
44
+ from_arguments(next_args, type, variables)
45
45
  end
46
46
  end
47
47
  end
48
48
 
49
- def self.defaults_for(argument_defns)
50
- if argument_defns.values.none?(&:default_value?)
51
- GraphQL::Query::Arguments::NO_ARGS
52
- else
53
- from_arguments([], argument_defns, nil)
54
- end
55
- end
56
-
57
- def self.from_arguments(ast_arguments, argument_defns, variables)
58
- # Variables is nil when making .defaults_for
49
+ def self.from_arguments(ast_arguments, argument_owner, variables)
59
50
  context = variables ? variables.context : nil
60
51
  values_hash = {}
61
52
  indexed_arguments = case ast_arguments
@@ -67,6 +58,7 @@ module GraphQL
67
58
  raise ArgumentError, "Unexpected ast_arguments: #{ast_arguments}"
68
59
  end
69
60
 
61
+ argument_defns = argument_owner.arguments
70
62
  argument_defns.each do |arg_name, arg_defn|
71
63
  ast_arg = indexed_arguments[arg_name]
72
64
  # First, check the argument in the AST.
@@ -113,7 +105,7 @@ module GraphQL
113
105
  end
114
106
  end
115
107
 
116
- GraphQL::Query::Arguments.new(values_hash, argument_definitions: argument_defns)
108
+ argument_owner.arguments_class.instantiate_arguments(values_hash)
117
109
  end
118
110
  end
119
111
  end
@@ -7,6 +7,30 @@ module GraphQL
7
7
  encode(idx.to_s)
8
8
  end
9
9
 
10
+ def has_next_page
11
+ if first
12
+ # There are more items after these items
13
+ sliced_nodes.count > first
14
+ elsif GraphQL::Relay::ConnectionType.bidirectional_pagination && before
15
+ # The original array is longer than the `before` index
16
+ index_from_cursor(before) < nodes.length
17
+ else
18
+ false
19
+ end
20
+ end
21
+
22
+ def has_previous_page
23
+ if last
24
+ # There are items preceding the ones in this result
25
+ sliced_nodes.count > last
26
+ elsif GraphQL::Relay::ConnectionType.bidirectional_pagination && after
27
+ # We've paginated into the Array a bit, there are some behind us
28
+ index_from_cursor(after) > 0
29
+ else
30
+ false
31
+ end
32
+ end
33
+
10
34
  private
11
35
 
12
36
  def first
@@ -3,10 +3,14 @@ module GraphQL
3
3
  module Relay
4
4
  module ConnectionType
5
5
  class << self
6
+ # @return [Boolean] If true, connection types get a `nodes` shortcut field
6
7
  attr_accessor :default_nodes_field
8
+ # @return [Boolean] If true, connections check for reverse-direction `has*Page` values
9
+ attr_accessor :bidirectional_pagination
7
10
  end
8
11
 
9
12
  self.default_nodes_field = false
13
+ self.bidirectional_pagination = false
10
14
 
11
15
  # Create a connection which exposes edges of this type
12
16
  def self.create_type(wrapped_type, edge_type: wrapped_type.edge_type, edge_class: GraphQL::Relay::Edge, nodes_field: ConnectionType.default_nodes_field, &block)
@@ -24,11 +24,25 @@ module GraphQL
24
24
  end
25
25
 
26
26
  def has_next_page
27
- !!(first && paged_nodes_length >= first && sliced_nodes_count > first)
27
+ if first
28
+ paged_nodes_length >= first && sliced_nodes_count > first
29
+ elsif GraphQL::Relay::ConnectionType.bidirectional_pagination && last
30
+ sliced_nodes_count > last
31
+ else
32
+ false
33
+ end
28
34
  end
29
35
 
30
36
  def has_previous_page
31
- !!(last && paged_nodes_length >= last && sliced_nodes_count > last)
37
+ if last
38
+ paged_nodes_length >= last && sliced_nodes_count > last
39
+ elsif GraphQL::Relay::ConnectionType.bidirectional_pagination && after
40
+ # We've already paginated through the collection a bit,
41
+ # there are nodes behind us
42
+ offset_from_cursor(after) > 0
43
+ else
44
+ false
45
+ end
32
46
  end
33
47
 
34
48
  def first
@@ -42,6 +42,11 @@ module GraphQL
42
42
  visit_roots = [member.query, member.mutation, member.subscription]
43
43
  if @introspection
44
44
  visit_roots << GraphQL::Introspection::SchemaType
45
+ if member.query
46
+ # Visit this so that arguments class is preconstructed
47
+ # Skip validation since it begins with __
48
+ visit_field_on_type(member.query, GraphQL::Introspection::TypeByNameField, dynamic_field: true)
49
+ end
45
50
  end
46
51
  visit_roots.concat(member.orphan_types)
47
52
  visit_roots.compact!
@@ -51,6 +56,9 @@ module GraphQL
51
56
  @type_reference_map[argument.type.unwrap.to_s] << argument
52
57
  visit(argument.type, "Directive argument #{member.name}.#{name}")
53
58
  end
59
+ # Construct arguments class here, which is later used to generate GraphQL::Query::Arguments
60
+ # to be passed to a resolver proc
61
+ GraphQL::Query::Arguments.construct_arguments_class(member)
54
62
  when GraphQL::BaseType
55
63
  type_defn = member.unwrap
56
64
  prev_type = @type_map[type_defn.name]
@@ -74,6 +82,10 @@ module GraphQL
74
82
  @type_reference_map[arg.type.unwrap.to_s] << arg
75
83
  visit(arg.type, "Input field #{type_defn.name}.#{name}")
76
84
  end
85
+
86
+ # Construct arguments class here, which is later used to generate GraphQL::Query::Arguments
87
+ # to be passed to a resolver proc
88
+ GraphQL::Query::Arguments.construct_arguments_class(type_defn)
77
89
  end
78
90
  elsif !prev_type.equal?(type_defn)
79
91
  # If the previous entry in the map isn't the same object we just found, raise.
@@ -87,17 +99,28 @@ module GraphQL
87
99
 
88
100
  def visit_fields(type_defn)
89
101
  type_defn.all_fields.each do |field_defn|
90
- instrumented_field_defn = @field_instrumenters.reduce(field_defn) do |defn, inst|
91
- inst.instrument(type_defn, defn)
92
- end
102
+ visit_field_on_type(type_defn, field_defn)
103
+ end
104
+ end
105
+
106
+ def visit_field_on_type(type_defn, field_defn, dynamic_field: false)
107
+ instrumented_field_defn = @field_instrumenters.reduce(field_defn) do |defn, inst|
108
+ inst.instrument(type_defn, defn)
109
+ end
110
+ if !dynamic_field
93
111
  @instrumented_field_map[type_defn.name][instrumented_field_defn.name] = instrumented_field_defn
94
- @type_reference_map[instrumented_field_defn.type.unwrap.name] << instrumented_field_defn
95
- visit(instrumented_field_defn.type, "Field #{type_defn.name}.#{instrumented_field_defn.name}'s return type")
96
- instrumented_field_defn.arguments.each do |name, arg|
97
- @type_reference_map[arg.type.unwrap.to_s] << arg
98
- visit(arg.type, "Argument #{name} on #{type_defn.name}.#{instrumented_field_defn.name}")
99
- end
100
112
  end
113
+ @type_reference_map[instrumented_field_defn.type.unwrap.name] << instrumented_field_defn
114
+ visit(instrumented_field_defn.type, "Field #{type_defn.name}.#{instrumented_field_defn.name}'s return type")
115
+
116
+ instrumented_field_defn.arguments.each do |name, arg|
117
+ @type_reference_map[arg.type.unwrap.to_s] << arg
118
+ visit(arg.type, "Argument #{name} on #{type_defn.name}.#{instrumented_field_defn.name}")
119
+ end
120
+
121
+ # Construct arguments class here, which is later used to generate GraphQL::Query::Arguments
122
+ # to be passed to a resolver proc
123
+ GraphQL::Query::Arguments.construct_arguments_class(instrumented_field_defn)
101
124
  end
102
125
 
103
126
  def validate_type(member, context_description)
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # test_via: ../subscriptions.rb
2
3
  module GraphQL
3
4
  class Subscriptions
4
5
  # This thing can be:
@@ -37,7 +38,7 @@ module GraphQL
37
38
  when Hash
38
39
  GraphQL::Query::LiteralInput.from_arguments(
39
40
  arguments,
40
- field.arguments,
41
+ field,
41
42
  nil,
42
43
  )
43
44
  else
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.7.0"
3
+ VERSION = "1.7.1"
4
4
  end
@@ -182,7 +182,7 @@ describe GraphQL::InputObjectType do
182
182
 
183
183
  it "has problem with correct path" do
184
184
  paths = result.problems.map { |p| p["path"] }
185
- assert_equal(paths, [["isDelicious"]])
185
+ assert_equal([["isDelicious"]], paths)
186
186
  end
187
187
 
188
188
  it "has correct problem explanation" do
@@ -37,10 +37,14 @@ describe GraphQL::ListType do
37
37
 
38
38
  describe "list of input objects" do
39
39
  let(:input_object) do
40
- GraphQL::InputObjectType.define do
40
+ input_object = GraphQL::InputObjectType.define do
41
41
  name "SomeInputObjectType"
42
42
  argument :float, !types.Float
43
43
  end
44
+
45
+ GraphQL::Query::Arguments.construct_arguments_class(input_object)
46
+
47
+ input_object
44
48
  end
45
49
 
46
50
  let(:input_object_list) { input_object.to_list_type }
@@ -291,4 +291,23 @@ describe GraphQL::Query::Arguments do
291
291
  assert_equal 30, args['inputObject']['a']
292
292
  end
293
293
  end
294
+
295
+ describe "construct_arguments_class" do
296
+ let(:input_object) do
297
+ GraphQL::InputObjectType.define do
298
+ argument :foo, types.Int
299
+ argument :bar, types.Int
300
+ end
301
+ end
302
+
303
+ it "generates argument classes that responds to keys as functions" do
304
+ assert_equal nil, input_object.arguments_class
305
+
306
+ GraphQL::Query::Arguments.construct_arguments_class(input_object)
307
+ args = input_object.arguments_class.instantiate_arguments({foo: 3, bar: -90})
308
+
309
+ assert_equal 3, args.foo
310
+ assert_equal -90, args.bar
311
+ end
312
+ end
294
313
  end
@@ -11,6 +11,10 @@ describe GraphQL::Relay::ArrayConnection do
11
11
  result["data"]["rebels"]["ships"]["edges"].last["cursor"]
12
12
  end
13
13
 
14
+ def get_page_info(result, key = "bases")
15
+ result["data"]["rebels"][key]["pageInfo"]
16
+ end
17
+
14
18
  describe "results" do
15
19
  let(:query_string) {%|
16
20
  query getShips($first: Int, $after: String, $last: Int, $before: String, $nameIncludes: String){
@@ -61,6 +65,36 @@ describe GraphQL::Relay::ArrayConnection do
61
65
  assert_equal("NQ==", result["data"]["rebels"]["ships"]["pageInfo"]["endCursor"])
62
66
  end
63
67
 
68
+ it "provides bidirectional_pagination" do
69
+ result = star_wars_query(query_string, "first" => 1)
70
+ last_cursor = get_last_cursor(result)
71
+
72
+ # When going forwards, bi-directional pagination
73
+ # returns `true` even for `hasPreviousPage`
74
+ result = star_wars_query(query_string, "first" => 1, "after" => last_cursor)
75
+ assert_equal(true, get_page_info(result, "ships")["hasNextPage"])
76
+ assert_equal(false, get_page_info(result, "ships")["hasPreviousPage"])
77
+
78
+ result = with_bidirectional_pagination {
79
+ star_wars_query(query_string, "first" => 3, "after" => last_cursor)
80
+ }
81
+ assert_equal(true, get_page_info(result, "ships")["hasNextPage"])
82
+ assert_equal(true, get_page_info(result, "ships")["hasPreviousPage"])
83
+
84
+ # When going backwards, bi-directional pagination
85
+ # returns true for `hasNextPage`
86
+ last_cursor = get_last_cursor(result)
87
+ result = star_wars_query(query_string, "last" => 1, "before" => last_cursor)
88
+ assert_equal(false, get_page_info(result, "ships")["hasNextPage"])
89
+ assert_equal(true, get_page_info(result, "ships")["hasPreviousPage"])
90
+
91
+ result = with_bidirectional_pagination {
92
+ star_wars_query(query_string, "last" => 2, "before" => last_cursor)
93
+ }
94
+ assert_equal(true, get_page_info(result, "ships")["hasNextPage"])
95
+ assert_equal(true, get_page_info(result, "ships")["hasPreviousPage"])
96
+ end
97
+
64
98
  it 'slices the result' do
65
99
  result = star_wars_query(query_string, "first" => 1)
66
100
  assert_equal(["X-Wing"], get_names(result))
@@ -142,10 +176,6 @@ describe GraphQL::Relay::ArrayConnection do
142
176
  result["data"]["rebels"]["bases"]["edges"].map { |e| e["node"]["name"] }
143
177
  end
144
178
 
145
- def get_page_info(result)
146
- result["data"]["rebels"]["bases"]["pageInfo"]
147
- end
148
-
149
179
  let(:query_string) {%|
150
180
  query getShips($first: Int, $after: String, $last: Int, $before: String){
151
181
  rebels {
@@ -74,6 +74,35 @@ describe GraphQL::Relay::RelationConnection do
74
74
  )
75
75
  end
76
76
 
77
+ it "provides bidirectional_pagination" do
78
+ result = star_wars_query(query_string, "first" => 1)
79
+ last_cursor = get_last_cursor(result)
80
+
81
+ result = star_wars_query(query_string, "first" => 1, "after" => last_cursor)
82
+ assert_equal true, get_page_info(result)["hasNextPage"]
83
+ assert_equal false, get_page_info(result)["hasPreviousPage"]
84
+
85
+ result = with_bidirectional_pagination {
86
+ star_wars_query(query_string, "first" => 1, "after" => last_cursor)
87
+ }
88
+ assert_equal true, get_page_info(result)["hasNextPage"]
89
+ assert_equal true, get_page_info(result)["hasPreviousPage"]
90
+
91
+ result = star_wars_query(query_string, "first" => 100)
92
+ last_cursor = get_last_cursor(result)
93
+
94
+ result = star_wars_query(query_string, "last" => 1, "before" => last_cursor)
95
+ assert_equal false, get_page_info(result)["hasNextPage"]
96
+ assert_equal true, get_page_info(result)["hasPreviousPage"]
97
+
98
+ result = with_bidirectional_pagination {
99
+ star_wars_query(query_string, "last" => 1, "before" => last_cursor)
100
+ }
101
+ assert_equal true, get_page_info(result)["hasNextPage"]
102
+ assert_equal true, get_page_info(result)["hasPreviousPage"]
103
+
104
+ end
105
+
77
106
  it 'slices the result' do
78
107
  result = star_wars_query(query_string, "first" => 2)
79
108
  assert_equal(["Death Star", "Shield Generator"], get_names(result))
@@ -68,6 +68,14 @@ def star_wars_query(string, variables={}, context: {})
68
68
  GraphQL::Query.new(StarWars::Schema, string, variables: variables, context: context).result
69
69
  end
70
70
 
71
+ def with_bidirectional_pagination
72
+ prev_value = GraphQL::Relay::ConnectionType.bidirectional_pagination
73
+ GraphQL::Relay::ConnectionType.bidirectional_pagination = true
74
+ yield
75
+ ensure
76
+ GraphQL::Relay::ConnectionType.bidirectional_pagination = prev_value
77
+ end
78
+
71
79
  module TestTracing
72
80
  class << self
73
81
  def clear
@@ -109,7 +109,7 @@ module Dummy
109
109
  field :flavors, types[types.String], "Chocolate, Strawberry, etc" do
110
110
  argument :limit, types.Int
111
111
  resolve ->(milk, args, ctx) {
112
- args[:limit] ? milk.flavors.first(args[:limit]) : milk.flavors
112
+ args[:limit] ? milk.flavors.first(args.limit) : milk.flavors
113
113
  }
114
114
  end
115
115
  field :executionError do
@@ -234,7 +234,7 @@ module Dummy
234
234
  name "DeepNonNull"
235
235
  field :nonNullInt, !types.Int do
236
236
  argument :returning, types.Int
237
- resolve ->(obj, args, ctx) { args[:returning] }
237
+ resolve ->(obj, args, ctx) { args.returning }
238
238
  end
239
239
 
240
240
  field :deepNonNull, DeepNonNullType.to_non_null_type do
@@ -328,7 +328,7 @@ module Dummy
328
328
  argument :executionErrorAtIndex, types.Int
329
329
  resolve ->(obj, args, ctx) {
330
330
  result = CHEESES.values + MILKS.values
331
- result[args[:executionErrorAtIndex]] = GraphQL::ExecutionError.new("missing dairy") if args[:executionErrorAtIndex]
331
+ result[args.executionErrorAtIndex] = GraphQL::ExecutionError.new("missing dairy") if args.executionErrorAtIndex
332
332
  result
333
333
  }
334
334
  end
@@ -384,7 +384,7 @@ module Dummy
384
384
  description("Push a value onto a global array :D")
385
385
  argument :value, !types.Int, as: :val
386
386
  resolve ->(o, args, ctx) {
387
- GLOBAL_VALUES << args[:val]
387
+ GLOBAL_VALUES << args.val
388
388
  GLOBAL_VALUES
389
389
  }
390
390
  end
@@ -394,7 +394,7 @@ module Dummy
394
394
  argument :input, !ReplaceValuesInputType
395
395
  resolve ->(o, args, ctx) {
396
396
  GLOBAL_VALUES.clear
397
- GLOBAL_VALUES.push(*args[:input][:values])
397
+ GLOBAL_VALUES.push(*args.input[:values])
398
398
  GLOBAL_VALUES
399
399
  }
400
400
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo