graphql 1.8.1 → 1.8.2
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/core.rb +1 -1
- data/lib/generators/graphql/enum_generator.rb +3 -3
- data/lib/generators/graphql/install_generator.rb +12 -1
- data/lib/generators/graphql/mutation_generator.rb +4 -4
- data/lib/generators/graphql/templates/base_enum.erb +2 -0
- data/lib/generators/graphql/templates/base_input_object.erb +2 -0
- data/lib/generators/graphql/templates/base_interface.erb +3 -0
- data/lib/generators/graphql/templates/base_object.erb +2 -0
- data/lib/generators/graphql/templates/base_union.erb +2 -0
- data/lib/generators/graphql/templates/enum.erb +1 -2
- data/lib/generators/graphql/templates/interface.erb +2 -3
- data/lib/generators/graphql/templates/mutation.erb +4 -5
- data/lib/generators/graphql/templates/mutation_type.erb +6 -9
- data/lib/generators/graphql/templates/object.erb +2 -3
- data/lib/generators/graphql/templates/query_type.erb +6 -8
- data/lib/generators/graphql/templates/schema.erb +7 -7
- data/lib/generators/graphql/templates/union.erb +1 -2
- data/lib/generators/graphql/type_generator.rb +33 -18
- data/lib/generators/graphql/union_generator.rb +1 -1
- data/lib/graphql/execution/execute.rb +3 -0
- data/lib/graphql/schema.rb +1 -0
- data/lib/graphql/schema/input_object.rb +22 -1
- data/lib/graphql/schema/relay_classic_mutation.rb +6 -2
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +31 -0
- data/lib/graphql/subscriptions/instrumentation.rb +3 -0
- data/lib/graphql/version.rb +1 -1
- data/spec/generators/graphql/enum_generator_spec.rb +1 -2
- data/spec/generators/graphql/install_generator_spec.rb +23 -24
- data/spec/generators/graphql/interface_generator_spec.rb +5 -6
- data/spec/generators/graphql/mutation_generator_spec.rb +10 -27
- data/spec/generators/graphql/object_generator_spec.rb +7 -10
- data/spec/generators/graphql/union_generator_spec.rb +3 -6
- data/spec/graphql/execution/execute_spec.rb +97 -0
- data/spec/graphql/schema/input_object_spec.rb +32 -0
- data/spec/graphql/schema/relay_classic_mutation_spec.rb +16 -0
- data/spec/graphql/static_validation/rules/argument_names_are_unique_spec.rb +44 -0
- data/spec/graphql/subscriptions_spec.rb +21 -1
- data/spec/support/jazz.rb +15 -2
- metadata +10 -6
- data/lib/generators/graphql/function_generator.rb +0 -18
- data/lib/generators/graphql/templates/function.erb +0 -17
- data/spec/generators/graphql/function_generator_spec.rb +0 -59
@@ -44,7 +44,10 @@ module GraphQL
|
|
44
44
|
|
45
45
|
# Wrap the proc with subscription registration logic
|
46
46
|
def call(obj, args, ctx)
|
47
|
+
@inner_proc.call(obj, args, ctx) if @inner_proc && !@inner_proc.is_a?(GraphQL::Field::Resolve::BuiltInResolve)
|
48
|
+
|
47
49
|
events = ctx.namespace(:subscriptions)[:events]
|
50
|
+
|
48
51
|
if events
|
49
52
|
# This is the first execution, so gather an Event
|
50
53
|
# for the backend to register:
|
data/lib/graphql/version.rb
CHANGED
@@ -7,8 +7,7 @@ class GraphQLGeneratorsEnumGeneratorTest < BaseGeneratorTest
|
|
7
7
|
|
8
8
|
test "it generate enums with values" do
|
9
9
|
expected_content = <<-RUBY
|
10
|
-
Types::FamilyType
|
11
|
-
name "Family"
|
10
|
+
class Types::FamilyType < Types::BaseEnum
|
12
11
|
value "NIGHTSHADE"
|
13
12
|
value "BRASSICA", value: Family::COLE
|
14
13
|
value "UMBELLIFER", value: :umbellifer
|
@@ -19,6 +19,9 @@ class GraphQLGeneratorsInstallGeneratorTest < Rails::Generators::TestCase
|
|
19
19
|
|
20
20
|
assert_file "app/graphql/types/.keep"
|
21
21
|
assert_file "app/graphql/mutations/.keep"
|
22
|
+
["base_object", "base_input_object", "base_enum", "base_union", "base_interface"].each do |base_type|
|
23
|
+
assert_file "app/graphql/types/#{base_type}.rb"
|
24
|
+
end
|
22
25
|
expected_query_route = %|post "/graphql", to: "graphql#execute"|
|
23
26
|
expected_graphiql_route = %|
|
24
27
|
if Rails.env.development?
|
@@ -36,7 +39,7 @@ class GraphQLGeneratorsInstallGeneratorTest < Rails::Generators::TestCase
|
|
36
39
|
end
|
37
40
|
|
38
41
|
expected_schema = <<-RUBY
|
39
|
-
DummySchema
|
42
|
+
class DummySchema < GraphQL::Schema
|
40
43
|
mutation(Types::MutationType)
|
41
44
|
query(Types::QueryType)
|
42
45
|
end
|
@@ -45,17 +48,15 @@ RUBY
|
|
45
48
|
|
46
49
|
|
47
50
|
expected_query_type = <<-RUBY
|
48
|
-
Types::QueryType
|
49
|
-
name "Query"
|
51
|
+
class Types::QueryType < Types::BaseObject
|
50
52
|
# Add root-level fields here.
|
51
53
|
# They will be entry points for queries on your schema.
|
52
54
|
|
53
55
|
# TODO: remove me
|
54
|
-
field :
|
55
|
-
description \"An example field added by the generator\"
|
56
|
-
|
57
|
-
|
58
|
-
}
|
56
|
+
field :test_field, String, null: false,
|
57
|
+
description: \"An example field added by the generator\"
|
58
|
+
def test_field
|
59
|
+
\"Hello World!\"
|
59
60
|
end
|
60
61
|
end
|
61
62
|
RUBY
|
@@ -79,20 +80,18 @@ RUBY
|
|
79
80
|
end
|
80
81
|
|
81
82
|
expected_query_type = <<-RUBY
|
82
|
-
Types::QueryType
|
83
|
-
name "Query"
|
83
|
+
class Types::QueryType < Types::BaseObject
|
84
84
|
# Add root-level fields here.
|
85
85
|
# They will be entry points for queries on your schema.
|
86
86
|
|
87
87
|
# TODO: remove me
|
88
|
-
field :
|
89
|
-
description \"An example field added by the generator\"
|
90
|
-
|
91
|
-
|
92
|
-
}
|
88
|
+
field :test_field, String, null: false,
|
89
|
+
description: \"An example field added by the generator\"
|
90
|
+
def test_field
|
91
|
+
\"Hello World!\"
|
93
92
|
end
|
94
93
|
|
95
|
-
field :node, GraphQL::Relay::Node.field
|
94
|
+
field :node, field: GraphQL::Relay::Node.field
|
96
95
|
end
|
97
96
|
RUBY
|
98
97
|
|
@@ -126,7 +125,7 @@ RUBY
|
|
126
125
|
refute_includes contents, "GraphiQL::Rails"
|
127
126
|
end
|
128
127
|
|
129
|
-
assert_file "app/graphql/custom_schema.rb", /CustomSchema
|
128
|
+
assert_file "app/graphql/custom_schema.rb", /class CustomSchema < GraphQL::Schema/
|
130
129
|
assert_file "app/controllers/graphql_controller.rb", /CustomSchema\.execute/
|
131
130
|
end
|
132
131
|
|
@@ -167,36 +166,36 @@ end
|
|
167
166
|
RUBY
|
168
167
|
|
169
168
|
EXPECTED_RELAY_BATCH_SCHEMA = <<-RUBY
|
170
|
-
DummySchema
|
169
|
+
class DummySchema < GraphQL::Schema
|
171
170
|
|
172
171
|
mutation(Types::MutationType)
|
173
172
|
query(Types::QueryType)
|
174
173
|
# Relay Object Identification:
|
175
174
|
|
176
175
|
# Return a string UUID for `object`
|
177
|
-
id_from_object
|
176
|
+
def self.id_from_object(object, type_definition, query_ctx)
|
178
177
|
# Here's a simple implementation which:
|
179
178
|
# - joins the type name & object.id
|
180
179
|
# - encodes it with base64:
|
181
180
|
# GraphQL::Schema::UniqueWithinType.encode(type_definition.name, object.id)
|
182
|
-
|
181
|
+
end
|
183
182
|
|
184
183
|
# Given a string UUID, find the object
|
185
|
-
object_from_id
|
184
|
+
def self.object_from_id(id, query_ctx)
|
186
185
|
# For example, to decode the UUIDs generated above:
|
187
186
|
# type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id)
|
188
187
|
#
|
189
188
|
# Then, based on `type_name` and `id`
|
190
189
|
# find an object in your application
|
191
190
|
# ...
|
192
|
-
|
191
|
+
end
|
193
192
|
|
194
193
|
# Object Resolution
|
195
|
-
resolve_type
|
194
|
+
def self.resolve_type(type, obj, ctx)
|
196
195
|
# TODO: Implement this function
|
197
196
|
# to return the correct type for `obj`
|
198
197
|
raise(NotImplementedError)
|
199
|
-
|
198
|
+
end
|
200
199
|
|
201
200
|
# GraphQL::Batch setup:
|
202
201
|
use GraphQL::Batch
|
@@ -10,16 +10,15 @@ class GraphQLGeneratorsInterfaceGeneratorTest < BaseGeneratorTest
|
|
10
10
|
# GraphQL-style:
|
11
11
|
["Bird", "wingspan:Int!", "foliage:[Color]"],
|
12
12
|
# Ruby-style:
|
13
|
-
["BirdType", "wingspan
|
13
|
+
["BirdType", "wingspan:Integer!", "foliage:[Types::ColorType]"],
|
14
14
|
# Mixed
|
15
|
-
["BirdType", "wingspan:!Int", "foliage:
|
15
|
+
["BirdType", "wingspan:!Int", "foliage:[Color]"],
|
16
16
|
]
|
17
17
|
|
18
18
|
expected_content = <<-RUBY
|
19
|
-
Types::BirdType
|
20
|
-
|
21
|
-
field :
|
22
|
-
field :foliage, types[Types::ColorType]
|
19
|
+
class Types::BirdType < Types::BaseInterface
|
20
|
+
field :wingspan, Integer, null: false
|
21
|
+
field :foliage, [Types::ColorType], null: true
|
23
22
|
end
|
24
23
|
RUBY
|
25
24
|
|
@@ -19,47 +19,30 @@ class GraphQLGeneratorsMutationGeneratorTest < BaseGeneratorTest
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
run_generator(["UpdateName"])
|
25
|
-
|
26
|
-
expected_content = <<-RUBY
|
27
|
-
Mutations::UpdateName = GraphQL::Relay::Mutation.define do
|
28
|
-
name "UpdateName"
|
22
|
+
UPDATE_NAME_MUTATION = <<-RUBY
|
23
|
+
class Mutations::UpdateName < GraphQL::Schema::RelayClassicMutation
|
29
24
|
# TODO: define return fields
|
30
25
|
# return_field :post, Types::PostType
|
31
26
|
|
32
27
|
# TODO: define arguments
|
33
28
|
# input_field :name, !types.String
|
34
29
|
|
35
|
-
resolve
|
36
|
-
# TODO: define resolve
|
37
|
-
|
30
|
+
def resolve(**inputs)
|
31
|
+
# TODO: define resolve method
|
32
|
+
end
|
38
33
|
end
|
39
34
|
RUBY
|
40
35
|
|
41
|
-
|
36
|
+
test "it generates an empty resolver by name" do
|
37
|
+
setup
|
38
|
+
run_generator(["UpdateName"])
|
39
|
+
assert_file "app/graphql/mutations/update_name.rb", UPDATE_NAME_MUTATION
|
42
40
|
end
|
43
41
|
|
44
42
|
test "it allows for user-specified directory" do
|
45
43
|
setup "app/mydirectory"
|
46
44
|
run_generator(["UpdateName", "--directory", "app/mydirectory"])
|
47
45
|
|
48
|
-
|
49
|
-
Mutations::UpdateName = GraphQL::Relay::Mutation.define do
|
50
|
-
name "UpdateName"
|
51
|
-
# TODO: define return fields
|
52
|
-
# return_field :post, Types::PostType
|
53
|
-
|
54
|
-
# TODO: define arguments
|
55
|
-
# input_field :name, !types.String
|
56
|
-
|
57
|
-
resolve ->(obj, args, ctx) {
|
58
|
-
# TODO: define resolve function
|
59
|
-
}
|
60
|
-
end
|
61
|
-
RUBY
|
62
|
-
|
63
|
-
assert_file "app/mydirectory/mutations/update_name.rb", expected_content
|
46
|
+
assert_file "app/mydirectory/mutations/update_name.rb", UPDATE_NAME_MUTATION
|
64
47
|
end
|
65
48
|
end
|
@@ -10,16 +10,15 @@ class GraphQLGeneratorsObjectGeneratorTest < BaseGeneratorTest
|
|
10
10
|
# GraphQL-style:
|
11
11
|
["Bird", "wingspan:Int!", "foliage:[Color]"],
|
12
12
|
# Ruby-style:
|
13
|
-
["BirdType", "wingspan:!
|
13
|
+
["BirdType", "wingspan:!Integer", "foliage:[Types::ColorType]"],
|
14
14
|
# Mixed
|
15
|
-
["BirdType", "wingspan:!Int", "foliage:
|
15
|
+
["BirdType", "wingspan:!Int", "foliage:[Color]"],
|
16
16
|
]
|
17
17
|
|
18
18
|
expected_content = <<-RUBY
|
19
|
-
Types::BirdType
|
20
|
-
|
21
|
-
field :
|
22
|
-
field :foliage, types[Types::ColorType]
|
19
|
+
class Types::BirdType < Types::BaseObject
|
20
|
+
field :wingspan, Integer, null: false
|
21
|
+
field :foliage, [Types::ColorType], null: true
|
23
22
|
end
|
24
23
|
RUBY
|
25
24
|
|
@@ -33,8 +32,7 @@ RUBY
|
|
33
32
|
test "it generates classifed file" do
|
34
33
|
run_generator(["page"])
|
35
34
|
assert_file "app/graphql/types/page_type.rb", <<-RUBY
|
36
|
-
Types::PageType
|
37
|
-
name "Page"
|
35
|
+
class Types::PageType < Types::BaseObject
|
38
36
|
end
|
39
37
|
RUBY
|
40
38
|
end
|
@@ -42,8 +40,7 @@ RUBY
|
|
42
40
|
test "it makes Relay nodes" do
|
43
41
|
run_generator(["Page", "--node"])
|
44
42
|
assert_file "app/graphql/types/page_type.rb", <<-RUBY
|
45
|
-
Types::PageType
|
46
|
-
name "Page"
|
43
|
+
class Types::PageType < Types::BaseObject
|
47
44
|
implements GraphQL::Relay::Node.interface
|
48
45
|
end
|
49
46
|
RUBY
|
@@ -14,8 +14,7 @@ class GraphQLGeneratorsUnionGeneratorTest < BaseGeneratorTest
|
|
14
14
|
]
|
15
15
|
|
16
16
|
expected_content = <<-RUBY
|
17
|
-
Types::WingedCreatureType
|
18
|
-
name "WingedCreature"
|
17
|
+
class Types::WingedCreatureType < Types::BaseUnion
|
19
18
|
possible_types [Types::InsectType, Types::BirdType]
|
20
19
|
end
|
21
20
|
RUBY
|
@@ -36,8 +35,7 @@ RUBY
|
|
36
35
|
]
|
37
36
|
|
38
37
|
expected_content = <<-RUBY
|
39
|
-
Types::WingedCreatureType
|
40
|
-
name "WingedCreature"
|
38
|
+
class Types::WingedCreatureType < Types::BaseUnion
|
41
39
|
end
|
42
40
|
RUBY
|
43
41
|
|
@@ -52,8 +50,7 @@ RUBY
|
|
52
50
|
command = ["WingedCreature", "--directory", "app/mydirectory"]
|
53
51
|
|
54
52
|
expected_content = <<-RUBY
|
55
|
-
Types::WingedCreatureType
|
56
|
-
name "WingedCreature"
|
53
|
+
class Types::WingedCreatureType < Types::BaseUnion
|
57
54
|
end
|
58
55
|
RUBY
|
59
56
|
|
@@ -77,6 +77,103 @@ describe GraphQL::Execution::Execute do
|
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
|
+
describe "when a member of a list of non-null type returns nil" do
|
81
|
+
let(:schema) {
|
82
|
+
node_type = GraphQL::ObjectType.define do
|
83
|
+
name "Node"
|
84
|
+
|
85
|
+
field :id, types.ID, "" do
|
86
|
+
resolve ->(obj, args, ctx) {
|
87
|
+
obj[:id]
|
88
|
+
}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
query_type = GraphQL::ObjectType.define do
|
93
|
+
name "Query"
|
94
|
+
|
95
|
+
field :nonNullListWithNullStrings, types[!types.String], "" do
|
96
|
+
resolve ->(obj, args, ctx) {
|
97
|
+
[nil]
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
field :nonNullListWithNullStringsLazy, types[!types.String], "" do
|
102
|
+
resolve ->(obj, args, ctx) {
|
103
|
+
LazyHelpers::Wrapper.new([nil])
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
field :nonNullListWithNullTypes, types[!node_type], "" do
|
108
|
+
resolve ->(obj, args, ctx) {
|
109
|
+
[{ id: 1 }, nil, { id: 2 }]
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
field :listWithNullStrings, types[types.String], "" do
|
114
|
+
resolve ->(obj, args, ctx) {
|
115
|
+
[nil, "hello"]
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
field :listWithNullTypes, types[node_type], "" do
|
120
|
+
resolve ->(obj, args, ctx) {
|
121
|
+
[nil, { id: 1 }, nil]
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
field :nonNullList, !types[!types.String], "" do
|
126
|
+
resolve ->(obj, args, ctx) {
|
127
|
+
[nil]
|
128
|
+
}
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
GraphQL::Schema.define do
|
133
|
+
query query_type
|
134
|
+
lazy_resolve(LazyHelpers::Wrapper, :item)
|
135
|
+
end
|
136
|
+
}
|
137
|
+
|
138
|
+
it "propagates null for non-lazy resolvers" do
|
139
|
+
query = <<-GRAPHQL
|
140
|
+
{
|
141
|
+
nonNullListWithNullStrings
|
142
|
+
nonNullListWithNullTypes {
|
143
|
+
id
|
144
|
+
}
|
145
|
+
listWithNullStrings
|
146
|
+
listWithNullTypes {
|
147
|
+
id
|
148
|
+
}
|
149
|
+
}
|
150
|
+
GRAPHQL
|
151
|
+
|
152
|
+
result = schema.execute(query).to_h
|
153
|
+
|
154
|
+
assert_equal ["nonNullListWithNullStrings", "nonNullListWithNullTypes", "listWithNullStrings", "listWithNullTypes"], result["data"].keys
|
155
|
+
|
156
|
+
assert_equal nil, result["data"]["nonNullListWithNullStrings"]
|
157
|
+
assert_equal nil, result["data"]["nonNullListWithNullTypes"]
|
158
|
+
assert_equal [nil, "hello"], result["data"]["listWithNullStrings"]
|
159
|
+
assert_equal [nil, { "id" => "1" }, nil], result["data"]["listWithNullTypes"]
|
160
|
+
end
|
161
|
+
|
162
|
+
it "propagates null for lazy resolvers" do
|
163
|
+
result = schema.execute("{ nonNullListWithNullStringsLazy }").to_h
|
164
|
+
|
165
|
+
assert_equal ["nonNullListWithNullStringsLazy"], result["data"].keys
|
166
|
+
|
167
|
+
assert_equal nil, result["data"]["nonNullListWithNullStringsLazy"]
|
168
|
+
end
|
169
|
+
|
170
|
+
it "propagates null for non-null lists of non-null types" do
|
171
|
+
result = schema.execute("{ nonNullList }").to_h
|
172
|
+
|
173
|
+
assert_equal nil, result["data"]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
80
177
|
describe "when a list member raises an error" do
|
81
178
|
let(:schema) {
|
82
179
|
thing_type = GraphQL::ObjectType.define do
|
@@ -108,4 +108,36 @@ describe GraphQL::Schema::InputObject do
|
|
108
108
|
assert_equal expected_info, res["data"]["inspectInput"]
|
109
109
|
end
|
110
110
|
end
|
111
|
+
|
112
|
+
describe "#to_h" do
|
113
|
+
module InputObjectToHTest
|
114
|
+
class TestInput1 < GraphQL::Schema::InputObject
|
115
|
+
graphql_name "TestInput1"
|
116
|
+
argument :d, Int, required: true
|
117
|
+
argument :e, Int, required: true
|
118
|
+
end
|
119
|
+
|
120
|
+
class TestInput2 < GraphQL::Schema::InputObject
|
121
|
+
graphql_name "TestInput2"
|
122
|
+
argument :a, Int, required: true
|
123
|
+
argument :b, Int, required: true
|
124
|
+
argument :c, TestInput1, as: :inputObject, required: true
|
125
|
+
end
|
126
|
+
|
127
|
+
TestInput1.to_graphql
|
128
|
+
TestInput2.to_graphql
|
129
|
+
end
|
130
|
+
|
131
|
+
it "returns a symbolized, aliased, ruby keyword style hash" do
|
132
|
+
arg_values = {a: 1, b: 2, c: { d: 3, e: 4 }}
|
133
|
+
|
134
|
+
input_object = InputObjectToHTest::TestInput2.new(
|
135
|
+
arg_values,
|
136
|
+
context: nil,
|
137
|
+
defaults_used: Set.new
|
138
|
+
)
|
139
|
+
|
140
|
+
assert_equal({ a: 1, b: 2, input_object: { d: 3, e: 4 } }, input_object.to_h)
|
141
|
+
end
|
142
|
+
end
|
111
143
|
end
|
@@ -35,4 +35,20 @@ describe GraphQL::Schema::RelayClassicMutation do
|
|
35
35
|
assert mutation.null
|
36
36
|
end
|
37
37
|
end
|
38
|
+
|
39
|
+
describe "execution" do
|
40
|
+
it "works with no arguments" do
|
41
|
+
res = Jazz::Schema.execute <<-GRAPHQL
|
42
|
+
mutation {
|
43
|
+
addSitar(input: {}) {
|
44
|
+
instrument {
|
45
|
+
name
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
GRAPHQL
|
50
|
+
|
51
|
+
assert_equal "Sitar", res["data"]["addSitar"]["instrument"]["name"]
|
52
|
+
end
|
53
|
+
end
|
38
54
|
end
|