graphql 1.6.7 → 1.6.8
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 +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
|