graphql 1.7.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
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