graphql 1.6.7 → 1.6.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/graphql/templates/mutation_type.erb +7 -1
- data/lib/graphql.rb +18 -12
- data/lib/graphql/enum_type.rb +15 -0
- data/lib/graphql/field.rb +1 -0
- data/lib/graphql/non_null_type.rb +10 -0
- data/lib/graphql/query.rb +5 -1
- data/lib/graphql/relay/relation_connection.rb +24 -6
- data/lib/graphql/schema/build_from_definition.rb +12 -0
- data/lib/graphql/schema/traversal.rb +5 -0
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +7 -1
- data/lib/graphql/unresolved_type_error.rb +4 -1
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/enum_type_spec.rb +15 -0
- data/spec/graphql/object_type_spec.rb +13 -0
- data/spec/graphql/query/serial_execution/value_resolution_spec.rb +9 -3
- data/spec/graphql/query_spec.rb +51 -3
- data/spec/graphql/relay/relation_connection_spec.rb +82 -1
- data/spec/graphql/schema/build_from_definition_spec.rb +79 -0
- data/spec/graphql/schema/traversal_spec.rb +13 -1
- data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +5 -5
- data/spec/rails_dependency_sanity_spec.rb +14 -0
- data/spec/spec_helper.rb +24 -8
- data/spec/support/star_wars/data.rb +1 -1
- data/spec/support/star_wars/schema.rb +4 -0
- metadata +4 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '056695dd78098e5adfe501785c8e7e111cb8bc2d'
|
4
|
+
data.tar.gz: 5ef5aa896020c5b149c1960a5e870d8f13e4a2f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50d4e93b57572c9a3cce63468b0e6b23daca8f61a97120e5899613018c1bdfc4fe9ecade9b8906e4243c383a55f636b37d066e52d14310c002911e7e7c3c086f
|
7
|
+
data.tar.gz: 63d326d4a3ea184e7840308d6841afb7af3336d570f9d8362ffdad5d0e351016e0d67c72bf9ec62e3629a5ce768bf4fe1b92bc0873b7effa47b37aa3f370f7f9
|
@@ -1,5 +1,11 @@
|
|
1
1
|
Types::MutationType = GraphQL::ObjectType.define do
|
2
2
|
name "Mutation"
|
3
3
|
|
4
|
-
# TODO:
|
4
|
+
# TODO: Remove me
|
5
|
+
field :testField, types.String do
|
6
|
+
description "An example field added by the generator"
|
7
|
+
resolve ->(obj, args, ctx) {
|
8
|
+
"Hello World!"
|
9
|
+
}
|
10
|
+
end
|
5
11
|
end
|
data/lib/graphql.rb
CHANGED
@@ -5,22 +5,28 @@ require "set"
|
|
5
5
|
require "singleton"
|
6
6
|
|
7
7
|
module GraphQL
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
8
|
+
if RUBY_VERSION == "2.4.0"
|
9
|
+
# Ruby stdlib was pretty busted until this fix:
|
10
|
+
# https://bugs.ruby-lang.org/issues/13111
|
11
|
+
# https://github.com/ruby/ruby/commit/46c0e79bb5b96c45c166ef62f8e585f528862abb#diff-43adf0e587a50dbaf51764a262008d40
|
12
|
+
module Delegate
|
13
|
+
def def_delegators(accessor, *method_names)
|
14
|
+
method_names.each do |method_name|
|
15
|
+
class_eval <<-RUBY
|
16
|
+
def #{method_name}(*args)
|
17
|
+
if block_given?
|
18
|
+
#{accessor}.#{method_name}(*args, &Proc.new)
|
19
|
+
else
|
20
|
+
#{accessor}.#{method_name}(*args)
|
21
|
+
end
|
19
22
|
end
|
23
|
+
RUBY
|
20
24
|
end
|
21
|
-
RUBY
|
22
25
|
end
|
23
26
|
end
|
27
|
+
else
|
28
|
+
require "forwardable"
|
29
|
+
Delegate = Forwardable
|
24
30
|
end
|
25
31
|
|
26
32
|
class Error < StandardError
|
data/lib/graphql/enum_type.rb
CHANGED
@@ -138,11 +138,26 @@ module GraphQL
|
|
138
138
|
accepts_definitions(*ATTRIBUTES)
|
139
139
|
attr_accessor(*ATTRIBUTES)
|
140
140
|
ensure_defined(*ATTRIBUTES)
|
141
|
+
|
142
|
+
def name=(new_name)
|
143
|
+
# Validate that the name is correct
|
144
|
+
unless new_name =~ /^[_a-zA-Z][_a-zA-Z0-9]*$/
|
145
|
+
raise(
|
146
|
+
GraphQL::EnumType::InvalidEnumNameError,
|
147
|
+
"Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but '#{new_name}' does not"
|
148
|
+
)
|
149
|
+
end
|
150
|
+
|
151
|
+
@name = new_name
|
152
|
+
end
|
141
153
|
end
|
142
154
|
|
143
155
|
class UnresolvedValueError < GraphQL::Error
|
144
156
|
end
|
145
157
|
|
158
|
+
class InvalidEnumNameError < GraphQL::Error
|
159
|
+
end
|
160
|
+
|
146
161
|
private
|
147
162
|
|
148
163
|
# Get the underlying value for this enum value
|
data/lib/graphql/field.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module GraphQL
|
3
|
+
class DoubleNonNullTypeError < GraphQL::Error
|
4
|
+
end
|
5
|
+
|
3
6
|
# A non-null type modifies another type.
|
4
7
|
#
|
5
8
|
# Non-null types can be created with `!` (`InnerType!`)
|
@@ -34,6 +37,13 @@ module GraphQL
|
|
34
37
|
|
35
38
|
attr_reader :of_type
|
36
39
|
def initialize(of_type:)
|
40
|
+
if of_type.is_a?(GraphQL::NonNullType)
|
41
|
+
raise(
|
42
|
+
DoubleNonNullTypeError,
|
43
|
+
"You tried to add a non-null constraint twice (!! instead of !)"
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
37
47
|
super()
|
38
48
|
@of_type = of_type
|
39
49
|
end
|
data/lib/graphql/query.rb
CHANGED
@@ -44,6 +44,7 @@ module GraphQL
|
|
44
44
|
|
45
45
|
# @return [String, nil] The name of the operation to run (may be inferred)
|
46
46
|
def selected_operation_name
|
47
|
+
return nil unless selected_operation
|
47
48
|
selected_operation.name
|
48
49
|
end
|
49
50
|
|
@@ -161,7 +162,10 @@ module GraphQL
|
|
161
162
|
end
|
162
163
|
|
163
164
|
def irep_selection
|
164
|
-
@selection ||=
|
165
|
+
@selection ||= begin
|
166
|
+
return nil unless selected_operation
|
167
|
+
internal_representation.operation_definitions[selected_operation.name]
|
168
|
+
end
|
165
169
|
end
|
166
170
|
|
167
171
|
# Node-level cache for calculating arguments. Used during execution and query analysis.
|
@@ -24,11 +24,11 @@ module GraphQL
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def has_next_page
|
27
|
-
!!(first && sliced_nodes_count > first)
|
27
|
+
!!(first && paged_nodes_length >= first && sliced_nodes_count > first)
|
28
28
|
end
|
29
29
|
|
30
30
|
def has_previous_page
|
31
|
-
!!(last && sliced_nodes_count > last)
|
31
|
+
!!(last && paged_nodes_length >= last && sliced_nodes_count > last)
|
32
32
|
end
|
33
33
|
|
34
34
|
def first
|
@@ -68,7 +68,8 @@ module GraphQL
|
|
68
68
|
items = items.offset(offset).limit(last)
|
69
69
|
end
|
70
70
|
else
|
71
|
-
|
71
|
+
slice_count = relation_count(items)
|
72
|
+
offset = (relation_offset(items) || 0) + slice_count - [last, slice_count].min
|
72
73
|
items = items.offset(offset).limit(last)
|
73
74
|
end
|
74
75
|
end
|
@@ -121,17 +122,26 @@ module GraphQL
|
|
121
122
|
|
122
123
|
if before && after
|
123
124
|
if offset_from_cursor(after) < offset_from_cursor(before)
|
124
|
-
@sliced_nodes = @sliced_nodes
|
125
|
+
@sliced_nodes = limit_nodes(@sliced_nodes, offset_from_cursor(before) - offset_from_cursor(after) - 1)
|
125
126
|
else
|
126
|
-
@sliced_nodes = @sliced_nodes
|
127
|
+
@sliced_nodes = limit_nodes(@sliced_nodes, 0)
|
127
128
|
end
|
129
|
+
|
128
130
|
elsif before
|
129
|
-
@sliced_nodes = @sliced_nodes
|
131
|
+
@sliced_nodes = limit_nodes(@sliced_nodes, offset_from_cursor(before) - 1)
|
130
132
|
end
|
131
133
|
|
132
134
|
@sliced_nodes
|
133
135
|
end
|
134
136
|
|
137
|
+
def limit_nodes(sliced_nodes, limit)
|
138
|
+
if limit > 0 || defined?(ActiveRecord::Relation) && sliced_nodes.is_a?(ActiveRecord::Relation)
|
139
|
+
sliced_nodes.limit(limit)
|
140
|
+
else
|
141
|
+
sliced_nodes.where(false)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
135
145
|
def sliced_nodes_count
|
136
146
|
return @sliced_nodes_count if defined? @sliced_nodes_count
|
137
147
|
|
@@ -147,6 +157,14 @@ module GraphQL
|
|
147
157
|
return @paged_nodes_array if defined?(@paged_nodes_array)
|
148
158
|
@paged_nodes_array = paged_nodes.to_a
|
149
159
|
end
|
160
|
+
|
161
|
+
def paged_nodes_length
|
162
|
+
if paged_nodes.respond_to?(:length)
|
163
|
+
paged_nodes.length
|
164
|
+
else
|
165
|
+
paged_nodes_array.length
|
166
|
+
end
|
167
|
+
end
|
150
168
|
end
|
151
169
|
|
152
170
|
if defined?(ActiveRecord::Relation)
|
@@ -280,6 +280,9 @@ module GraphQL
|
|
280
280
|
resolve: ->(obj, args, ctx) { default_resolve.call(field, obj, args, ctx) },
|
281
281
|
deprecation_reason: build_deprecation_reason(field_definition.directives),
|
282
282
|
)
|
283
|
+
|
284
|
+
type_name = resolve_type_name(field_definition.type)
|
285
|
+
field.connection = type_name.end_with?("Connection")
|
283
286
|
[field_definition.name, field]
|
284
287
|
end
|
285
288
|
end
|
@@ -294,6 +297,15 @@ module GraphQL
|
|
294
297
|
end
|
295
298
|
type
|
296
299
|
end
|
300
|
+
|
301
|
+
def resolve_type_name(type)
|
302
|
+
case type
|
303
|
+
when GraphQL::Language::Nodes::TypeName
|
304
|
+
return type.name
|
305
|
+
else
|
306
|
+
resolve_type_name(type.of_type)
|
307
|
+
end
|
308
|
+
end
|
297
309
|
end
|
298
310
|
|
299
311
|
private_constant :Builder
|
@@ -29,6 +29,7 @@ module GraphQL
|
|
29
29
|
def visit(member, context_description)
|
30
30
|
case member
|
31
31
|
when GraphQL::Schema
|
32
|
+
member.directives.each { |name, directive| visit(directive, "Directive #{name}") }
|
32
33
|
# Find the starting points, then visit them
|
33
34
|
visit_roots = [member.query, member.mutation, member.subscription]
|
34
35
|
if @introspection
|
@@ -37,6 +38,10 @@ module GraphQL
|
|
37
38
|
visit_roots.concat(member.orphan_types)
|
38
39
|
visit_roots.compact!
|
39
40
|
visit_roots.each { |t| visit(t, t.name) }
|
41
|
+
when GraphQL::Directive
|
42
|
+
member.arguments.each do |name, argument|
|
43
|
+
visit(argument.type, "Directive argument #{member.name}.#{name}")
|
44
|
+
end
|
40
45
|
when GraphQL::BaseType
|
41
46
|
type_defn = member.unwrap
|
42
47
|
prev_type = @type_map[type_defn.name]
|
@@ -7,7 +7,7 @@ module GraphQL
|
|
7
7
|
if argument_defn.nil?
|
8
8
|
kind_of_node = node_type(parent)
|
9
9
|
error_arg_name = parent_name(parent, defn)
|
10
|
-
context.errors << message("#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'",
|
10
|
+
context.errors << message("#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'", node, context: context)
|
11
11
|
GraphQL::Language::Visitor::SKIP
|
12
12
|
else
|
13
13
|
nil
|
@@ -53,7 +53,13 @@ module GraphQL
|
|
53
53
|
return
|
54
54
|
end
|
55
55
|
if !ast_var.default_value.nil?
|
56
|
-
var_type
|
56
|
+
unless var_type.is_a?(GraphQL::NonNullType)
|
57
|
+
# If the value is required, but the argument is not,
|
58
|
+
# and yet there's a non-nil default, then we impliclty
|
59
|
+
# make the argument also a required type.
|
60
|
+
|
61
|
+
var_type = GraphQL::NonNullType.new(of_type: var_type)
|
62
|
+
end
|
57
63
|
end
|
58
64
|
|
59
65
|
arg_defn = arguments[arg_node.name]
|
@@ -24,7 +24,10 @@ module GraphQL
|
|
24
24
|
@parent_type = parent_type
|
25
25
|
@resolved_type = resolved_type
|
26
26
|
@possible_types = possible_types
|
27
|
-
message =
|
27
|
+
message = "The value from \"#{field.name}\" on \"#{parent_type}\" could not be resolved to \"#{field.type}\". " \
|
28
|
+
"(Received: `#{resolved_type.inspect}`, Expected: [#{possible_types.map(&:inspect).join(", ")}]) " \
|
29
|
+
"Make sure you have defined a `type_from_object` proc on your schema and that value `#{value.inspect}` " \
|
30
|
+
"gets resolved to a valid type."
|
28
31
|
super(message)
|
29
32
|
end
|
30
33
|
end
|
data/lib/graphql/version.rb
CHANGED
@@ -37,6 +37,21 @@ describe GraphQL::EnumType do
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
describe "invalid names" do
|
41
|
+
it "rejects names with a space" do
|
42
|
+
assert_raises(GraphQL::EnumType::InvalidEnumNameError) {
|
43
|
+
InvalidEnumTest = GraphQL::EnumType.define do
|
44
|
+
name "InvalidEnumTest"
|
45
|
+
|
46
|
+
value("SPACE IN VALUE", "Invalid enum because it contains spaces", value: 1)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Force evaluation
|
50
|
+
InvalidEnumTest.name
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
40
55
|
describe "values that are Arrays" do
|
41
56
|
let(:schema) {
|
42
57
|
enum = GraphQL::EnumType.define do
|
@@ -4,6 +4,19 @@ require "spec_helper"
|
|
4
4
|
describe GraphQL::ObjectType do
|
5
5
|
let(:type) { Dummy::CheeseType }
|
6
6
|
|
7
|
+
it "doesn't allow double non-null constraints" do
|
8
|
+
assert_raises(GraphQL::DoubleNonNullTypeError) {
|
9
|
+
DoubleNullObject = GraphQL::ObjectType.define do
|
10
|
+
name "DoubleNull"
|
11
|
+
|
12
|
+
field :id, !!types.Int, "Fails because double !"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Force evaluation
|
16
|
+
DoubleNullObject.name
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
7
20
|
it "has a name" do
|
8
21
|
assert_equal("Cheese", type.name)
|
9
22
|
type.name = "Fromage"
|
@@ -35,7 +35,7 @@ describe GraphQL::Query::SerialExecution::ValueResolution do
|
|
35
35
|
resolve ->(obj, args, ctx) { (args["today"] + 1) % 7 }
|
36
36
|
end
|
37
37
|
field :resolvesToNilInterface, interface do
|
38
|
-
resolve ->(obj, args, ctx) {
|
38
|
+
resolve ->(obj, args, ctx) { 1337 }
|
39
39
|
end
|
40
40
|
field :resolvesToWrongTypeInterface, interface do
|
41
41
|
resolve ->(obj, args, ctx) { :something }
|
@@ -86,7 +86,10 @@ describe GraphQL::Query::SerialExecution::ValueResolution do
|
|
86
86
|
|
87
87
|
it "raises an error" do
|
88
88
|
err = assert_raises(GraphQL::UnresolvedTypeError) { result }
|
89
|
-
expected_message =
|
89
|
+
expected_message = "The value from \"resolvesToNilInterface\" on \"Query\" could not be resolved to \"SomeInterface\". " \
|
90
|
+
"(Received: `nil`, Expected: [SomeObject]) " \
|
91
|
+
"Make sure you have defined a `type_from_object` proc on your schema and that value `1337` " \
|
92
|
+
"gets resolved to a valid type."
|
90
93
|
assert_equal expected_message, err.message
|
91
94
|
end
|
92
95
|
end
|
@@ -100,7 +103,10 @@ describe GraphQL::Query::SerialExecution::ValueResolution do
|
|
100
103
|
|
101
104
|
it "raises an error" do
|
102
105
|
err = assert_raises(GraphQL::UnresolvedTypeError) { result }
|
103
|
-
expected_message =
|
106
|
+
expected_message = "The value from \"resolvesToWrongTypeInterface\" on \"Query\" could not be resolved to \"SomeInterface\". " \
|
107
|
+
"(Received: `OtherObject`, Expected: [SomeObject]) " \
|
108
|
+
"Make sure you have defined a `type_from_object` proc on your schema and that value `:something` " \
|
109
|
+
"gets resolved to a valid type."
|
104
110
|
assert_equal expected_message, err.message
|
105
111
|
end
|
106
112
|
end
|
data/spec/graphql/query_spec.rb
CHANGED
@@ -68,7 +68,7 @@ describe GraphQL::Query do
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
-
describe "operation_name" do
|
71
|
+
describe "#operation_name" do
|
72
72
|
describe "when provided" do
|
73
73
|
let(:query_string) { <<-GRAPHQL
|
74
74
|
query q1 { cheese(id: 1) { flavor } }
|
@@ -79,7 +79,6 @@ describe GraphQL::Query do
|
|
79
79
|
|
80
80
|
it "returns the provided name" do
|
81
81
|
assert_equal "q2", query.operation_name
|
82
|
-
assert_equal "q2", query.selected_operation_name
|
83
82
|
end
|
84
83
|
end
|
85
84
|
|
@@ -91,7 +90,44 @@ describe GraphQL::Query do
|
|
91
90
|
|
92
91
|
it "returns nil" do
|
93
92
|
assert_equal nil, query.operation_name
|
94
|
-
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "#selected_operation_name" do
|
97
|
+
describe "when an operation isprovided" do
|
98
|
+
let(:query_string) { <<-GRAPHQL
|
99
|
+
query q1 { cheese(id: 1) { flavor } }
|
100
|
+
query q2 { cheese(id: 2) { flavor } }
|
101
|
+
GRAPHQL
|
102
|
+
}
|
103
|
+
let(:operation_name) { "q2" }
|
104
|
+
|
105
|
+
it "returns the provided name" do
|
106
|
+
assert_equal "q2", query.selected_operation_name
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "when operation is inferred" do
|
111
|
+
let(:query_string) { <<-GRAPHQL
|
112
|
+
query q3 { cheese(id: 3) { flavor } }
|
113
|
+
GRAPHQL
|
114
|
+
}
|
115
|
+
|
116
|
+
it "returns the inferred operation name" do
|
117
|
+
assert_equal "q3", query.selected_operation_name
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "when there are no operations" do
|
122
|
+
let(:query_string) { <<-GRAPHQL
|
123
|
+
# Only Comments
|
124
|
+
# In this Query
|
125
|
+
GRAPHQL
|
126
|
+
}
|
127
|
+
|
128
|
+
it "returns the inferred operation name" do
|
129
|
+
assert_equal nil, query.selected_operation_name
|
130
|
+
end
|
95
131
|
end
|
96
132
|
end
|
97
133
|
|
@@ -633,6 +669,18 @@ describe GraphQL::Query do
|
|
633
669
|
end
|
634
670
|
end
|
635
671
|
|
672
|
+
describe '#irep_selection' do
|
673
|
+
it "returns the irep for the selected operation" do
|
674
|
+
assert_kind_of GraphQL::InternalRepresentation::Node, query.irep_selection
|
675
|
+
assert_equal 'getFlavor', query.irep_selection.name
|
676
|
+
end
|
677
|
+
|
678
|
+
it "returns nil when there is no selected operation" do
|
679
|
+
query = GraphQL::Query.new(schema, '# Only a comment')
|
680
|
+
assert_equal nil, query.irep_selection
|
681
|
+
end
|
682
|
+
end
|
683
|
+
|
636
684
|
describe "query_execution_strategy" do
|
637
685
|
let(:custom_execution_schema) {
|
638
686
|
schema.redefine do
|
@@ -123,12 +123,19 @@ describe GraphQL::Relay::RelationConnection do
|
|
123
123
|
assert_equal([], get_names(result))
|
124
124
|
end
|
125
125
|
|
126
|
-
it 'handles cursors
|
126
|
+
it 'handles cursors above the bounds of the array' do
|
127
127
|
overreaching_cursor = Base64.strict_encode64("100")
|
128
128
|
result = star_wars_query(query_string, "after" => overreaching_cursor, "first" => 2)
|
129
129
|
assert_equal([], get_names(result))
|
130
130
|
end
|
131
131
|
|
132
|
+
it 'handles cursors below the bounds of the array' do
|
133
|
+
underreaching_cursor = Base64.strict_encode64("1")
|
134
|
+
result = star_wars_query(query_string, "before" => underreaching_cursor, "first" => 2)
|
135
|
+
assert_equal([], get_names(result))
|
136
|
+
end
|
137
|
+
|
138
|
+
|
132
139
|
it 'handles grouped connections with only last argument' do
|
133
140
|
grouped_conn_query = <<-GRAPHQL
|
134
141
|
query {
|
@@ -286,6 +293,68 @@ describe GraphQL::Relay::RelationConnection do
|
|
286
293
|
end
|
287
294
|
end
|
288
295
|
|
296
|
+
describe "applying a max_page_size bigger than the results" do
|
297
|
+
let(:query_string) {%|
|
298
|
+
query getBases($first: Int, $after: String, $last: Int, $before: String){
|
299
|
+
empire {
|
300
|
+
bases: basesWithLargeMaxLimitRelation(first: $first, after: $after, last: $last, before: $before) {
|
301
|
+
... basesConnection
|
302
|
+
}
|
303
|
+
}
|
304
|
+
}
|
305
|
+
|
306
|
+
fragment basesConnection on BaseConnection {
|
307
|
+
edges {
|
308
|
+
cursor
|
309
|
+
node {
|
310
|
+
name
|
311
|
+
}
|
312
|
+
},
|
313
|
+
pageInfo {
|
314
|
+
hasNextPage
|
315
|
+
hasPreviousPage
|
316
|
+
startCursor
|
317
|
+
endCursor
|
318
|
+
}
|
319
|
+
}
|
320
|
+
|}
|
321
|
+
|
322
|
+
it "applies to queries by `first`" do
|
323
|
+
result = star_wars_query(query_string, "first" => 100)
|
324
|
+
assert_equal(6, result["data"]["empire"]["bases"]["edges"].size)
|
325
|
+
assert_equal(false, result["data"]["empire"]["bases"]["pageInfo"]["hasNextPage"])
|
326
|
+
|
327
|
+
# Max page size is applied _without_ `first`, also
|
328
|
+
result = star_wars_query(query_string)
|
329
|
+
assert_equal(6, result["data"]["empire"]["bases"]["edges"].size)
|
330
|
+
assert_equal(false, result["data"]["empire"]["bases"]["pageInfo"]["hasNextPage"], "hasNextPage is false when first is not specified")
|
331
|
+
end
|
332
|
+
|
333
|
+
it "applies to queries by `last`" do
|
334
|
+
all_names = ["Yavin", "Echo Base", "Secret Hideout", "Death Star", "Shield Generator", "Headquarters"]
|
335
|
+
|
336
|
+
last_cursor = "Ng=="
|
337
|
+
result = star_wars_query(query_string, "last" => 100, "before" => last_cursor)
|
338
|
+
assert_equal(all_names[0..4], get_names(result))
|
339
|
+
assert_equal(false, result["data"]["empire"]["bases"]["pageInfo"]["hasPreviousPage"])
|
340
|
+
|
341
|
+
result = star_wars_query(query_string, "last" => 100)
|
342
|
+
assert_equal(all_names, get_names(result))
|
343
|
+
assert_equal(false, result["data"]["empire"]["bases"]["pageInfo"]["hasPreviousPage"])
|
344
|
+
|
345
|
+
result = star_wars_query(query_string, "before" => last_cursor)
|
346
|
+
assert_equal(all_names[0..4], get_names(result))
|
347
|
+
assert_equal(false, result["data"]["empire"]["bases"]["pageInfo"]["hasPreviousPage"], "hasPreviousPage is false when last is not specified")
|
348
|
+
|
349
|
+
fourth_cursor = "NA=="
|
350
|
+
result = star_wars_query(query_string, "last" => 100, "before" => fourth_cursor)
|
351
|
+
assert_equal(all_names[0..2], get_names(result))
|
352
|
+
|
353
|
+
result = star_wars_query(query_string, "before" => fourth_cursor)
|
354
|
+
assert_equal(all_names[0..2], get_names(result))
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
289
358
|
describe "without a block" do
|
290
359
|
let(:query_string) {%|
|
291
360
|
{
|
@@ -441,6 +510,18 @@ describe GraphQL::Relay::RelationConnection do
|
|
441
510
|
|
442
511
|
end
|
443
512
|
|
513
|
+
it 'handles cursors above the bounds of the array' do
|
514
|
+
overreaching_cursor = Base64.strict_encode64("100")
|
515
|
+
result = star_wars_query(query_string, "after" => overreaching_cursor, "first" => 2)
|
516
|
+
assert_equal([], get_names(result))
|
517
|
+
end
|
518
|
+
|
519
|
+
it 'handles cursors below the bounds of the array' do
|
520
|
+
underreaching_cursor = Base64.strict_encode64("1")
|
521
|
+
result = star_wars_query(query_string, "before" => underreaching_cursor, "first" => 2)
|
522
|
+
assert_equal([], get_names(result))
|
523
|
+
end
|
524
|
+
|
444
525
|
it "applies custom arguments" do
|
445
526
|
result = star_wars_query(query_string, "first" => 1, "nameIncludes" => "ea")
|
446
527
|
assert_equal(["Death Star"], get_names(result))
|
@@ -206,6 +206,85 @@ type Hello {
|
|
206
206
|
build_schema_and_compare_output(schema.chop)
|
207
207
|
end
|
208
208
|
|
209
|
+
it 'properly understands connections' do
|
210
|
+
schema = <<-SCHEMA
|
211
|
+
schema {
|
212
|
+
query: Type
|
213
|
+
}
|
214
|
+
|
215
|
+
type Organization {
|
216
|
+
email: String
|
217
|
+
}
|
218
|
+
|
219
|
+
# The connection type for Organization.
|
220
|
+
type OrganizationConnection {
|
221
|
+
# A list of edges.
|
222
|
+
edges: [OrganizationEdge]
|
223
|
+
|
224
|
+
# A list of nodes.
|
225
|
+
nodes: [Organization]
|
226
|
+
|
227
|
+
# Information to aid in pagination.
|
228
|
+
pageInfo: PageInfo!
|
229
|
+
|
230
|
+
# Identifies the total count of items in the connection.
|
231
|
+
totalCount: Int!
|
232
|
+
}
|
233
|
+
|
234
|
+
# An edge in a connection.
|
235
|
+
type OrganizationEdge {
|
236
|
+
# A cursor for use in pagination.
|
237
|
+
cursor: String!
|
238
|
+
|
239
|
+
# The item at the end of the edge.
|
240
|
+
node: Organization
|
241
|
+
}
|
242
|
+
|
243
|
+
# Information about pagination in a connection.
|
244
|
+
type PageInfo {
|
245
|
+
# When paginating forwards, the cursor to continue.
|
246
|
+
endCursor: String
|
247
|
+
|
248
|
+
# When paginating forwards, are there more items?
|
249
|
+
hasNextPage: Boolean!
|
250
|
+
|
251
|
+
# When paginating backwards, are there more items?
|
252
|
+
hasPreviousPage: Boolean!
|
253
|
+
|
254
|
+
# When paginating backwards, the cursor to continue.
|
255
|
+
startCursor: String
|
256
|
+
}
|
257
|
+
|
258
|
+
type Type {
|
259
|
+
name: String
|
260
|
+
organization(
|
261
|
+
# The login of the organization to find.
|
262
|
+
login: String!
|
263
|
+
): Organization
|
264
|
+
|
265
|
+
# A list of organizations the user belongs to.
|
266
|
+
organizations(
|
267
|
+
# Returns the elements in the list that come after the specified global ID.
|
268
|
+
after: String
|
269
|
+
|
270
|
+
# Returns the elements in the list that come before the specified global ID.
|
271
|
+
before: String
|
272
|
+
|
273
|
+
# Returns the first _n_ elements from the list.
|
274
|
+
first: Int
|
275
|
+
|
276
|
+
# Returns the last _n_ elements from the list.
|
277
|
+
last: Int
|
278
|
+
): OrganizationConnection!
|
279
|
+
}
|
280
|
+
SCHEMA
|
281
|
+
|
282
|
+
built_schema = build_schema_and_compare_output(schema.chop)
|
283
|
+
obj = built_schema.types["Type"]
|
284
|
+
refute obj.fields["organization"].connection?
|
285
|
+
assert obj.fields["organizations"].connection?
|
286
|
+
end
|
287
|
+
|
209
288
|
it 'supports simple type with multiple arguments' do
|
210
289
|
schema = <<-SCHEMA
|
211
290
|
schema {
|
@@ -8,8 +8,19 @@ describe GraphQL::Schema::Traversal do
|
|
8
8
|
traversal.type_map
|
9
9
|
end
|
10
10
|
|
11
|
+
it "finds types from directives" do
|
12
|
+
expected = {
|
13
|
+
"Boolean" => GraphQL::BOOLEAN_TYPE, # `skip` argument
|
14
|
+
"String" => GraphQL::STRING_TYPE # `deprecated` argument
|
15
|
+
}
|
16
|
+
result = reduce_types([])
|
17
|
+
assert_equal(expected.keys.sort, result.keys.sort)
|
18
|
+
assert_equal(expected, result.to_h)
|
19
|
+
end
|
20
|
+
|
11
21
|
it "finds types from a single type and its fields" do
|
12
22
|
expected = {
|
23
|
+
"Boolean" => GraphQL::BOOLEAN_TYPE,
|
13
24
|
"Cheese" => Dummy::CheeseType,
|
14
25
|
"Float" => GraphQL::FLOAT_TYPE,
|
15
26
|
"String" => GraphQL::STRING_TYPE,
|
@@ -57,9 +68,10 @@ describe GraphQL::Schema::Traversal do
|
|
57
68
|
|
58
69
|
result = reduce_types([type_parent])
|
59
70
|
expected = {
|
71
|
+
"Boolean" => GraphQL::BOOLEAN_TYPE,
|
72
|
+
"String" => GraphQL::STRING_TYPE,
|
60
73
|
"InputTypeParent" => type_parent,
|
61
74
|
"InputTypeChild" => type_child,
|
62
|
-
"String" => GraphQL::STRING_TYPE
|
63
75
|
}
|
64
76
|
assert_equal(expected, result.to_h)
|
65
77
|
end
|
@@ -24,28 +24,28 @@ describe GraphQL::StaticValidation::ArgumentsAreDefined do
|
|
24
24
|
|
25
25
|
query_root_error = {
|
26
26
|
"message"=>"Field 'cheese' doesn't accept argument 'silly'",
|
27
|
-
"locations"=>[{"line"=>4, "column"=>
|
27
|
+
"locations"=>[{"line"=>4, "column"=>14}],
|
28
28
|
"fields"=>["query getCheese", "cheese", "silly"],
|
29
29
|
}
|
30
30
|
assert_includes(errors, query_root_error)
|
31
31
|
|
32
32
|
input_obj_record = {
|
33
33
|
"message"=>"InputObject 'DairyProductInput' doesn't accept argument 'wacky'",
|
34
|
-
"locations"=>[{"line"=>5, "column"=>
|
34
|
+
"locations"=>[{"line"=>5, "column"=>30}],
|
35
35
|
"fields"=>["query getCheese", "searchDairy", "product", "wacky"],
|
36
36
|
}
|
37
37
|
assert_includes(errors, input_obj_record)
|
38
38
|
|
39
39
|
fragment_error = {
|
40
40
|
"message"=>"Field 'similarCheese' doesn't accept argument 'nonsense'",
|
41
|
-
"locations"=>[{"line"=>9, "column"=>
|
41
|
+
"locations"=>[{"line"=>9, "column"=>36}],
|
42
42
|
"fields"=>["fragment cheeseFields", "similarCheese", "nonsense"],
|
43
43
|
}
|
44
44
|
assert_includes(errors, fragment_error)
|
45
45
|
|
46
46
|
directive_error = {
|
47
47
|
"message"=>"Directive 'skip' doesn't accept argument 'something'",
|
48
|
-
"locations"=>[{"line"=>10, "column"=>
|
48
|
+
"locations"=>[{"line"=>10, "column"=>16}],
|
49
49
|
"fields"=>["fragment cheeseFields", "id", "something"],
|
50
50
|
}
|
51
51
|
assert_includes(errors, directive_error)
|
@@ -61,7 +61,7 @@ describe GraphQL::StaticValidation::ArgumentsAreDefined do
|
|
61
61
|
it "finds undefined arguments" do
|
62
62
|
assert_includes(errors, {
|
63
63
|
"message"=>"Field '__type' doesn't accept argument 'somethingInvalid'",
|
64
|
-
"locations"=>[{"line"=>3, "column"=>
|
64
|
+
"locations"=>[{"line"=>3, "column"=>16}],
|
65
65
|
"fields"=>["query", "__type", "somethingInvalid"],
|
66
66
|
})
|
67
67
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe 'Rails dependency sanity check' do
|
5
|
+
if rails_should_be_installed?
|
6
|
+
it "should have rails installed" do
|
7
|
+
assert defined?(Rails)
|
8
|
+
end
|
9
|
+
else
|
10
|
+
it "should not have rails installed" do
|
11
|
+
refute defined?(Rails)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,13 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
3
|
+
def rails_should_be_installed?
|
4
|
+
ENV['WITHOUT_RAILS'] != 'yes'
|
5
|
+
end
|
2
6
|
require "codeclimate-test-reporter"
|
3
7
|
CodeClimate::TestReporter.start
|
4
|
-
|
5
|
-
|
6
|
-
require "
|
7
|
-
require "
|
8
|
-
require "
|
9
|
-
|
10
|
-
require "
|
8
|
+
|
9
|
+
if rails_should_be_installed?
|
10
|
+
require "rake"
|
11
|
+
require "rails/all"
|
12
|
+
require "rails/generators"
|
13
|
+
|
14
|
+
require "jdbc/sqlite3" if RUBY_ENGINE == 'jruby'
|
15
|
+
require "sqlite3" if RUBY_ENGINE == 'ruby'
|
16
|
+
require "pg" if RUBY_ENGINE == 'ruby'
|
17
|
+
require "sequel"
|
18
|
+
end
|
19
|
+
|
11
20
|
require "graphql"
|
12
21
|
require "graphql/rake_task"
|
13
22
|
require "benchmark"
|
@@ -45,8 +54,15 @@ end
|
|
45
54
|
NO_OP_RESOLVE_TYPE = ->(type, obj, ctx) {
|
46
55
|
raise "this should never be called"
|
47
56
|
}
|
57
|
+
|
48
58
|
# Load support files
|
49
|
-
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each
|
59
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each do |f|
|
60
|
+
unless rails_should_be_installed?
|
61
|
+
next if f.end_with?('star_wars/data.rb')
|
62
|
+
next if f.end_with?('base_generator_test.rb')
|
63
|
+
end
|
64
|
+
require f
|
65
|
+
end
|
50
66
|
|
51
67
|
def star_wars_query(string, variables={}, context: {})
|
52
68
|
GraphQL::Query.new(StarWars::Schema, string, variables: variables, context: context).result
|
@@ -167,6 +167,10 @@ module StarWars
|
|
167
167
|
resolve ->(object, args, context) { Base.all.to_a }
|
168
168
|
end
|
169
169
|
|
170
|
+
connection :basesWithLargeMaxLimitRelation, BaseType.connection_type, max_page_size: 1000 do
|
171
|
+
resolve ->(object, args, context) { Base.all }
|
172
|
+
end
|
173
|
+
|
170
174
|
connection :basesAsSequelDataset, BaseConnectionWithTotalCountType, max_page_size: 1000 do
|
171
175
|
argument :nameIncludes, types.String
|
172
176
|
resolve ->(obj, args, ctx) {
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.6.
|
4
|
+
version: 1.6.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-08
|
11
|
+
date: 2017-09-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: benchmark-ips
|
@@ -192,20 +192,6 @@ dependencies:
|
|
192
192
|
- - "~>"
|
193
193
|
- !ruby/object:Gem::Version
|
194
194
|
version: '1.4'
|
195
|
-
- !ruby/object:Gem::Dependency
|
196
|
-
name: rails
|
197
|
-
requirement: !ruby/object:Gem::Requirement
|
198
|
-
requirements:
|
199
|
-
- - ">="
|
200
|
-
- !ruby/object:Gem::Version
|
201
|
-
version: '0'
|
202
|
-
type: :development
|
203
|
-
prerelease: false
|
204
|
-
version_requirements: !ruby/object:Gem::Requirement
|
205
|
-
requirements:
|
206
|
-
- - ">="
|
207
|
-
- !ruby/object:Gem::Version
|
208
|
-
version: '0'
|
209
195
|
- !ruby/object:Gem::Dependency
|
210
196
|
name: rake
|
211
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -639,6 +625,7 @@ files:
|
|
639
625
|
- spec/graphql/static_validation/validator_spec.rb
|
640
626
|
- spec/graphql/string_type_spec.rb
|
641
627
|
- spec/graphql/union_type_spec.rb
|
628
|
+
- spec/rails_dependency_sanity_spec.rb
|
642
629
|
- spec/spec_helper.rb
|
643
630
|
- spec/support/base_generator_test.rb
|
644
631
|
- spec/support/dummy/data.rb
|
@@ -793,6 +780,7 @@ test_files:
|
|
793
780
|
- spec/graphql/static_validation/validator_spec.rb
|
794
781
|
- spec/graphql/string_type_spec.rb
|
795
782
|
- spec/graphql/union_type_spec.rb
|
783
|
+
- spec/rails_dependency_sanity_spec.rb
|
796
784
|
- spec/spec_helper.rb
|
797
785
|
- spec/support/base_generator_test.rb
|
798
786
|
- spec/support/dummy/data.rb
|