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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/introspection/schema_type.rb +3 -3
  3. data/lib/graphql/language/nodes.rb +1 -1
  4. data/lib/graphql/query.rb +39 -27
  5. data/lib/graphql/query/executor.rb +1 -1
  6. data/lib/graphql/query/variables.rb +21 -28
  7. data/lib/graphql/relay/connection_type.rb +2 -0
  8. data/lib/graphql/relay/relation_connection.rb +8 -2
  9. data/lib/graphql/schema.rb +3 -0
  10. data/lib/graphql/schema/warden.rb +9 -0
  11. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  12. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
  13. data/lib/graphql/version.rb +1 -1
  14. data/spec/graphql/analysis/analyze_query_spec.rb +15 -15
  15. data/spec/graphql/analysis/field_usage_spec.rb +1 -1
  16. data/spec/graphql/analysis/max_query_complexity_spec.rb +8 -8
  17. data/spec/graphql/analysis/max_query_depth_spec.rb +7 -7
  18. data/spec/graphql/analysis/query_complexity_spec.rb +2 -2
  19. data/spec/graphql/analysis/query_depth_spec.rb +1 -1
  20. data/spec/graphql/base_type_spec.rb +19 -11
  21. data/spec/graphql/directive_spec.rb +1 -1
  22. data/spec/graphql/enum_type_spec.rb +2 -2
  23. data/spec/graphql/execution/typecast_spec.rb +19 -19
  24. data/spec/graphql/execution_error_spec.rb +1 -1
  25. data/spec/graphql/field_spec.rb +15 -7
  26. data/spec/graphql/id_type_spec.rb +1 -1
  27. data/spec/graphql/input_object_type_spec.rb +16 -16
  28. data/spec/graphql/interface_type_spec.rb +6 -6
  29. data/spec/graphql/internal_representation/rewrite_spec.rb +34 -34
  30. data/spec/graphql/introspection/directive_type_spec.rb +1 -1
  31. data/spec/graphql/introspection/input_value_type_spec.rb +2 -2
  32. data/spec/graphql/introspection/introspection_query_spec.rb +1 -1
  33. data/spec/graphql/introspection/schema_type_spec.rb +2 -2
  34. data/spec/graphql/introspection/type_type_spec.rb +1 -1
  35. data/spec/graphql/language/parser_spec.rb +1 -1
  36. data/spec/graphql/non_null_type_spec.rb +3 -3
  37. data/spec/graphql/object_type_spec.rb +8 -8
  38. data/spec/graphql/query/executor_spec.rb +4 -4
  39. data/spec/graphql/query/variables_spec.rb +20 -4
  40. data/spec/graphql/query_spec.rb +20 -2
  41. data/spec/graphql/relay/connection_type_spec.rb +1 -1
  42. data/spec/graphql/relay/mutation_spec.rb +9 -9
  43. data/spec/graphql/relay/node_spec.rb +8 -8
  44. data/spec/graphql/relay/relation_connection_spec.rb +24 -6
  45. data/spec/graphql/schema/catchall_middleware_spec.rb +3 -3
  46. data/spec/graphql/schema/reduce_types_spec.rb +9 -9
  47. data/spec/graphql/schema/type_expression_spec.rb +3 -3
  48. data/spec/graphql/schema/validation_spec.rb +1 -1
  49. data/spec/graphql/schema/warden_spec.rb +79 -0
  50. data/spec/graphql/schema_spec.rb +2 -2
  51. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +1 -1
  52. data/spec/graphql/static_validation/type_stack_spec.rb +2 -2
  53. data/spec/graphql/static_validation/validator_spec.rb +2 -2
  54. data/spec/graphql/union_type_spec.rb +2 -2
  55. data/spec/spec_helper.rb +1 -1
  56. data/spec/support/dummy/data.rb +27 -0
  57. data/spec/support/dummy/schema.rb +369 -0
  58. data/spec/support/star_wars/data.rb +81 -0
  59. data/spec/support/star_wars/schema.rb +250 -0
  60. data/spec/support/static_validation_helpers.rb +2 -2
  61. metadata +10 -10
  62. data/spec/support/dairy_app.rb +0 -369
  63. data/spec/support/dairy_data.rb +0 -26
  64. data/spec/support/star_wars_data.rb +0 -80
  65. 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
- StarWarsSchema.types
16
+ StarWars::Schema.types
17
17
 
18
- @previous_id_from_object_proc = StarWarsSchema.id_from_object_proc
19
- @previous_object_from_id_proc = StarWarsSchema.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
- StarWarsSchema.id_from_object = ->(obj, type_name, ctx) {
21
+ StarWars::Schema.id_from_object = ->(obj, type_name, ctx) {
22
22
  "#{type_name}/#{obj.id}"
23
23
  }
24
24
 
25
- StarWarsSchema.object_from_id = ->(global_id, ctx) {
25
+ StarWars::Schema.object_from_id = ->(global_id, ctx) {
26
26
  type_name, id = global_id.split("/")
27
- STAR_WARS_DATA[type_name][id]
27
+ StarWars::DATA[type_name][id]
28
28
  }
29
29
  end
30
30
 
31
31
  after do
32
- StarWarsSchema.id_from_object = @previous_id_from_object_proc
33
- StarWarsSchema.object_from_id = @previous_object_from_id_proc
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) { DummySchema.execute(query_string) }
5
+ let(:result) { Dummy::Schema.execute(query_string) }
6
6
  let(:query_string) {%| query noMilk { error }|}
7
7
 
8
8
  before do
9
- DummySchema.middleware << GraphQL::Schema::CatchallMiddleware
9
+ Dummy::Schema.middleware << GraphQL::Schema::CatchallMiddleware
10
10
  end
11
11
 
12
12
  after do
13
- DummySchema.middleware.delete(GraphQL::Schema::CatchallMiddleware)
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, DummySchema.types["Honey"]
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) { DummySchema.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
@@ -246,7 +246,7 @@ describe GraphQL::Schema::Validation do
246
246
  let(:null_default_value) {
247
247
  GraphQL::Argument.define do
248
248
  name "NullDefault"
249
- type DairyAnimalEnum
249
+ type Dummy::DairyAnimalEnum
250
250
  default_value nil
251
251
  end
252
252
  }
@@ -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
@@ -2,8 +2,8 @@
2
2
  require "spec_helper"
3
3
 
4
4
  describe GraphQL::Schema do
5
- let(:schema) { DummySchema }
6
- let(:relay_schema) { StarWarsSchema }
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) { DummySchema.execute(query_string) }
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: DummySchema, rules: [TypeCheckValidator]) }
26
- let(:query) { GraphQL::Query.new(DummySchema, query_string) }
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: DummySchema) }
6
- let(:query) { GraphQL::Query.new(DummySchema, query_string) }
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) { DummySchema.execute(query_string) }
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) { DummySchema.execute(query_string) }
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(StarWarsSchema, string, variables: variables).result
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