graphql 1.4.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/graphql/introspection/schema_type.rb +3 -3
- data/lib/graphql/language/nodes.rb +1 -1
- data/lib/graphql/query.rb +39 -27
- data/lib/graphql/query/executor.rb +1 -1
- data/lib/graphql/query/variables.rb +21 -28
- data/lib/graphql/relay/connection_type.rb +2 -0
- data/lib/graphql/relay/relation_connection.rb +8 -2
- data/lib/graphql/schema.rb +3 -0
- data/lib/graphql/schema/warden.rb +9 -0
- data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/analysis/analyze_query_spec.rb +15 -15
- data/spec/graphql/analysis/field_usage_spec.rb +1 -1
- data/spec/graphql/analysis/max_query_complexity_spec.rb +8 -8
- data/spec/graphql/analysis/max_query_depth_spec.rb +7 -7
- data/spec/graphql/analysis/query_complexity_spec.rb +2 -2
- data/spec/graphql/analysis/query_depth_spec.rb +1 -1
- data/spec/graphql/base_type_spec.rb +19 -11
- data/spec/graphql/directive_spec.rb +1 -1
- data/spec/graphql/enum_type_spec.rb +2 -2
- data/spec/graphql/execution/typecast_spec.rb +19 -19
- data/spec/graphql/execution_error_spec.rb +1 -1
- data/spec/graphql/field_spec.rb +15 -7
- data/spec/graphql/id_type_spec.rb +1 -1
- data/spec/graphql/input_object_type_spec.rb +16 -16
- data/spec/graphql/interface_type_spec.rb +6 -6
- data/spec/graphql/internal_representation/rewrite_spec.rb +34 -34
- data/spec/graphql/introspection/directive_type_spec.rb +1 -1
- data/spec/graphql/introspection/input_value_type_spec.rb +2 -2
- data/spec/graphql/introspection/introspection_query_spec.rb +1 -1
- data/spec/graphql/introspection/schema_type_spec.rb +2 -2
- data/spec/graphql/introspection/type_type_spec.rb +1 -1
- data/spec/graphql/language/parser_spec.rb +1 -1
- data/spec/graphql/non_null_type_spec.rb +3 -3
- data/spec/graphql/object_type_spec.rb +8 -8
- data/spec/graphql/query/executor_spec.rb +4 -4
- data/spec/graphql/query/variables_spec.rb +20 -4
- data/spec/graphql/query_spec.rb +20 -2
- data/spec/graphql/relay/connection_type_spec.rb +1 -1
- data/spec/graphql/relay/mutation_spec.rb +9 -9
- data/spec/graphql/relay/node_spec.rb +8 -8
- data/spec/graphql/relay/relation_connection_spec.rb +24 -6
- data/spec/graphql/schema/catchall_middleware_spec.rb +3 -3
- data/spec/graphql/schema/reduce_types_spec.rb +9 -9
- data/spec/graphql/schema/type_expression_spec.rb +3 -3
- data/spec/graphql/schema/validation_spec.rb +1 -1
- data/spec/graphql/schema/warden_spec.rb +79 -0
- data/spec/graphql/schema_spec.rb +2 -2
- data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +1 -1
- data/spec/graphql/static_validation/type_stack_spec.rb +2 -2
- data/spec/graphql/static_validation/validator_spec.rb +2 -2
- data/spec/graphql/union_type_spec.rb +2 -2
- data/spec/spec_helper.rb +1 -1
- data/spec/support/dummy/data.rb +27 -0
- data/spec/support/dummy/schema.rb +369 -0
- data/spec/support/star_wars/data.rb +81 -0
- data/spec/support/star_wars/schema.rb +250 -0
- data/spec/support/static_validation_helpers.rb +2 -2
- metadata +10 -10
- data/spec/support/dairy_app.rb +0 -369
- data/spec/support/dairy_data.rb +0 -26
- data/spec/support/star_wars_data.rb +0 -80
- data/spec/support/star_wars_schema.rb +0 -242
@@ -13,24 +13,24 @@ describe GraphQL::Relay::Node do
|
|
13
13
|
before do
|
14
14
|
# TODO: make the schema eager-load so we can remove this
|
15
15
|
# Ensure the schema is defined:
|
16
|
-
|
16
|
+
StarWars::Schema.types
|
17
17
|
|
18
|
-
@previous_id_from_object_proc =
|
19
|
-
@previous_object_from_id_proc =
|
18
|
+
@previous_id_from_object_proc = StarWars::Schema.id_from_object_proc
|
19
|
+
@previous_object_from_id_proc = StarWars::Schema.object_from_id_proc
|
20
20
|
|
21
|
-
|
21
|
+
StarWars::Schema.id_from_object = ->(obj, type_name, ctx) {
|
22
22
|
"#{type_name}/#{obj.id}"
|
23
23
|
}
|
24
24
|
|
25
|
-
|
25
|
+
StarWars::Schema.object_from_id = ->(global_id, ctx) {
|
26
26
|
type_name, id = global_id.split("/")
|
27
|
-
|
27
|
+
StarWars::DATA[type_name][id]
|
28
28
|
}
|
29
29
|
end
|
30
30
|
|
31
31
|
after do
|
32
|
-
|
33
|
-
|
32
|
+
StarWars::Schema.id_from_object = @previous_id_from_object_proc
|
33
|
+
StarWars::Schema.object_from_id = @previous_object_from_id_proc
|
34
34
|
end
|
35
35
|
|
36
36
|
it "Deconstructs the ID by the custom proc" do
|
@@ -69,7 +69,7 @@ describe GraphQL::Relay::RelationConnection do
|
|
69
69
|
it 'provides custom fields on the connection type' do
|
70
70
|
result = star_wars_query(query_string, "first" => 2)
|
71
71
|
assert_equal(
|
72
|
-
Base.where(faction_id: 2).count,
|
72
|
+
StarWars::Base.where(faction_id: 2).count,
|
73
73
|
result["data"]["empire"]["bases"]["totalCount"]
|
74
74
|
)
|
75
75
|
end
|
@@ -105,6 +105,24 @@ describe GraphQL::Relay::RelationConnection do
|
|
105
105
|
assert_equal([], get_names(result))
|
106
106
|
end
|
107
107
|
|
108
|
+
it 'handles grouped connections with only last argument' do
|
109
|
+
grouped_conn_query = <<-GRAPHQL
|
110
|
+
query {
|
111
|
+
newestBasesGroupedByFaction(last: 2) {
|
112
|
+
edges {
|
113
|
+
node {
|
114
|
+
name
|
115
|
+
}
|
116
|
+
}
|
117
|
+
}
|
118
|
+
}
|
119
|
+
GRAPHQL
|
120
|
+
|
121
|
+
result = star_wars_query(grouped_conn_query)
|
122
|
+
names = result['data']['newestBasesGroupedByFaction']['edges'].map { |edge| edge['node']['name'] }
|
123
|
+
assert_equal(['Headquarters', 'Secret Hideout'], names)
|
124
|
+
end
|
125
|
+
|
108
126
|
it "applies custom arguments" do
|
109
127
|
result = star_wars_query(query_string, "first" => 1, "nameIncludes" => "ea")
|
110
128
|
assert_equal(["Death Star"], get_names(result))
|
@@ -311,7 +329,7 @@ describe GraphQL::Relay::RelationConnection do
|
|
311
329
|
it 'provides custom fields on the connection type' do
|
312
330
|
result = star_wars_query(query_string, "first" => 2)
|
313
331
|
assert_equal(
|
314
|
-
Base.where(faction_id: 2).count,
|
332
|
+
StarWars::Base.where(faction_id: 2).count,
|
315
333
|
result["data"]["empire"]["basesAsSequelDataset"]["totalCount"]
|
316
334
|
)
|
317
335
|
end
|
@@ -356,11 +374,11 @@ describe GraphQL::Relay::RelationConnection do
|
|
356
374
|
end
|
357
375
|
|
358
376
|
describe "#cursor_from_node" do
|
359
|
-
let(:connection) { GraphQL::Relay::RelationConnection.new(Base.where(faction_id: 1), {}) }
|
377
|
+
let(:connection) { GraphQL::Relay::RelationConnection.new(StarWars::Base.where(faction_id: 1), {}) }
|
360
378
|
|
361
379
|
it "returns the cursor for a node in the connection" do
|
362
|
-
assert_equal "MQ==", connection.cursor_from_node(Base.all[0])
|
363
|
-
assert_equal "Mg==", connection.cursor_from_node(Base.all[1])
|
380
|
+
assert_equal "MQ==", connection.cursor_from_node(StarWars::Base.all[0])
|
381
|
+
assert_equal "Mg==", connection.cursor_from_node(StarWars::Base.all[1])
|
364
382
|
end
|
365
383
|
|
366
384
|
it "raises when the node isn't found" do
|
@@ -372,7 +390,7 @@ describe GraphQL::Relay::RelationConnection do
|
|
372
390
|
end
|
373
391
|
|
374
392
|
it "is chosen for a relation" do
|
375
|
-
relation = Base.where(faction_id: 1)
|
393
|
+
relation = StarWars::Base.where(faction_id: 1)
|
376
394
|
assert relation.is_a?(ActiveRecord::Relation)
|
377
395
|
connection = GraphQL::Relay::BaseConnection.connection_for_nodes(relation)
|
378
396
|
assert_equal GraphQL::Relay::RelationConnection, connection
|
@@ -2,15 +2,15 @@
|
|
2
2
|
require "spec_helper"
|
3
3
|
|
4
4
|
describe GraphQL::Schema::CatchallMiddleware do
|
5
|
-
let(:result) {
|
5
|
+
let(:result) { Dummy::Schema.execute(query_string) }
|
6
6
|
let(:query_string) {%| query noMilk { error }|}
|
7
7
|
|
8
8
|
before do
|
9
|
-
|
9
|
+
Dummy::Schema.middleware << GraphQL::Schema::CatchallMiddleware
|
10
10
|
end
|
11
11
|
|
12
12
|
after do
|
13
|
-
|
13
|
+
Dummy::Schema.middleware.delete(GraphQL::Schema::CatchallMiddleware)
|
14
14
|
end
|
15
15
|
|
16
16
|
describe "rescuing errors" do
|
@@ -8,23 +8,23 @@ describe GraphQL::Schema::ReduceTypes do
|
|
8
8
|
|
9
9
|
it "finds types from a single type and its fields" do
|
10
10
|
expected = {
|
11
|
-
"Cheese" => CheeseType,
|
11
|
+
"Cheese" => Dummy::CheeseType,
|
12
12
|
"Float" => GraphQL::FLOAT_TYPE,
|
13
13
|
"String" => GraphQL::STRING_TYPE,
|
14
|
-
"Edible" => EdibleInterface,
|
15
|
-
"DairyAnimal" => DairyAnimalEnum,
|
14
|
+
"Edible" => Dummy::EdibleInterface,
|
15
|
+
"DairyAnimal" => Dummy::DairyAnimalEnum,
|
16
16
|
"Int" => GraphQL::INT_TYPE,
|
17
|
-
"AnimalProduct" => AnimalProductInterface,
|
18
|
-
"LocalProduct" => LocalProductInterface,
|
17
|
+
"AnimalProduct" => Dummy::AnimalProductInterface,
|
18
|
+
"LocalProduct" => Dummy::LocalProductInterface,
|
19
19
|
}
|
20
|
-
result = reduce_types([CheeseType])
|
20
|
+
result = reduce_types([Dummy::CheeseType])
|
21
21
|
assert_equal(expected.keys, result.keys)
|
22
22
|
assert_equal(expected, result.to_h)
|
23
23
|
end
|
24
24
|
|
25
25
|
it "finds type from arguments" do
|
26
|
-
result = reduce_types([DairyAppQueryType])
|
27
|
-
assert_equal(DairyProductInputType, result["DairyProductInput"])
|
26
|
+
result = reduce_types([Dummy::DairyAppQueryType])
|
27
|
+
assert_equal(Dummy::DairyProductInputType, result["DairyProductInput"])
|
28
28
|
end
|
29
29
|
|
30
30
|
it "finds types from nested InputObjectTypes" do
|
@@ -98,7 +98,7 @@ describe GraphQL::Schema::ReduceTypes do
|
|
98
98
|
|
99
99
|
describe "when a field is only accessible through an interface" do
|
100
100
|
it "is found through Schema.define(types:)" do
|
101
|
-
assert_equal HoneyType,
|
101
|
+
assert_equal Dummy::HoneyType, Dummy::Schema.types["Honey"]
|
102
102
|
end
|
103
103
|
end
|
104
104
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
require "spec_helper"
|
3
3
|
|
4
4
|
describe GraphQL::Schema::TypeExpression do
|
5
|
-
let(:types) {
|
5
|
+
let(:types) { Dummy::Schema.types }
|
6
6
|
let(:ast_node) {
|
7
7
|
document = GraphQL.parse("query dostuff($var: #{type_name}) { id } ")
|
8
8
|
document.definitions.first.variables.first.type
|
@@ -13,7 +13,7 @@ describe GraphQL::Schema::TypeExpression do
|
|
13
13
|
describe "simple types" do
|
14
14
|
let(:type_name) { "DairyProductInput" }
|
15
15
|
it "it gets types from the provided types" do
|
16
|
-
assert_equal(DairyProductInputType, type_expression_result)
|
16
|
+
assert_equal(Dummy::DairyProductInputType, type_expression_result)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -28,7 +28,7 @@ describe GraphQL::Schema::TypeExpression do
|
|
28
28
|
let(:type_name) { "[DairyAnimal!]!" }
|
29
29
|
|
30
30
|
it "makes list types" do
|
31
|
-
expected = DairyAnimalEnum
|
31
|
+
expected = Dummy::DairyAnimalEnum
|
32
32
|
.to_non_null_type
|
33
33
|
.to_list_type
|
34
34
|
.to_non_null_type
|
@@ -96,8 +96,17 @@ module MaskHelpers
|
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
99
|
+
MutationType = GraphQL::ObjectType.define do
|
100
|
+
name "Mutation"
|
101
|
+
field :add_phoneme, PhonemeType do
|
102
|
+
argument :symbol, types.String
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
99
106
|
Schema = GraphQL::Schema.define do
|
100
107
|
query QueryType
|
108
|
+
mutation MutationType
|
109
|
+
subscription MutationType
|
101
110
|
resolve_type -> (obj, ctx) { PhonemeType }
|
102
111
|
end
|
103
112
|
|
@@ -151,6 +160,52 @@ describe GraphQL::Schema::Warden do
|
|
151
160
|
query_result["errors"].map { |err| err["message"] }
|
152
161
|
end
|
153
162
|
|
163
|
+
describe "hiding root types" do
|
164
|
+
let(:mask) { ->(m, ctx) { m == MaskHelpers::MutationType } }
|
165
|
+
|
166
|
+
it "acts as if the root doesn't exist" do
|
167
|
+
query_string = %|mutation { add_phoneme(symbol: "ϕ") { name } }|
|
168
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
169
|
+
assert MaskHelpers::Schema.mutation # it _does_ exist
|
170
|
+
assert_equal 1, res["errors"].length
|
171
|
+
assert_equal "Schema is not configured for mutations", res["errors"][0]["message"]
|
172
|
+
|
173
|
+
query_string = %|subscription { add_phoneme(symbol: "ϕ") { name } }|
|
174
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
175
|
+
assert MaskHelpers::Schema.subscription # it _does_ exist
|
176
|
+
assert_equal 1, res["errors"].length
|
177
|
+
assert_equal "Schema is not configured for subscriptions", res["errors"][0]["message"]
|
178
|
+
end
|
179
|
+
|
180
|
+
it "doesn't show in introspection" do
|
181
|
+
query_string = <<-GRAPHQL
|
182
|
+
{
|
183
|
+
__schema {
|
184
|
+
queryType {
|
185
|
+
name
|
186
|
+
}
|
187
|
+
mutationType {
|
188
|
+
name
|
189
|
+
}
|
190
|
+
subscriptionType {
|
191
|
+
name
|
192
|
+
}
|
193
|
+
types {
|
194
|
+
name
|
195
|
+
}
|
196
|
+
}
|
197
|
+
}
|
198
|
+
GRAPHQL
|
199
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
200
|
+
assert_equal "Query", res["data"]["__schema"]["queryType"]["name"]
|
201
|
+
assert_equal nil, res["data"]["__schema"]["mutationType"]
|
202
|
+
assert_equal nil, res["data"]["__schema"]["subscriptionType"]
|
203
|
+
type_names = res["data"]["__schema"]["types"].map { |t| t["name"] }
|
204
|
+
refute type_names.include?("Mutation")
|
205
|
+
refute type_names.include?("Subscription")
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
154
209
|
describe "hiding fields" do
|
155
210
|
let(:mask) {
|
156
211
|
-> (member, ctx) { member.metadata[:hidden_field] || member.metadata[:hidden_type] }
|
@@ -512,4 +567,28 @@ describe GraphQL::Schema::Warden do
|
|
512
567
|
}
|
513
568
|
end
|
514
569
|
end
|
570
|
+
|
571
|
+
describe "default_mask" do
|
572
|
+
let(:default_mask) {
|
573
|
+
-> (member, ctx) { member.metadata[:hidden_enum_value] }
|
574
|
+
}
|
575
|
+
let(:schema) {
|
576
|
+
MaskHelpers::Schema.redefine(default_mask: default_mask)
|
577
|
+
}
|
578
|
+
let(:query_str) { <<-GRAPHQL
|
579
|
+
{
|
580
|
+
enum: __type(name: "Manner") { enumValues { name } }
|
581
|
+
input: __type(name: "WithinInput") { name }
|
582
|
+
}
|
583
|
+
GRAPHQL
|
584
|
+
}
|
585
|
+
|
586
|
+
it "is additive with query filters" do
|
587
|
+
query_except = -> (member, ctx) { member.metadata[:hidden_input_object_type] }
|
588
|
+
res = schema.execute(query_str, except: query_except)
|
589
|
+
assert_equal nil, res["data"]["input"]
|
590
|
+
enum_values = res["data"]["enum"]["enumValues"].map { |v| v["name"] }
|
591
|
+
refute_includes enum_values, "TRILL"
|
592
|
+
end
|
593
|
+
end
|
515
594
|
end
|
data/spec/graphql/schema_spec.rb
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
require "spec_helper"
|
3
3
|
|
4
4
|
describe GraphQL::Schema do
|
5
|
-
let(:schema) {
|
6
|
-
let(:relay_schema) {
|
5
|
+
let(:schema) { Dummy::Schema }
|
6
|
+
let(:relay_schema) { StarWars::Schema }
|
7
7
|
let(:empty_schema) { GraphQL::Schema.define }
|
8
8
|
|
9
9
|
describe "#rescue_from" do
|
@@ -33,7 +33,7 @@ describe GraphQL::StaticValidation::FragmentsAreUsed do
|
|
33
33
|
let(:query_string) {%|
|
34
34
|
# I am a comment.
|
35
35
|
|}
|
36
|
-
let(:result) {
|
36
|
+
let(:result) { Dummy::Schema.execute(query_string) }
|
37
37
|
it "handles them gracefully" do
|
38
38
|
assert_equal({}, result)
|
39
39
|
end
|
@@ -22,8 +22,8 @@ describe GraphQL::StaticValidation::TypeStack do
|
|
22
22
|
fragment edibleFields on Edible { fatContent @skip(if: false)}
|
23
23
|
|}
|
24
24
|
|
25
|
-
let(:validator) { GraphQL::StaticValidation::Validator.new(schema:
|
26
|
-
let(:query) { GraphQL::Query.new(
|
25
|
+
let(:validator) { GraphQL::StaticValidation::Validator.new(schema: Dummy::Schema, rules: [TypeCheckValidator]) }
|
26
|
+
let(:query) { GraphQL::Query.new(Dummy::Schema, query_string) }
|
27
27
|
|
28
28
|
|
29
29
|
it "stores up types" do
|
@@ -2,8 +2,8 @@
|
|
2
2
|
require "spec_helper"
|
3
3
|
|
4
4
|
describe GraphQL::StaticValidation::Validator do
|
5
|
-
let(:validator) { GraphQL::StaticValidation::Validator.new(schema:
|
6
|
-
let(:query) { GraphQL::Query.new(
|
5
|
+
let(:validator) { GraphQL::StaticValidation::Validator.new(schema: Dummy::Schema) }
|
6
|
+
let(:query) { GraphQL::Query.new(Dummy::Schema, query_string) }
|
7
7
|
let(:errors) { validator.validate(query)[:errors].map(&:to_h) }
|
8
8
|
|
9
9
|
|
@@ -27,7 +27,7 @@ describe GraphQL::UnionType do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
describe "typecasting from union to union" do
|
30
|
-
let(:result) {
|
30
|
+
let(:result) { Dummy::Schema.execute(query_string) }
|
31
31
|
let(:query_string) {%|
|
32
32
|
{
|
33
33
|
allDairy {
|
@@ -55,7 +55,7 @@ describe GraphQL::UnionType do
|
|
55
55
|
|
56
56
|
describe "list of union type" do
|
57
57
|
describe "fragment spreads" do
|
58
|
-
let(:result) {
|
58
|
+
let(:result) { Dummy::Schema.execute(query_string) }
|
59
59
|
let(:query_string) {%|
|
60
60
|
{
|
61
61
|
allDairy {
|
data/spec/spec_helper.rb
CHANGED
@@ -48,5 +48,5 @@ end
|
|
48
48
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
49
49
|
|
50
50
|
def star_wars_query(string, variables={})
|
51
|
-
GraphQL::Query.new(
|
51
|
+
GraphQL::Query.new(StarWars::Schema, string, variables: variables).result
|
52
52
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'ostruct'
|
3
|
+
module Dummy
|
4
|
+
Cheese = Struct.new(:id, :flavor, :origin, :fat_content, :source)
|
5
|
+
CHEESES = {
|
6
|
+
1 => Cheese.new(1, "Brie", "France", 0.19, 1),
|
7
|
+
2 => Cheese.new(2, "Gouda", "Netherlands", 0.3, 1),
|
8
|
+
3 => Cheese.new(3, "Manchego", "Spain", 0.065, "SHEEP")
|
9
|
+
}
|
10
|
+
|
11
|
+
Milk = Struct.new(:id, :fatContent, :origin, :source, :flavors)
|
12
|
+
MILKS = {
|
13
|
+
1 => Milk.new(1, 0.04, "Antiquity", 1, ["Natural", "Chocolate", "Strawberry"]),
|
14
|
+
}
|
15
|
+
|
16
|
+
DAIRY = OpenStruct.new(
|
17
|
+
id: 1,
|
18
|
+
cheese: CHEESES[1],
|
19
|
+
milks: [MILKS[1]]
|
20
|
+
)
|
21
|
+
|
22
|
+
COW = OpenStruct.new(
|
23
|
+
id: 1,
|
24
|
+
name: "Billy",
|
25
|
+
last_produced_dairy: MILKS[1]
|
26
|
+
)
|
27
|
+
end
|
@@ -0,0 +1,369 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Dummy
|
3
|
+
class NoSuchDairyError < StandardError; end
|
4
|
+
|
5
|
+
GraphQL::Field.accepts_definitions(joins: GraphQL::Define.assign_metadata_key(:joins))
|
6
|
+
GraphQL::BaseType.accepts_definitions(class_names: GraphQL::Define.assign_metadata_key(:class_names))
|
7
|
+
|
8
|
+
LocalProductInterface = GraphQL::InterfaceType.define do
|
9
|
+
name "LocalProduct"
|
10
|
+
description "Something that comes from somewhere"
|
11
|
+
field :origin, !types.String, "Place the thing comes from"
|
12
|
+
end
|
13
|
+
|
14
|
+
EdibleInterface = GraphQL::InterfaceType.define do
|
15
|
+
name "Edible"
|
16
|
+
description "Something you can eat, yum"
|
17
|
+
field :fatContent, !types.Float, "Percentage which is fat"
|
18
|
+
field :origin, !types.String, "Place the edible comes from"
|
19
|
+
field :selfAsEdible, EdibleInterface, resolve: ->(o, a, c) { o }
|
20
|
+
end
|
21
|
+
|
22
|
+
AnimalProductInterface = GraphQL::InterfaceType.define do
|
23
|
+
name "AnimalProduct"
|
24
|
+
description "Comes from an animal, no joke"
|
25
|
+
field :source, !types.String, "Animal which produced this product"
|
26
|
+
end
|
27
|
+
|
28
|
+
BeverageUnion = GraphQL::UnionType.define do
|
29
|
+
name "Beverage"
|
30
|
+
description "Something you can drink"
|
31
|
+
possible_types [MilkType]
|
32
|
+
end
|
33
|
+
|
34
|
+
DairyAnimalEnum = GraphQL::EnumType.define do
|
35
|
+
name "DairyAnimal"
|
36
|
+
description "An animal which can yield milk"
|
37
|
+
value("COW", "Animal with black and white spots", value: 1)
|
38
|
+
value("DONKEY", "Animal with fur", value: :donkey)
|
39
|
+
value("GOAT", "Animal with horns")
|
40
|
+
value("REINDEER", "Animal with horns", value: 'reindeer')
|
41
|
+
value("SHEEP", "Animal with wool")
|
42
|
+
value("YAK", "Animal with long hair", deprecation_reason: "Out of fashion")
|
43
|
+
end
|
44
|
+
|
45
|
+
CheeseType = GraphQL::ObjectType.define do
|
46
|
+
name "Cheese"
|
47
|
+
class_names ["Cheese"]
|
48
|
+
description "Cultured dairy product"
|
49
|
+
interfaces [EdibleInterface, AnimalProductInterface, LocalProductInterface]
|
50
|
+
|
51
|
+
# Can have (name, type, desc)
|
52
|
+
field :id, !types.Int, "Unique identifier"
|
53
|
+
field :flavor, !types.String, "Kind of Cheese"
|
54
|
+
field :origin, !types.String, "Place the cheese comes from"
|
55
|
+
|
56
|
+
field :source, !DairyAnimalEnum,
|
57
|
+
"Animal which produced the milk for this cheese"
|
58
|
+
|
59
|
+
# Or can define by block, `resolve ->` should override `property:`
|
60
|
+
field :similarCheese, CheeseType, "Cheeses like this one", property: :this_should_be_overriden do
|
61
|
+
# metadata test
|
62
|
+
joins [:cheeses, :milks]
|
63
|
+
argument :source, !types[!DairyAnimalEnum]
|
64
|
+
argument :nullableSource, types[!DairyAnimalEnum], default_value: [1]
|
65
|
+
resolve ->(t, a, c) {
|
66
|
+
# get the strings out:
|
67
|
+
sources = a["source"]
|
68
|
+
if sources.include?("YAK")
|
69
|
+
raise NoSuchDairyError.new("No cheeses are made from Yak milk!")
|
70
|
+
else
|
71
|
+
CHEESES.values.find { |c| sources.include?(c.source) }
|
72
|
+
end
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
field :nullableCheese, CheeseType, "Cheeses like this one" do
|
77
|
+
argument :source, types[!DairyAnimalEnum]
|
78
|
+
resolve ->(t, a, c) { raise("NotImplemented") }
|
79
|
+
end
|
80
|
+
|
81
|
+
field :deeplyNullableCheese, CheeseType, "Cheeses like this one" do
|
82
|
+
argument :source, types[types[DairyAnimalEnum]]
|
83
|
+
resolve ->(t, a, c) { raise("NotImplemented") }
|
84
|
+
end
|
85
|
+
|
86
|
+
# Keywords can be used for definition methods
|
87
|
+
field :fatContent,
|
88
|
+
property: :fat_content,
|
89
|
+
type: !GraphQL::FLOAT_TYPE,
|
90
|
+
description: "Percentage which is milkfat",
|
91
|
+
deprecation_reason: "Diet fashion has changed"
|
92
|
+
end
|
93
|
+
|
94
|
+
MilkType = GraphQL::ObjectType.define do
|
95
|
+
name "Milk"
|
96
|
+
description "Dairy beverage"
|
97
|
+
interfaces [EdibleInterface, AnimalProductInterface, LocalProductInterface]
|
98
|
+
field :id, !types.ID
|
99
|
+
field :source, DairyAnimalEnum, "Animal which produced this milk", hash_key: :source
|
100
|
+
field :origin, !types.String, "Place the milk comes from"
|
101
|
+
field :flavors, types[types.String], "Chocolate, Strawberry, etc" do
|
102
|
+
argument :limit, types.Int
|
103
|
+
resolve ->(milk, args, ctx) {
|
104
|
+
args[:limit] ? milk.flavors.first(args[:limit]) : milk.flavors
|
105
|
+
}
|
106
|
+
end
|
107
|
+
field :executionError do
|
108
|
+
type GraphQL::STRING_TYPE
|
109
|
+
resolve ->(t, a, c) { raise(GraphQL::ExecutionError, "There was an execution error") }
|
110
|
+
end
|
111
|
+
|
112
|
+
field :allDairy, -> { types[DairyProductUnion] } do
|
113
|
+
resolve ->(obj, args, ctx) { CHEESES.values + MILKS.values }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
SweetenerInterface = GraphQL::InterfaceType.define do
|
118
|
+
name "Sweetener"
|
119
|
+
field :sweetness, types.Int
|
120
|
+
end
|
121
|
+
|
122
|
+
# No actual data; This type is an "orphan", only accessible through Interfaces
|
123
|
+
HoneyType = GraphQL::ObjectType.define do
|
124
|
+
name "Honey"
|
125
|
+
description "Sweet, dehydrated bee barf"
|
126
|
+
field :flowerType, types.String, "What flower this honey came from"
|
127
|
+
interfaces [EdibleInterface, AnimalProductInterface, SweetenerInterface]
|
128
|
+
end
|
129
|
+
|
130
|
+
DairyType = GraphQL::ObjectType.define do
|
131
|
+
name "Dairy"
|
132
|
+
description "A farm where milk is harvested and cheese is produced"
|
133
|
+
field :id, !types.ID
|
134
|
+
field :cheese, CheeseType
|
135
|
+
field :milks, types[MilkType]
|
136
|
+
end
|
137
|
+
|
138
|
+
MaybeNullType = GraphQL::ObjectType.define do
|
139
|
+
name "MaybeNull"
|
140
|
+
description "An object whose fields return nil"
|
141
|
+
field :cheese, CheeseType
|
142
|
+
end
|
143
|
+
|
144
|
+
DairyProductUnion = GraphQL::UnionType.define do
|
145
|
+
name "DairyProduct"
|
146
|
+
description "Kinds of food made from milk"
|
147
|
+
# Test that these forms of declaration still work:
|
148
|
+
possible_types ["Dummy::MilkType", -> { CheeseType }]
|
149
|
+
end
|
150
|
+
|
151
|
+
CowType = GraphQL::ObjectType.define do
|
152
|
+
name "Cow"
|
153
|
+
description "A farm where milk is harvested and cheese is produced"
|
154
|
+
field :id, !types.ID
|
155
|
+
field :name, types.String
|
156
|
+
field :last_produced_dairy, DairyProductUnion
|
157
|
+
|
158
|
+
field :cantBeNullButIs do
|
159
|
+
type !GraphQL::STRING_TYPE
|
160
|
+
resolve ->(t, a, c) { nil }
|
161
|
+
end
|
162
|
+
|
163
|
+
field :cantBeNullButRaisesExecutionError do
|
164
|
+
type !GraphQL::STRING_TYPE
|
165
|
+
resolve ->(t, a, c) { raise GraphQL::ExecutionError, "BOOM" }
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
DairyProductInputType = GraphQL::InputObjectType.define {
|
170
|
+
name "DairyProductInput"
|
171
|
+
description "Properties for finding a dairy product"
|
172
|
+
input_field :source, !DairyAnimalEnum do
|
173
|
+
# ensure we can define description in block
|
174
|
+
description "Where it came from"
|
175
|
+
end
|
176
|
+
|
177
|
+
input_field :originDairy, types.String, "Dairy which produced it", default_value: "Sugar Hollow Dairy" do
|
178
|
+
description "Ignored because arg takes precedence"
|
179
|
+
default_value "Ignored because keyword arg takes precedence"
|
180
|
+
end
|
181
|
+
|
182
|
+
input_field :fatContent, types.Float, "How much fat it has" do
|
183
|
+
# ensure we can define default in block
|
184
|
+
default_value 0.3
|
185
|
+
end
|
186
|
+
|
187
|
+
# ensure default can be false
|
188
|
+
input_field :organic, types.Boolean, default_value: false
|
189
|
+
}
|
190
|
+
|
191
|
+
DeepNonNullType = GraphQL::ObjectType.define do
|
192
|
+
name "DeepNonNull"
|
193
|
+
field :nonNullInt, !types.Int do
|
194
|
+
argument :returning, types.Int
|
195
|
+
resolve ->(obj, args, ctx) { args[:returning] }
|
196
|
+
end
|
197
|
+
|
198
|
+
field :deepNonNull, DeepNonNullType.to_non_null_type do
|
199
|
+
resolve ->(obj, args, ctx) { :deepNonNull }
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class FetchField
|
204
|
+
def self.create(type:, data:, id_type: !GraphQL::INT_TYPE)
|
205
|
+
desc = "Find a #{type.name} by id"
|
206
|
+
return_type = type
|
207
|
+
GraphQL::Field.define do
|
208
|
+
type(return_type)
|
209
|
+
description(desc)
|
210
|
+
argument :id, id_type
|
211
|
+
|
212
|
+
resolve ->(t, a, c) {
|
213
|
+
id_string = a["id"].to_s # Cheese has Int type, Milk has ID type :(
|
214
|
+
id, item = data.find { |id, item| id.to_s == id_string }
|
215
|
+
item
|
216
|
+
}
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
class SingletonField
|
222
|
+
def self.create(type:, data:)
|
223
|
+
desc = "Find the only #{type.name}"
|
224
|
+
return_type = type
|
225
|
+
GraphQL::Field.define do
|
226
|
+
type(return_type)
|
227
|
+
description(desc)
|
228
|
+
|
229
|
+
resolve ->(t, a, c) {data}
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
SourceFieldDefn = Proc.new {
|
235
|
+
type GraphQL::ListType.new(of_type: CheeseType)
|
236
|
+
description "Cheese from source"
|
237
|
+
argument :source, DairyAnimalEnum, default_value: 1
|
238
|
+
resolve ->(target, arguments, context) {
|
239
|
+
CHEESES.values.select{ |c| c.source == arguments["source"] }
|
240
|
+
}
|
241
|
+
}
|
242
|
+
|
243
|
+
FavoriteFieldDefn = GraphQL::Field.define do
|
244
|
+
name "favoriteEdible"
|
245
|
+
description "My favorite food"
|
246
|
+
type EdibleInterface
|
247
|
+
resolve ->(t, a, c) { MILKS[1] }
|
248
|
+
end
|
249
|
+
|
250
|
+
DairyAppQueryType = GraphQL::ObjectType.define do
|
251
|
+
name "Query"
|
252
|
+
description "Query root of the system"
|
253
|
+
field :root, types.String do
|
254
|
+
resolve ->(root_value, args, c) { root_value }
|
255
|
+
end
|
256
|
+
field :cheese, field: FetchField.create(type: CheeseType, data: CHEESES)
|
257
|
+
field :milk, field: FetchField.create(type: MilkType, data: MILKS, id_type: !types.ID)
|
258
|
+
field :dairy, field: SingletonField.create(type: DairyType, data: DAIRY)
|
259
|
+
field :fromSource, &SourceFieldDefn
|
260
|
+
field :favoriteEdible, FavoriteFieldDefn
|
261
|
+
field :cow, field: SingletonField.create(type: CowType, data: COW)
|
262
|
+
field :searchDairy do
|
263
|
+
description "Find dairy products matching a description"
|
264
|
+
type !DairyProductUnion
|
265
|
+
# This is a list just for testing 😬
|
266
|
+
argument :product, types[DairyProductInputType], default_value: [{"source" => "SHEEP"}]
|
267
|
+
resolve ->(t, args, c) {
|
268
|
+
source = args["product"][0][:source] # String or Sym is ok
|
269
|
+
products = CHEESES.values + MILKS.values
|
270
|
+
if !source.nil?
|
271
|
+
products = products.select { |pr| pr.source == source }
|
272
|
+
end
|
273
|
+
products.first
|
274
|
+
}
|
275
|
+
end
|
276
|
+
|
277
|
+
field :allDairy, types[DairyProductUnion] do
|
278
|
+
argument :executionErrorAtIndex, types.Int
|
279
|
+
resolve ->(obj, args, ctx) {
|
280
|
+
result = CHEESES.values + MILKS.values
|
281
|
+
result[args[:executionErrorAtIndex]] = GraphQL::ExecutionError.new("missing dairy") if args[:executionErrorAtIndex]
|
282
|
+
result
|
283
|
+
}
|
284
|
+
end
|
285
|
+
|
286
|
+
field :allEdible, types[EdibleInterface] do
|
287
|
+
resolve ->(obj, args, ctx) { CHEESES.values + MILKS.values }
|
288
|
+
end
|
289
|
+
|
290
|
+
field :error do
|
291
|
+
description "Raise an error"
|
292
|
+
type GraphQL::STRING_TYPE
|
293
|
+
resolve ->(t, a, c) { raise("This error was raised on purpose") }
|
294
|
+
end
|
295
|
+
|
296
|
+
field :executionError do
|
297
|
+
type GraphQL::STRING_TYPE
|
298
|
+
resolve ->(t, a, c) { raise(GraphQL::ExecutionError, "There was an execution error") }
|
299
|
+
end
|
300
|
+
|
301
|
+
field :valueWithExecutionError do
|
302
|
+
type !GraphQL::INT_TYPE
|
303
|
+
resolve ->(t, a, c) {
|
304
|
+
c.add_error(GraphQL::ExecutionError.new("Could not fetch latest value"))
|
305
|
+
return 0
|
306
|
+
}
|
307
|
+
end
|
308
|
+
|
309
|
+
# To test possibly-null fields
|
310
|
+
field :maybeNull, MaybeNullType do
|
311
|
+
resolve ->(t, a, c) { OpenStruct.new(cheese: nil) }
|
312
|
+
end
|
313
|
+
|
314
|
+
field :deepNonNull, !DeepNonNullType do
|
315
|
+
resolve ->(o, a, c) { :deepNonNull }
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
GLOBAL_VALUES = []
|
320
|
+
|
321
|
+
ReplaceValuesInputType = GraphQL::InputObjectType.define do
|
322
|
+
name "ReplaceValuesInput"
|
323
|
+
input_field :values, !types[!types.Int]
|
324
|
+
end
|
325
|
+
|
326
|
+
DairyAppMutationType = GraphQL::ObjectType.define do
|
327
|
+
name "Mutation"
|
328
|
+
description "The root for mutations in this schema"
|
329
|
+
field :pushValue, !types[!types.Int] do
|
330
|
+
description("Push a value onto a global array :D")
|
331
|
+
argument :value, !types.Int
|
332
|
+
resolve ->(o, args, ctx) {
|
333
|
+
GLOBAL_VALUES << args[:value]
|
334
|
+
GLOBAL_VALUES
|
335
|
+
}
|
336
|
+
end
|
337
|
+
|
338
|
+
field :replaceValues, !types[!types.Int] do
|
339
|
+
description("Replace the global array with new values")
|
340
|
+
argument :input, !ReplaceValuesInputType
|
341
|
+
resolve ->(o, args, ctx) {
|
342
|
+
GLOBAL_VALUES.clear
|
343
|
+
GLOBAL_VALUES.push(*args[:input][:values])
|
344
|
+
GLOBAL_VALUES
|
345
|
+
}
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
SubscriptionType = GraphQL::ObjectType.define do
|
350
|
+
name "Subscription"
|
351
|
+
field :test, types.String do
|
352
|
+
resolve ->(o, a, c) { "Test" }
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
Schema = GraphQL::Schema.define do
|
357
|
+
query DairyAppQueryType
|
358
|
+
mutation DairyAppMutationType
|
359
|
+
subscription SubscriptionType
|
360
|
+
max_depth 5
|
361
|
+
orphan_types [HoneyType, BeverageUnion]
|
362
|
+
|
363
|
+
rescue_from(NoSuchDairyError) { |err| err.message }
|
364
|
+
|
365
|
+
resolve_type ->(obj, ctx) {
|
366
|
+
Schema.types[obj.class.name.split("::").last]
|
367
|
+
}
|
368
|
+
end
|
369
|
+
end
|