graphql 1.4.5 → 1.5.3

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 (139) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/enum_generator.rb +33 -0
  3. data/lib/generators/graphql/function_generator.rb +15 -0
  4. data/lib/generators/graphql/install_generator.rb +118 -0
  5. data/lib/generators/graphql/interface_generator.rb +27 -0
  6. data/lib/generators/graphql/loader_generator.rb +17 -0
  7. data/lib/generators/graphql/mutation_generator.rb +19 -0
  8. data/lib/generators/graphql/object_generator.rb +34 -0
  9. data/lib/generators/graphql/templates/enum.erb +4 -0
  10. data/lib/generators/graphql/templates/function.erb +17 -0
  11. data/lib/generators/graphql/templates/graphql_controller.erb +32 -0
  12. data/lib/generators/graphql/templates/interface.erb +4 -0
  13. data/lib/generators/graphql/templates/loader.erb +15 -0
  14. data/lib/generators/graphql/templates/mutation.erb +12 -0
  15. data/lib/generators/graphql/templates/object.erb +5 -0
  16. data/lib/generators/graphql/templates/query_type.erb +15 -0
  17. data/lib/generators/graphql/templates/schema.erb +34 -0
  18. data/lib/generators/graphql/templates/union.erb +4 -0
  19. data/lib/generators/graphql/type_generator.rb +78 -0
  20. data/lib/generators/graphql/union_generator.rb +33 -0
  21. data/lib/graphql.rb +10 -0
  22. data/lib/graphql/analysis/analyze_query.rb +1 -1
  23. data/lib/graphql/analysis/query_complexity.rb +6 -50
  24. data/lib/graphql/analysis/query_depth.rb +1 -1
  25. data/lib/graphql/argument.rb +21 -0
  26. data/lib/graphql/compatibility/execution_specification/counter_schema.rb +3 -3
  27. data/lib/graphql/define.rb +1 -0
  28. data/lib/graphql/define/assign_argument.rb +3 -19
  29. data/lib/graphql/define/assign_mutation_function.rb +34 -0
  30. data/lib/graphql/define/assign_object_field.rb +26 -14
  31. data/lib/graphql/define/defined_object_proxy.rb +21 -0
  32. data/lib/graphql/define/instance_definable.rb +61 -11
  33. data/lib/graphql/directive.rb +6 -1
  34. data/lib/graphql/execution/directive_checks.rb +1 -0
  35. data/lib/graphql/execution/execute.rb +14 -9
  36. data/lib/graphql/execution/field_result.rb +1 -0
  37. data/lib/graphql/execution/lazy.rb +8 -17
  38. data/lib/graphql/execution/lazy/lazy_method_map.rb +2 -0
  39. data/lib/graphql/execution/lazy/resolve.rb +1 -0
  40. data/lib/graphql/execution/selection_result.rb +1 -0
  41. data/lib/graphql/execution/typecast.rb +39 -26
  42. data/lib/graphql/field.rb +15 -3
  43. data/lib/graphql/field/resolve.rb +3 -3
  44. data/lib/graphql/function.rb +134 -0
  45. data/lib/graphql/id_type.rb +1 -1
  46. data/lib/graphql/input_object_type.rb +1 -1
  47. data/lib/graphql/internal_representation.rb +1 -1
  48. data/lib/graphql/internal_representation/node.rb +35 -107
  49. data/lib/graphql/internal_representation/rewrite.rb +189 -183
  50. data/lib/graphql/internal_representation/visit.rb +38 -0
  51. data/lib/graphql/introspection/input_value_type.rb +10 -1
  52. data/lib/graphql/introspection/schema_type.rb +1 -1
  53. data/lib/graphql/language/lexer.rb +6 -3
  54. data/lib/graphql/language/lexer.rl +6 -3
  55. data/lib/graphql/object_type.rb +53 -13
  56. data/lib/graphql/query.rb +30 -14
  57. data/lib/graphql/query/arguments.rb +2 -0
  58. data/lib/graphql/query/context.rb +2 -2
  59. data/lib/graphql/query/literal_input.rb +9 -0
  60. data/lib/graphql/query/serial_execution/field_resolution.rb +2 -2
  61. data/lib/graphql/query/serial_execution/selection_resolution.rb +1 -1
  62. data/lib/graphql/relay.rb +1 -0
  63. data/lib/graphql/relay/array_connection.rb +1 -1
  64. data/lib/graphql/relay/base_connection.rb +34 -15
  65. data/lib/graphql/relay/connection_resolve.rb +7 -2
  66. data/lib/graphql/relay/mutation.rb +45 -4
  67. data/lib/graphql/relay/node.rb +18 -6
  68. data/lib/graphql/relay/range_add.rb +45 -0
  69. data/lib/graphql/relay/relation_connection.rb +17 -2
  70. data/lib/graphql/runtime_type_error.rb +1 -0
  71. data/lib/graphql/schema.rb +40 -5
  72. data/lib/graphql/schema/base_64_encoder.rb +1 -0
  73. data/lib/graphql/schema/build_from_definition.rb +56 -21
  74. data/lib/graphql/schema/default_parse_error.rb +10 -0
  75. data/lib/graphql/schema/loader.rb +8 -1
  76. data/lib/graphql/schema/null_mask.rb +1 -0
  77. data/lib/graphql/schema/validation.rb +35 -0
  78. data/lib/graphql/static_validation.rb +1 -0
  79. data/lib/graphql/static_validation/all_rules.rb +1 -0
  80. data/lib/graphql/static_validation/arguments_validator.rb +7 -4
  81. data/lib/graphql/static_validation/definition_dependencies.rb +183 -0
  82. data/lib/graphql/static_validation/rules/fields_will_merge.rb +28 -96
  83. data/lib/graphql/static_validation/rules/fragment_names_are_unique.rb +23 -0
  84. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +8 -5
  85. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +6 -31
  86. data/lib/graphql/static_validation/rules/fragments_are_used.rb +11 -41
  87. data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +2 -2
  88. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +19 -7
  89. data/lib/graphql/static_validation/validation_context.rb +22 -1
  90. data/lib/graphql/static_validation/validator.rb +4 -1
  91. data/lib/graphql/string_type.rb +5 -1
  92. data/lib/graphql/version.rb +1 -1
  93. data/readme.md +12 -3
  94. data/spec/generators/graphql/enum_generator_spec.rb +29 -0
  95. data/spec/generators/graphql/function_generator_spec.rb +33 -0
  96. data/spec/generators/graphql/install_generator_spec.rb +185 -0
  97. data/spec/generators/graphql/interface_generator_spec.rb +32 -0
  98. data/spec/generators/graphql/loader_generator_spec.rb +31 -0
  99. data/spec/generators/graphql/mutation_generator_spec.rb +28 -0
  100. data/spec/generators/graphql/object_generator_spec.rb +42 -0
  101. data/spec/generators/graphql/union_generator_spec.rb +50 -0
  102. data/spec/graphql/analysis/query_complexity_spec.rb +2 -1
  103. data/spec/graphql/define/instance_definable_spec.rb +38 -0
  104. data/spec/graphql/directive/skip_directive_spec.rb +1 -0
  105. data/spec/graphql/directive_spec.rb +18 -0
  106. data/spec/graphql/execution/typecast_spec.rb +41 -46
  107. data/spec/graphql/field_spec.rb +1 -1
  108. data/spec/graphql/function_spec.rb +128 -0
  109. data/spec/graphql/internal_representation/rewrite_spec.rb +166 -129
  110. data/spec/graphql/introspection/type_type_spec.rb +1 -1
  111. data/spec/graphql/language/lexer_spec.rb +6 -0
  112. data/spec/graphql/object_type_spec.rb +73 -2
  113. data/spec/graphql/query/arguments_spec.rb +28 -0
  114. data/spec/graphql/query/variables_spec.rb +7 -1
  115. data/spec/graphql/query_spec.rb +30 -0
  116. data/spec/graphql/relay/base_connection_spec.rb +26 -8
  117. data/spec/graphql/relay/connection_resolve_spec.rb +45 -0
  118. data/spec/graphql/relay/connection_type_spec.rb +21 -0
  119. data/spec/graphql/relay/node_spec.rb +30 -2
  120. data/spec/graphql/relay/range_add_spec.rb +113 -0
  121. data/spec/graphql/schema/build_from_definition_spec.rb +114 -0
  122. data/spec/graphql/schema/loader_spec.rb +1 -0
  123. data/spec/graphql/schema/printer_spec.rb +2 -2
  124. data/spec/graphql/schema/validation_spec.rb +80 -11
  125. data/spec/graphql/schema/warden_spec.rb +10 -10
  126. data/spec/graphql/schema_spec.rb +18 -1
  127. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +16 -0
  128. data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +50 -3
  129. data/spec/graphql/static_validation/rules/fragment_names_are_unique_spec.rb +27 -0
  130. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +57 -0
  131. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +1 -1
  132. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +14 -0
  133. data/spec/graphql/string_type_spec.rb +7 -0
  134. data/spec/spec_helper.rb +3 -3
  135. data/spec/support/base_generator_test.rb +7 -0
  136. data/spec/support/dummy/schema.rb +32 -30
  137. data/spec/support/star_wars/schema.rb +81 -23
  138. metadata +98 -20
  139. data/lib/graphql/internal_representation/selection.rb +0 -85
@@ -138,6 +138,26 @@ describe GraphQL::Query::Arguments do
138
138
  1
139
139
  }
140
140
  end
141
+
142
+ field :noArgTest, types.Int do
143
+ resolve ->(obj, args, ctx) {
144
+ arg_values_array << args
145
+ 1
146
+ }
147
+ end
148
+
149
+ field :noDefaultsTest, types.Int do
150
+ argument :a, types.Int
151
+ argument :b, types.Int
152
+ resolve ->(obj, args, ctx) {
153
+ arg_values_array << args
154
+ 1
155
+ }
156
+ resolve ->(obj, args, ctx) {
157
+ arg_values_array << args
158
+ 1
159
+ }
160
+ end
141
161
  end
142
162
 
143
163
  GraphQL::Schema.define(query: query)
@@ -170,6 +190,14 @@ describe GraphQL::Query::Arguments do
170
190
  assert_equal({"a" => 1, "b" => 2}, last_args.to_h)
171
191
  end
172
192
 
193
+ it "uses Field#default_arguments when no args are provided" do
194
+ schema.execute("{ argTest noArgTest noDefaultsTest }")
195
+
196
+ assert schema.query.get_field("argTest").default_arguments.eql?(arg_values[0])
197
+ assert GraphQL::Query::Arguments::NO_ARGS.eql?(arg_values[1])
198
+ assert GraphQL::Query::Arguments::NO_ARGS.eql?(arg_values[2])
199
+ end
200
+
173
201
  it "works from variables" do
174
202
  variables = { "arg" => { "a" => 1, "d" => nil } }
175
203
  schema.execute("query ArgTest($arg: TestInput){ argTest(d: $arg) }", variables: variables)
@@ -33,6 +33,12 @@ describe GraphQL::Query::Variables do
33
33
  end
34
34
 
35
35
  describe "nullable variables" do
36
+ module ObjectWithThingsCount
37
+ def self.thingsCount(args, ctx) # rubocop:disable Style/MethodName
38
+ 1
39
+ end
40
+ end
41
+
36
42
  let(:schema) { GraphQL::Schema.from_definition(%|
37
43
  type Query {
38
44
  thingsCount(ids: [ID!]): Int!
@@ -45,7 +51,7 @@ describe GraphQL::Query::Variables do
45
51
  }
46
52
  |}
47
53
  let(:result) {
48
- schema.execute(query_string, variables: provided_variables, root_value: OpenStruct.new(thingsCount: 1))
54
+ schema.execute(query_string, variables: provided_variables, root_value: ObjectWithThingsCount)
49
55
  }
50
56
 
51
57
  describe "when they are present, but null" do
@@ -420,4 +420,34 @@ describe GraphQL::Query do
420
420
  assert_equal({"cheeseId" => 2}, query.provided_variables)
421
421
  end
422
422
  end
423
+
424
+ describe "parse errors" do
425
+ let(:invalid_query_string) {
426
+ <<-GRAPHQL
427
+ {
428
+ getStuff
429
+ nonsense
430
+ This is broken 1
431
+ }
432
+ GRAPHQL
433
+ }
434
+ it "adds an entry to the errors key" do
435
+ res = schema.execute(" { ")
436
+ assert_equal 1, res["errors"].length
437
+ assert_equal "Unexpected end of document", res["errors"][0]["message"]
438
+ assert_equal [], res["errors"][0]["locations"]
439
+
440
+ res = schema.execute(invalid_query_string)
441
+ assert_equal 1, res["errors"].length
442
+ assert_equal %|Parse error on "1" (INT) at [4, 26]|, res["errors"][0]["message"]
443
+ assert_equal({"line" => 4, "column" => 26}, res["errors"][0]["locations"][0])
444
+ end
445
+
446
+ it "can be configured to raise" do
447
+ raise_schema = schema.redefine(parse_error: ->(err, ctx) { raise err })
448
+ assert_raises(GraphQL::ParseError) {
449
+ raise_schema.execute(invalid_query_string)
450
+ }
451
+ end
452
+ end
423
453
  end
@@ -2,6 +2,15 @@
2
2
  require 'spec_helper'
3
3
 
4
4
  describe GraphQL::Relay::BaseConnection do
5
+ module Encoder
6
+ module_function
7
+ def encode(str, nonce: false); str; end
8
+ def decode(str, nonce: false); str; end
9
+ end
10
+
11
+ let(:schema) { OpenStruct.new(cursor_encoder: Encoder) }
12
+ let(:context) { OpenStruct.new(schema: schema) }
13
+
5
14
  describe ".connection_for_nodes" do
6
15
  it "resolves most specific connection type" do
7
16
  class SpecialArray < Array; end
@@ -15,16 +24,25 @@ describe GraphQL::Relay::BaseConnection do
15
24
  end
16
25
  end
17
26
 
18
- describe "#context" do
19
- module Encoder
20
- module_function
21
- def encode(str, nonce: false); str; end
22
- def decode(str, nonce: false); str; end
23
- end
27
+ describe "arguments" do
28
+ it "limits pagination args to positive numbers" do
29
+ args = {
30
+ first: 1,
31
+ last: -1,
32
+ }
33
+ conn = GraphQL::Relay::BaseConnection.new([], args, context: context)
34
+ assert_equal 1, conn.first
35
+ assert_equal 0, conn.last
24
36
 
25
- let(:schema) { OpenStruct.new(cursor_encoder: Encoder) }
26
- let(:context) { OpenStruct.new(schema: schema) }
37
+ args = {
38
+ first: nil,
39
+ }
40
+ conn = GraphQL::Relay::BaseConnection.new([], args, context: context)
41
+ assert_equal nil, conn.first
42
+ end
43
+ end
27
44
 
45
+ describe "#context" do
28
46
  it "Has public access to the field context" do
29
47
  conn = GraphQL::Relay::BaseConnection.new([], {}, context: context)
30
48
  assert_equal context, conn.context
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ describe GraphQL::Relay::ConnectionResolve do
5
+ describe "when an execution error is returned" do
6
+ let(:query_string) { <<-GRAPHQL
7
+ query getError($error: String!){
8
+ rebels {
9
+ ships(nameIncludes: $error) {
10
+ edges {
11
+ node {
12
+ name
13
+ }
14
+ }
15
+ }
16
+ }
17
+ }
18
+ GRAPHQL
19
+ }
20
+
21
+ it "adds an error" do
22
+ result = star_wars_query(query_string, { "error" => "error"})
23
+ assert_equal 1, result["errors"].length
24
+ assert_equal "error from within connection", result["errors"][0]["message"]
25
+ end
26
+
27
+ it "adds an error for a lazy error" do
28
+ result = star_wars_query(query_string, { "error" => "lazyError"})
29
+ assert_equal 1, result["errors"].length
30
+ assert_equal "lazy error from within connection", result["errors"][0]["message"]
31
+ end
32
+
33
+ it "adds an error for a lazy raised error" do
34
+ result = star_wars_query(query_string, { "error" => "lazyRaisedError"})
35
+ assert_equal 1, result["errors"].length
36
+ assert_equal "lazy raised error from within connection", result["errors"][0]["message"]
37
+ end
38
+
39
+ it "adds an error for a raised error" do
40
+ result = star_wars_query(query_string, { "error" => "raisedError"})
41
+ assert_equal 1, result["errors"].length
42
+ assert_equal "error raised from within connection", result["errors"][0]["message"]
43
+ end
44
+ end
45
+ end
@@ -56,5 +56,26 @@ describe GraphQL::Relay::ConnectionType do
56
56
  assert_equal ["Yavin", "Echo Base", "Secret Hideout"] , bases["nodes"].map { |e| e["name"] }
57
57
  end
58
58
  end
59
+
60
+
61
+ describe "when an execution error is raised" do
62
+ let(:query_string) {%|
63
+ {
64
+ basesWithNullName {
65
+ edges {
66
+ node {
67
+ name
68
+ }
69
+ }
70
+ }
71
+ }
72
+ |}
73
+
74
+ it "nullifies the parent and adds an error" do
75
+ result = star_wars_query(query_string)
76
+ assert_equal nil, result["data"]["basesWithNullName"]["edges"][0]["node"]
77
+ assert_equal "Boom!", result["errors"][0]["message"]
78
+ end
79
+ end
59
80
  end
60
81
  end
@@ -9,7 +9,21 @@ describe GraphQL::Relay::Node do
9
9
  end
10
10
 
11
11
  describe ".field" do
12
- describe "with custom resolver" do
12
+ describe "with custom definition" do
13
+ it 'creates a field with the custom definition' do
14
+ faction = StarWars::DATA['Faction'][0]
15
+
16
+ node_field = GraphQL::Relay::Node.field do
17
+ name "nod3"
18
+ description "The Relay Node Field"
19
+ resolve ->(_, _ , _) { faction }
20
+ end
21
+
22
+ assert_equal "nod3", node_field.name
23
+ assert_equal "The Relay Node Field", node_field.description
24
+ assert_equal faction, node_field.resolve(nil, { 'id' => '1' }, nil)
25
+ end
26
+
13
27
  it "executes the custom resolve instead of relay default" do
14
28
  id = "resolver_is_hardcoded_so_this_does_not_matter"
15
29
 
@@ -131,7 +145,21 @@ describe GraphQL::Relay::Node do
131
145
  end
132
146
 
133
147
  describe ".plural_identifying_field" do
134
- describe "with custom resolver" do
148
+ describe "with custom definition" do
149
+ it 'creates a field with the custom definition' do
150
+ factions = StarWars::DATA['Faction']
151
+
152
+ node_field = GraphQL::Relay::Node.plural_field do
153
+ name "nodez"
154
+ description "The Relay Nodes Field"
155
+ resolve ->(_, _ , _) { factions }
156
+ end
157
+
158
+ assert_equal "nodez", node_field.name
159
+ assert_equal "The Relay Nodes Field", node_field.description
160
+ assert_equal factions, node_field.resolve_proc.call(nil, { 'ids' => ['1', '2'] }, nil)
161
+ end
162
+
135
163
  it "executes the custom resolve instead of relay default" do
136
164
  id = ["resolver_is_hardcoded_so_this_does_not_matter", "another_id"]
137
165
 
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ describe GraphQL::Relay::RangeAdd do
5
+ # Make sure that the encoder is found through `ctx.schema`:
6
+ module PassThroughEncoder
7
+ def self.encode(plaintext, nonce: false)
8
+ "__#{plaintext}"
9
+ end
10
+
11
+ def self.decode(ciphertext, nonce: false)
12
+ ciphertext[2..-1]
13
+ end
14
+ end
15
+
16
+ let(:schema) {
17
+ menus = [
18
+ OpenStruct.new(
19
+ name: "Los Primos",
20
+ items: [
21
+ OpenStruct.new(name: "California Burrito", price: 699),
22
+ OpenStruct.new(name: "Fish Taco", price: 399),
23
+ ]
24
+ )
25
+ ]
26
+
27
+ item = GraphQL::ObjectType.define do
28
+ name "Item"
29
+ field :price, !types.Int
30
+ field :name, !types.String
31
+ end
32
+ menu = GraphQL::ObjectType.define do
33
+ name "Menu"
34
+ field :name, !types.String
35
+ field :items, !item.connection_type
36
+ end
37
+ query = GraphQL::ObjectType.define do
38
+ name "Query"
39
+ field :menus, types[menu], resolve: Proc.new { menus }
40
+ end
41
+ add_item = GraphQL::Relay::Mutation.define do
42
+ name "AddItem"
43
+ input_field :name, !types.String
44
+ input_field :price, !types.Int
45
+ input_field :menu_idx, !types.Int
46
+
47
+ return_field :item_edge, item.edge_type
48
+ return_field :items, item.connection_type
49
+ return_field :menu, menu
50
+ resolve ->(obj, input, ctx) {
51
+ this_menu = menus[input[:menu_idx]]
52
+ new_item = OpenStruct.new(name: input[:name], price: input[:price])
53
+ this_menu.items << new_item
54
+ range_add = GraphQL::Relay::RangeAdd.new(
55
+ parent: this_menu,
56
+ item: new_item,
57
+ collection: this_menu.items,
58
+ context: ctx,
59
+ )
60
+
61
+ {
62
+ menu: range_add.parent,
63
+ items: range_add.connection,
64
+ item_edge: range_add.edge,
65
+ }
66
+ }
67
+ end
68
+ mutation = GraphQL::ObjectType.define do
69
+ name "Mutation"
70
+ field :add_item, add_item.field
71
+ end
72
+
73
+ GraphQL::Schema.define(query: query, mutation: mutation, cursor_encoder: PassThroughEncoder)
74
+ }
75
+
76
+
77
+ describe "returning Relay objects" do
78
+ let(:query_str) { <<-GRAPHQL
79
+ mutation {
80
+ add_item(input: {name: "Chilaquiles", price: 699, menu_idx: 0}) {
81
+ menu {
82
+ name
83
+ }
84
+ item_edge {
85
+ node {
86
+ name
87
+ price
88
+ }
89
+ }
90
+ items {
91
+ edges {
92
+ node {
93
+ name
94
+ }
95
+ cursor
96
+ }
97
+ }
98
+ }
99
+ }
100
+ GRAPHQL
101
+ }
102
+
103
+ it "returns a connection and an edge" do
104
+ res = schema.execute(query_str)
105
+
106
+ mutation_res = res["data"]["add_item"]
107
+ assert_equal("Los Primos", mutation_res["menu"]["name"])
108
+ assert_equal({"name"=>"Chilaquiles", "price"=>699}, mutation_res["item_edge"]["node"])
109
+ assert_equal(["California Burrito", "Fish Taco", "Chilaquiles"], mutation_res["items"]["edges"].map { |e| e["node"]["name"] })
110
+ assert_equal(["__1", "__2", "__3"], mutation_res["items"]["edges"].map { |e| e["cursor"] })
111
+ end
112
+ end
113
+ end
@@ -700,4 +700,118 @@ SCHEMA
700
700
  assert_equal 'Specified query type "Foo" not found in document.', err.message
701
701
  end
702
702
  end
703
+
704
+ describe "executable schemas from string" do
705
+ let(:schema_defn) {
706
+ <<-GRAPHQL
707
+ type Todo {text: String, from_context: String}
708
+ type Query { all_todos: [Todo]}
709
+ type Mutation { todo_add(text: String!): Todo}
710
+ GRAPHQL
711
+ }
712
+
713
+ Todo = Struct.new(:text, :from_context)
714
+
715
+ class RootResolver
716
+ attr_accessor :todos
717
+
718
+ def initialize
719
+ @todos = [Todo.new("Pay the bills.")]
720
+ end
721
+
722
+ def all_todos
723
+ @todos
724
+ end
725
+
726
+ def todo_add(args, ctx) # this is a method and accepting arguments
727
+ todo = Todo.new(args[:text], ctx[:context_value])
728
+ @todos << todo
729
+ todo
730
+ end
731
+ end
732
+
733
+ it "calls methods with args if args are defined" do
734
+ schema = GraphQL::Schema.from_definition(schema_defn)
735
+ root_values = RootResolver.new
736
+ schema.execute("mutation { todoAdd: todo_add(text: \"Buy Milk\") { text } }", root_value: root_values, context: {context_value: "bar"})
737
+ result = schema.execute("query { allTodos: all_todos { text, from_context } }", root_value: root_values)
738
+ assert_equal(result.to_json, '{"data":{"allTodos":[{"text":"Pay the bills.","from_context":null},{"text":"Buy Milk","from_context":"bar"}]}}')
739
+ end
740
+
741
+ describe "hash of resolvers" do
742
+ let(:todos) { [Todo.new("Pay the bills.")] }
743
+ let(:schema) { GraphQL::Schema.from_definition(schema_defn, default_resolve: resolve_hash) }
744
+ let(:resolve_hash) {
745
+ h = base_hash
746
+ h["Query"] ||= {}
747
+ h["Query"]["all_todos"] = ->(obj, args, ctx) { obj }
748
+ h["Mutation"] ||= {}
749
+ h["Mutation"]["todo_add"] = ->(obj, args, ctx) {
750
+ todo = Todo.new(args[:text], ctx[:context_value])
751
+ obj << todo
752
+ todo
753
+ }
754
+ h
755
+ }
756
+ describe "with defaults" do
757
+ let(:base_hash) {
758
+ # Fallback is to resolve by sending the field name
759
+ Hash.new { |h, k| h[k] = Hash.new { |h2, k2| ->(obj, args, ctx) { obj.public_send(k2) } } }
760
+ }
761
+
762
+ it "accepts a hash of resolve functions" do
763
+ schema.execute("mutation { todoAdd: todo_add(text: \"Buy Milk\") { text } }", context: {context_value: "bar"}, root_value: todos)
764
+ result = schema.execute("query { allTodos: all_todos { text, from_context } }", root_value: todos)
765
+ assert_equal(result.to_json, '{"data":{"allTodos":[{"text":"Pay the bills.","from_context":null},{"text":"Buy Milk","from_context":"bar"}]}}')
766
+ end
767
+ end
768
+
769
+ describe "wihtout defaults" do
770
+ let(:base_hash) { {} }
771
+ it "raises a KeyError" do
772
+ assert_raises(KeyError) do
773
+ schema.execute("mutation { todoAdd: todo_add(text: \"Buy Milk\") { text } }", context: {context_value: "bar"}, root_value: todos)
774
+ end
775
+ end
776
+ end
777
+ end
778
+
779
+ describe "custom resolve behavior" do
780
+ class AppResolver
781
+ def initialize
782
+ @todos = [Todo.new("Pay the bills.")]
783
+ @resolves = {
784
+ "Query" => {
785
+ "all_todos" => ->(obj, args, ctx) { @todos },
786
+ },
787
+ "Mutation" => {
788
+ "todo_add" => ->(obj, args, ctx) {
789
+ todo = Todo.new(args[:text], ctx[:context_value])
790
+ @todos << todo
791
+ todo
792
+ },
793
+ },
794
+ "Todo" => {
795
+ "text" => ->(obj, args, ctx) { obj.text },
796
+ "from_context" => ->(obj, args, ctx) { obj.from_context },
797
+ }
798
+ }
799
+ end
800
+
801
+ def call(type, field, obj, args, ctx)
802
+ @resolves
803
+ .fetch(type.name)
804
+ .fetch(field.name)
805
+ .call(obj, args, ctx)
806
+ end
807
+ end
808
+
809
+ it "accepts a default_resolve callable" do
810
+ schema = GraphQL::Schema.from_definition(schema_defn, default_resolve: AppResolver.new)
811
+ schema.execute("mutation { todoAdd: todo_add(text: \"Buy Milk\") { text } }", context: {context_value: "bar"})
812
+ result = schema.execute("query { allTodos: all_todos { text, from_context } }")
813
+ assert_equal(result.to_json, '{"data":{"allTodos":[{"text":"Pay the bills.","from_context":null},{"text":"Buy Milk","from_context":"bar"}]}}')
814
+ end
815
+ end
816
+ end
703
817
  end