graphql 1.4.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
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