graphql 1.1.0 → 1.2.0

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyze_query.rb +4 -2
  3. data/lib/graphql/analysis/field_usage.rb +4 -4
  4. data/lib/graphql/analysis/query_complexity.rb +16 -21
  5. data/lib/graphql/argument.rb +13 -6
  6. data/lib/graphql/base_type.rb +2 -1
  7. data/lib/graphql/compatibility/execution_specification.rb +76 -0
  8. data/lib/graphql/compatibility/query_parser_specification.rb +16 -2
  9. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +0 -5
  10. data/lib/graphql/compatibility/schema_parser_specification.rb +6 -0
  11. data/lib/graphql/define/assign_argument.rb +8 -2
  12. data/lib/graphql/define/instance_definable.rb +12 -15
  13. data/lib/graphql/directive.rb +2 -1
  14. data/lib/graphql/enum_type.rb +5 -7
  15. data/lib/graphql/field.rb +6 -11
  16. data/lib/graphql/field/resolve.rb +1 -0
  17. data/lib/graphql/input_object_type.rb +9 -9
  18. data/lib/graphql/interface_type.rb +2 -1
  19. data/lib/graphql/internal_representation.rb +1 -0
  20. data/lib/graphql/internal_representation/node.rb +31 -9
  21. data/lib/graphql/internal_representation/rewrite.rb +26 -26
  22. data/lib/graphql/internal_representation/selections.rb +41 -0
  23. data/lib/graphql/introspection/input_value_type.rb +6 -2
  24. data/lib/graphql/language/generation.rb +2 -0
  25. data/lib/graphql/language/lexer.rl +4 -0
  26. data/lib/graphql/language/nodes.rb +3 -0
  27. data/lib/graphql/language/parser.rb +525 -509
  28. data/lib/graphql/language/parser.y +2 -0
  29. data/lib/graphql/object_type.rb +2 -2
  30. data/lib/graphql/query.rb +21 -0
  31. data/lib/graphql/query/context.rb +52 -4
  32. data/lib/graphql/query/serial_execution.rb +3 -4
  33. data/lib/graphql/query/serial_execution/field_resolution.rb +35 -36
  34. data/lib/graphql/query/serial_execution/operation_resolution.rb +9 -15
  35. data/lib/graphql/query/serial_execution/selection_resolution.rb +14 -11
  36. data/lib/graphql/query/serial_execution/value_resolution.rb +18 -17
  37. data/lib/graphql/query/variables.rb +1 -1
  38. data/lib/graphql/relay/mutation.rb +5 -8
  39. data/lib/graphql/scalar_type.rb +1 -2
  40. data/lib/graphql/schema.rb +2 -13
  41. data/lib/graphql/schema/build_from_definition.rb +28 -13
  42. data/lib/graphql/schema/loader.rb +4 -1
  43. data/lib/graphql/schema/printer.rb +10 -3
  44. data/lib/graphql/schema/timeout_middleware.rb +18 -2
  45. data/lib/graphql/schema/unique_within_type.rb +6 -3
  46. data/lib/graphql/static_validation/literal_validator.rb +3 -1
  47. data/lib/graphql/union_type.rb +1 -2
  48. data/lib/graphql/version.rb +1 -1
  49. data/readme.md +1 -0
  50. data/spec/graphql/analysis/analyze_query_spec.rb +6 -8
  51. data/spec/graphql/argument_spec.rb +18 -0
  52. data/spec/graphql/define/assign_argument_spec.rb +48 -0
  53. data/spec/graphql/define/instance_definable_spec.rb +4 -2
  54. data/spec/graphql/execution_error_spec.rb +66 -0
  55. data/spec/graphql/input_object_type_spec.rb +81 -0
  56. data/spec/graphql/internal_representation/rewrite_spec.rb +104 -21
  57. data/spec/graphql/introspection/input_value_type_spec.rb +43 -6
  58. data/spec/graphql/introspection/schema_type_spec.rb +1 -0
  59. data/spec/graphql/introspection/type_type_spec.rb +2 -0
  60. data/spec/graphql/language/generation_spec.rb +3 -2
  61. data/spec/graphql/query/arguments_spec.rb +17 -4
  62. data/spec/graphql/query/context_spec.rb +23 -0
  63. data/spec/graphql/query/variables_spec.rb +15 -1
  64. data/spec/graphql/relay/mutation_spec.rb +42 -2
  65. data/spec/graphql/schema/build_from_definition_spec.rb +4 -2
  66. data/spec/graphql/schema/loader_spec.rb +59 -1
  67. data/spec/graphql/schema/printer_spec.rb +2 -0
  68. data/spec/graphql/schema/reduce_types_spec.rb +1 -1
  69. data/spec/graphql/schema/timeout_middleware_spec.rb +2 -2
  70. data/spec/graphql/schema/unique_within_type_spec.rb +9 -0
  71. data/spec/graphql/schema/validation_spec.rb +15 -3
  72. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +122 -0
  73. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +78 -0
  74. data/spec/support/dairy_app.rb +9 -0
  75. data/spec/support/minimum_input_object.rb +4 -0
  76. data/spec/support/star_wars_schema.rb +1 -1
  77. metadata +5 -5
  78. data/lib/graphql/query/serial_execution/execution_context.rb +0 -37
  79. data/spec/graphql/query/serial_execution/execution_context_spec.rb +0 -54
@@ -32,6 +32,7 @@ describe GraphQL::Introspection::SchemaType do
32
32
  {"name"=>"milk"},
33
33
  {"name"=>"root"},
34
34
  {"name"=>"searchDairy"},
35
+ {"name"=>"valueWithExecutionError"},
35
36
  ]
36
37
  },
37
38
  "mutationType"=> {
@@ -18,6 +18,7 @@ describe GraphQL::Introspection::TypeType do
18
18
  {"name"=>"id", "isDeprecated" => false, "type" => { "kind" => "NON_NULL", "name" => nil, "ofType" => { "name" => "Int"}}},
19
19
  {"name"=>"nullableCheese", "isDeprecated"=>false, "type"=>{ "kind" => "OBJECT", "name" => "Cheese", "ofType"=>nil}},
20
20
  {"name"=>"origin", "isDeprecated" => false, "type" => { "kind" => "NON_NULL", "name" => nil, "ofType" => { "name" => "String"}}},
21
+ {"name"=>"selfAsEdible", "isDeprecated"=>false, "type"=>{"kind"=>"INTERFACE", "name"=>"Edible", "ofType"=>nil}},
21
22
  {"name"=>"similarCheese", "isDeprecated"=>false, "type"=>{ "kind" => "OBJECT", "name"=>"Cheese", "ofType"=>nil}},
22
23
  {"name"=>"source", "isDeprecated" => false, "type" => { "kind" => "NON_NULL", "name" => nil, "ofType" => { "name" => "DairyAnimal"}}},
23
24
  ]}
@@ -49,6 +50,7 @@ describe GraphQL::Introspection::TypeType do
49
50
  {"type"=>{"kind"=>"LIST","name"=>nil, "ofType"=>{"name"=>"String"}}},
50
51
  {"type"=>{"kind"=>"NON_NULL","name"=>nil, "ofType"=>{"name"=>"ID"}}},
51
52
  {"type"=>{"kind"=>"NON_NULL","name"=>nil, "ofType"=>{"name"=>"String"}}},
53
+ {"type"=>{"kind"=>"INTERFACE", "name"=>"Edible", "ofType"=>nil}},
52
54
  {"type"=>{"kind"=>"ENUM","name"=>"DairyAnimal", "ofType"=>nil}},
53
55
  ]
54
56
  },
@@ -31,7 +31,7 @@ describe GraphQL::Language::Generation do
31
31
  describe "inputs" do
32
32
  let(:query_string) {%|
33
33
  query {
34
- field(int: 3, float: 4.7e-24, bool: false, string: "☀︎🏆\\n escaped \\" unicode ¶ /", enum: ENUM_NAME, array: [7, 8, 9], object: {a: [1, 2, 3], b: {c: "4"}}, unicode_bom: "\xef\xbb\xbfquery")
34
+ field(null_value: null, null_in_array: [1, null, 3], int: 3, float: 4.7e-24, bool: false, string: "☀︎🏆\\n escaped \\" unicode ¶ /", enum: ENUM_NAME, array: [7, 8, 9], object: {a: [1, 2, 3], b: {c: "4"}}, unicode_bom: "\xef\xbb\xbfquery")
35
35
  }
36
36
  |}
37
37
 
@@ -102,7 +102,7 @@ describe GraphQL::Language::Generation do
102
102
  end
103
103
 
104
104
  describe "full featured schema" do
105
- # From: https://github.com/graphql/graphql-js/blob/a725499b155285c2e33647a93393c82689b20b0f/src/language/__tests__/schema-kitchen-sink.graphql
105
+ # From: https://github.com/graphql/graphql-js/blob/bc96406ab44453a120da25a0bd6e2b0237119ddf/src/language/__tests__/schema-kitchen-sink.graphql
106
106
  let(:query_string) {<<-schema
107
107
  schema {
108
108
  query: QueryType
@@ -119,6 +119,7 @@ describe GraphQL::Language::Generation do
119
119
  four(argument: String = "string"): String
120
120
  five(argument: [String] = ["string", "string"]): String
121
121
  six(argument: InputType = {key: "value"}): Type
122
+ seven(argument: String = null): Type
122
123
  }
123
124
 
124
125
  # Scalar description
@@ -168,11 +168,9 @@ describe GraphQL::Query::Arguments do
168
168
  assert_equal true, test_inputs.key?(:b)
169
169
 
170
170
  assert_equal false, test_inputs.key?(:c)
171
- # This _was_ present in the variables,
172
- # but it was nil, which is not allowed in GraphQL
173
- assert_equal false, test_inputs.key?(:d)
171
+ assert_equal true, test_inputs.key?(:d)
174
172
 
175
- assert_equal({"a" => 1, "b" => 2}, test_inputs.to_h)
173
+ assert_equal({"a" => 1, "b" => 2, "d" => nil}, test_inputs.to_h)
176
174
  end
177
175
 
178
176
  it "works with variable default values" do
@@ -188,5 +186,20 @@ describe GraphQL::Query::Arguments do
188
186
  assert_equal false, test_defaults.key?(:d)
189
187
  assert_equal({"a" => 1, "b" => 2}, test_defaults.to_h)
190
188
  end
189
+
190
+ it "works with variable default values with null" do
191
+ schema.execute("query ArgTest($arg: TestInput = {d: null}){ argTest(d: $arg) }")
192
+
193
+ test_defaults = arg_values.last["d"]
194
+
195
+ assert_equal false, test_defaults.key?(:a)
196
+ # This is present from default val
197
+ assert_equal true, test_defaults.key?(:b)
198
+
199
+ assert_equal false, test_defaults.key?(:c)
200
+ assert_equal true, test_defaults.key?(:d)
201
+
202
+ assert_equal({"d" => nil, "b" => 2}, test_defaults.to_h)
203
+ end
191
204
  end
192
205
  end
@@ -1,6 +1,9 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe GraphQL::Query::Context do
4
+ CTX = []
5
+ before { CTX.clear }
6
+
4
7
  let(:query_type) { GraphQL::ObjectType.define {
5
8
  name "Query"
6
9
  field :context, types.String do
@@ -16,6 +19,10 @@ describe GraphQL::Query::Context do
16
19
  field :queryName, types.String do
17
20
  resolve ->(target, args, ctx) { ctx.query.class.name }
18
21
  end
22
+
23
+ field :pushContext, types.Int do
24
+ resolve ->(t,a,c) { CTX << c; 1 }
25
+ end
19
26
  }}
20
27
  let(:schema) { GraphQL::Schema.define(query: query_type, mutation: nil)}
21
28
  let(:result) { schema.execute(query_string, context: {"some_key" => "some value"})}
@@ -81,4 +88,20 @@ describe GraphQL::Query::Context do
81
88
  assert_equal("wow!", context[:some_key])
82
89
  end
83
90
  end
91
+
92
+ describe "accessing context after the fact" do
93
+ let(:query_string) { %|
94
+ { pushContext }
95
+ |}
96
+
97
+ it "preserves path information" do
98
+ assert_equal 1, result["data"]["pushContext"]
99
+ last_ctx = CTX.pop
100
+ assert_equal ["pushContext"], last_ctx.path
101
+ err = GraphQL::ExecutionError.new("Test position info")
102
+ last_ctx.add_error(err)
103
+ assert_equal ["pushContext"], err.path
104
+ assert_equal [2, 9], [err.ast_node.line, err.ast_node.col]
105
+ end
106
+ end
84
107
  end
@@ -2,7 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe GraphQL::Query::Variables do
4
4
  let(:query_string) {%|
5
- query getCheese($animals: [DairyAnimal]) {
5
+ query getCheese($animals: [DairyAnimal], $int: Int, $intWithDefault: Int = 10) {
6
6
  cheese(id: 1) {
7
7
  similarCheese(source: $animals)
8
8
  }
@@ -25,5 +25,19 @@ describe GraphQL::Query::Variables do
25
25
  assert_equal ["YAK"], variables["animals"]
26
26
  end
27
27
  end
28
+
29
+ describe "coercing null" do
30
+ let(:provided_variables) {
31
+ {"int" => nil, "intWithDefault" => nil}
32
+ }
33
+
34
+ it "null variable" do
35
+ assert_equal nil, variables["int"]
36
+ end
37
+
38
+ it "preserves explicit null when variable has a default value" do
39
+ assert_equal nil, variables["intWithDefault"]
40
+ end
41
+ end
28
42
  end
29
43
  end
@@ -2,8 +2,8 @@ require 'spec_helper'
2
2
 
3
3
  describe GraphQL::Relay::Mutation do
4
4
  let(:query_string) {%|
5
- mutation addBagel($clientMutationId: String) {
6
- introduceShip(input: {shipName: "Bagel", factionId: "1", clientMutationId: $clientMutationId}) {
5
+ mutation addBagel($clientMutationId: String, $shipName: String = "Bagel") {
6
+ introduceShip(input: {shipName: $shipName, factionId: "1", clientMutationId: $clientMutationId}) {
7
7
  clientMutationId
8
8
  shipEdge {
9
9
  node { name, id }
@@ -25,6 +25,24 @@ describe GraphQL::Relay::Mutation do
25
25
  STAR_WARS_DATA["Faction"]["1"].ships.delete("9")
26
26
  end
27
27
 
28
+ it "supports null values" do
29
+ result = star_wars_query(query_string, "clientMutationId" => "1234", "shipName" => nil)
30
+
31
+ expected = {"data" => {
32
+ "introduceShip" => {
33
+ "clientMutationId" => "1234",
34
+ "shipEdge" => {
35
+ "node" => {
36
+ "name" => nil,
37
+ "id" => GraphQL::Schema::UniqueWithinType.encode("Ship", "9"),
38
+ },
39
+ },
40
+ "faction" => {"name" => STAR_WARS_DATA["Faction"]["1"].name }
41
+ }
42
+ }}
43
+ assert_equal(expected, result)
44
+ end
45
+
28
46
  it "returns the result & clientMutationId" do
29
47
  result = star_wars_query(query_string, "clientMutationId" => "1234")
30
48
  expected = {"data" => {
@@ -71,6 +89,11 @@ describe GraphQL::Relay::Mutation do
71
89
  custom_type = custom_return_type
72
90
  GraphQL::Relay::Mutation.define do
73
91
  name "CustomReturnTypeTest"
92
+
93
+ input_field :nullDefault, types.String, default_value: nil
94
+ input_field :noDefault, types.String
95
+ input_field :stringDefault, types.String, default_value: 'String'
96
+
74
97
  return_type custom_type
75
98
  resolve ->(obj, input, ctx) {
76
99
  OpenStruct.new(name: "Custom Return Type Test")
@@ -78,6 +101,8 @@ describe GraphQL::Relay::Mutation do
78
101
  end
79
102
  }
80
103
 
104
+ let(:input) { mutation.field.arguments['input'].type.unwrap }
105
+
81
106
  let(:schema) {
82
107
  mutation_field = mutation.field
83
108
 
@@ -102,5 +127,20 @@ describe GraphQL::Relay::Mutation do
102
127
  it "doesn't get a mutation in the metadata" do
103
128
  assert_equal nil, custom_return_type.mutation
104
129
  end
130
+
131
+ it "supports input fields with nil default value" do
132
+ assert input.arguments['nullDefault'].default_value?
133
+ assert_equal nil, input.arguments['nullDefault'].default_value
134
+ end
135
+
136
+ it "supports input fields with no default value" do
137
+ assert !input.arguments['noDefault'].default_value?
138
+ assert_equal nil, input.arguments['noDefault'].default_value
139
+ end
140
+
141
+ it "supports input fields with non-nil default value" do
142
+ assert input.arguments['stringDefault'].default_value?
143
+ assert_equal "String", input.arguments['stringDefault'].default_value
144
+ end
105
145
  end
106
146
  end
@@ -31,7 +31,7 @@ schema {
31
31
  query: Hello
32
32
  }
33
33
 
34
- directive @foo(arg: Int) on FIELD
34
+ directive @foo(arg: Int, nullDefault: Int = null) on FIELD
35
35
 
36
36
  type Hello {
37
37
  str: String
@@ -356,6 +356,7 @@ schema {
356
356
 
357
357
  input Input {
358
358
  int: Int
359
+ nullDefault: Int = null
359
360
  }
360
361
 
361
362
  type Root {
@@ -380,6 +381,7 @@ enum Color {
380
381
  type Hello {
381
382
  str(int: Int = 2): String
382
383
  hello(color: Color = RED): String
384
+ nullable(color: Color = null): String
383
385
  }
384
386
  SCHEMA
385
387
 
@@ -415,7 +417,7 @@ enum Color {
415
417
  }
416
418
 
417
419
  type Mutation {
418
- hello(str: String, int: Int, color: Color = RED): String
420
+ hello(str: String, int: Int, color: Color = RED, nullDefault: Int = null): String
419
421
  }
420
422
 
421
423
  type Query {
@@ -37,6 +37,17 @@ describe GraphQL::Schema::Loader do
37
37
  input_field :sub, types[sub_input_type]
38
38
  end
39
39
 
40
+ variant_input_type_with_nulls = GraphQL::InputObjectType.define do
41
+ name "VariedWithNulls"
42
+ input_field :id, types.ID, default_value: nil
43
+ input_field :int, types.Int, default_value: nil
44
+ input_field :bigint, big_int_type, default_value: nil
45
+ input_field :float, types.Float, default_value: nil
46
+ input_field :bool, types.Boolean, default_value: nil
47
+ input_field :enum, choice_type, default_value: nil
48
+ input_field :sub, types[sub_input_type], default_value: nil
49
+ end
50
+
40
51
  comment_type = GraphQL::ObjectType.define do
41
52
  name "Comment"
42
53
  description "A blog comment"
@@ -90,6 +101,7 @@ describe GraphQL::Schema::Loader do
90
101
  type post_type
91
102
  argument :id, !types.ID
92
103
  argument :varied, variant_input_type, default_value: { id: "123", int: 234, float: 2.3, enum: :foo, sub: [{ string: "str" }] }
104
+ argument :variedWithNull, variant_input_type_with_nulls, default_value: { id: nil, int: nil, float: nil, enum: nil, sub: nil, bigint: nil, bool: nil }
93
105
  end
94
106
 
95
107
  field :content do
@@ -205,7 +217,53 @@ describe GraphQL::Schema::Loader do
205
217
  field = type.fields['post']
206
218
  arg = field.arguments['varied']
207
219
 
208
- assert_equal arg.default_value, { 'id' => "123", 'bigint' => nil, 'bool' => nil, 'int' => 234, 'float' => 2.3, 'enum' => "FOO", 'sub' => [{ 'string' => "str" }] }
220
+ assert_equal arg.default_value, { 'id' => "123", 'int' => 234, 'float' => 2.3, 'enum' => "FOO", 'sub' => [{ 'string' => "str" }] }
221
+ assert !arg.default_value.key?('bool'), 'Omits default value for unspecified arguments'
222
+ end
223
+
224
+ it "does not set default value when there are none on input fields" do
225
+ type = loaded_schema.types['Varied']
226
+
227
+ assert !type.arguments['id'].default_value?
228
+ assert !type.arguments['int'].default_value?
229
+ assert type.arguments['bigint'].default_value?
230
+ assert !type.arguments['float'].default_value?
231
+ assert !type.arguments['bool'].default_value?
232
+ assert !type.arguments['enum'].default_value?
233
+ assert !type.arguments['sub'].default_value?
234
+ end
235
+
236
+ it "sets correct default values `null` on input fields" do
237
+ type = loaded_schema.types['VariedWithNulls']
238
+
239
+ assert type.arguments['id'].default_value?
240
+ assert type.arguments['id'].default_value.nil?
241
+
242
+ assert type.arguments['int'].default_value?
243
+ assert type.arguments['int'].default_value.nil?
244
+
245
+ assert type.arguments['bigint'].default_value?
246
+ assert type.arguments['bigint'].default_value.nil?
247
+
248
+ assert type.arguments['float'].default_value?
249
+ assert type.arguments['float'].default_value.nil?
250
+
251
+ assert type.arguments['bool'].default_value?
252
+ assert type.arguments['bool'].default_value.nil?
253
+
254
+ assert type.arguments['enum'].default_value?
255
+ assert type.arguments['enum'].default_value.nil?
256
+
257
+ assert type.arguments['sub'].default_value?
258
+ assert type.arguments['sub'].default_value.nil?
259
+ end
260
+
261
+ it "sets correct default values `null` on complex field arguments" do
262
+ type = loaded_schema.types['Query']
263
+ field = type.fields['post']
264
+ arg = field.arguments['variedWithNull']
265
+
266
+ assert_equal arg.default_value, { 'id' => nil, 'int' => nil, 'float' => nil, 'enum' => nil, 'sub' => nil, 'bool' => nil, 'bigint' => nil }
209
267
  end
210
268
  end
211
269
  end
@@ -85,6 +85,7 @@ describe GraphQL::Schema::Printer do
85
85
  type post_type
86
86
  argument :id, !types.ID, 'Post ID'
87
87
  argument :varied, variant_input_type, default_value: { id: "123", int: 234, float: 2.3, enum: :foo, sub: [{ string: "str" }] }
88
+ argument :variedWithNulls, variant_input_type, default_value: { id: nil, int: nil, float: nil, enum: nil, sub: nil }
88
89
  resolve ->(obj, args, ctx) { Post.find(args["id"]) }
89
90
  end
90
91
  end
@@ -444,6 +445,7 @@ type Query {
444
445
  # Post ID
445
446
  id: ID!
446
447
  varied: Varied = {id: \"123\", int: 234, float: 2.3, enum: FOO, sub: [{string: \"str\"}]}
448
+ variedWithNulls: Varied = {id: null, int: null, float: null, enum: null, sub: null}
447
449
  ): Post
448
450
  }
449
451
 
@@ -10,9 +10,9 @@ describe GraphQL::Schema::ReduceTypes do
10
10
  "Cheese" => CheeseType,
11
11
  "Float" => GraphQL::FLOAT_TYPE,
12
12
  "String" => GraphQL::STRING_TYPE,
13
+ "Edible" => EdibleInterface,
13
14
  "DairyAnimal" => DairyAnimalEnum,
14
15
  "Int" => GraphQL::INT_TYPE,
15
- "Edible" => EdibleInterface,
16
16
  "AnimalProduct" => AnimalProductInterface,
17
17
  "LocalProduct" => LocalProductInterface,
18
18
  }
@@ -166,7 +166,7 @@ describe GraphQL::Schema::TimeoutMiddleware do
166
166
  describe "with a custom block" do
167
167
  let(:timeout_middleware) {
168
168
  GraphQL::Schema::TimeoutMiddleware.new(max_seconds: max_seconds) do |err, query|
169
- raise("Query timed out after 2s: #{query.class.name} on #{query.context.ast_node.alias}")
169
+ raise("Query timed out after 2s: #{query.operations.count} on #{query.context.ast_node.alias}")
170
170
  end
171
171
  }
172
172
  let(:query_string) {%|
@@ -181,7 +181,7 @@ describe GraphQL::Schema::TimeoutMiddleware do
181
181
 
182
182
  it "calls the block" do
183
183
  err = assert_raises(RuntimeError) { result }
184
- assert_equal "Query timed out after 2s: GraphQL::Query on d", err.message
184
+ assert_equal "Query timed out after 2s: 1 on d", err.message
185
185
  end
186
186
  end
187
187
  end
@@ -9,6 +9,15 @@ describe GraphQL::Schema::UniqueWithinType do
9
9
  assert_equal("123", id)
10
10
  end
11
11
 
12
+ it "allows you specify default separator" do
13
+ GraphQL::Schema::UniqueWithinType.default_id_separator = '|'
14
+ global_id = GraphQL::Schema::UniqueWithinType.encode("Type-With-UUID", "250cda0e-a89d-41cf-99e1-2872d89f1100")
15
+ type_name, id = GraphQL::Schema::UniqueWithinType.decode(global_id)
16
+ assert_equal("Type-With-UUID", type_name)
17
+ assert_equal("250cda0e-a89d-41cf-99e1-2872d89f1100", id)
18
+ GraphQL::Schema::UniqueWithinType.default_id_separator = '-'
19
+ end
20
+
12
21
  it "allows you to specify the separator" do
13
22
  custom_separator = "---"
14
23
  global_id = GraphQL::Schema::UniqueWithinType.encode("Type-With-UUID", "250cda0e-a89d-41cf-99e1-2872d89f1100", separator: custom_separator)
@@ -200,7 +200,7 @@ describe GraphQL::Schema::Validation do
200
200
  end
201
201
  }
202
202
 
203
- let(:invalid_default_argument_for_non_null_field) {
203
+ let(:invalid_default_argument_for_non_null_argument) {
204
204
  GraphQL::Argument.define do
205
205
  name "InvalidDefault"
206
206
  type !GraphQL::INT_TYPE
@@ -208,12 +208,24 @@ describe GraphQL::Schema::Validation do
208
208
  end
209
209
  }
210
210
 
211
+ let(:null_default_value) {
212
+ GraphQL::Argument.define do
213
+ name "NullDefault"
214
+ type DairyAnimalEnum
215
+ default_value nil
216
+ end
217
+ }
218
+
211
219
  it "requires the type is a Base type" do
212
220
  assert_error_includes untyped_argument, "must be a valid input type (Scalar or InputObject), not Symbol"
213
221
  end
214
222
 
215
- it "does not allow default values for non-null fields" do
216
- assert_error_includes invalid_default_argument_for_non_null_field, 'Variable InvalidDefault of type "Int!" is required and will not use the default value. Perhaps you meant to use type "Int".'
223
+ it "does not allow default values for non-null argument" do
224
+ assert_error_includes invalid_default_argument_for_non_null_argument, 'Variable InvalidDefault of type "Int!" is required and will not use the default value. Perhaps you meant to use type "Int".'
225
+ end
226
+
227
+ it "allows null default value for nullable argument" do
228
+ assert_equal nil, GraphQL::Schema::Validation.validate(null_default_value)
217
229
  end
218
230
  end
219
231
  end
@@ -65,4 +65,126 @@ describe GraphQL::StaticValidation::ArgumentLiteralsAreCompatible do
65
65
  }
66
66
  assert_includes(errors, fragment_error)
67
67
  end
68
+
69
+ describe "null value" do
70
+ describe "nullable arg" do
71
+ let(:schema) {
72
+ GraphQL::Schema.from_definition(%|
73
+ type Query {
74
+ field(arg: Int): Int
75
+ }
76
+ |)
77
+ }
78
+ let(:query_string) {%|
79
+ query {
80
+ field(arg: null)
81
+ }
82
+ |}
83
+
84
+ it "finds no errors" do
85
+ assert_equal [], errors
86
+ end
87
+ end
88
+
89
+ describe "non-nullable arg" do
90
+ let(:schema) {
91
+ GraphQL::Schema.from_definition(%|
92
+ type Query {
93
+ field(arg: Int!): Int
94
+ }
95
+ |)
96
+ }
97
+ let(:query_string) {%|
98
+ query {
99
+ field(arg: null)
100
+ }
101
+ |}
102
+
103
+ it "finds error" do
104
+ assert_equal [{
105
+ "message"=>"Argument 'arg' on Field 'field' has an invalid value. Expected type 'Int!'.",
106
+ "locations"=>[{"line"=>3, "column"=>11}],
107
+ "fields"=>["query", "field", "arg"],
108
+ }], errors
109
+ end
110
+ end
111
+
112
+ describe "non-nullable array" do
113
+ let(:schema) {
114
+ GraphQL::Schema.from_definition(%|
115
+ type Query {
116
+ field(arg: [Int!]): Int
117
+ }
118
+ |)
119
+ }
120
+ let(:query_string) {%|
121
+ query {
122
+ field(arg: [null])
123
+ }
124
+ |}
125
+
126
+ it "finds error" do
127
+ assert_equal [{
128
+ "message"=>"Argument 'arg' on Field 'field' has an invalid value. Expected type '[Int!]'.",
129
+ "locations"=>[{"line"=>3, "column"=>11}],
130
+ "fields"=>["query", "field", "arg"],
131
+ }], errors
132
+ end
133
+ end
134
+
135
+ describe "array with nullable values" do
136
+ let(:schema) {
137
+ GraphQL::Schema.from_definition(%|
138
+ type Query {
139
+ field(arg: [Int]): Int
140
+ }
141
+ |)
142
+ }
143
+ let(:query_string) {%|
144
+ query {
145
+ field(arg: [null])
146
+ }
147
+ |}
148
+
149
+ it "finds no errors" do
150
+ assert_equal [], errors
151
+ end
152
+ end
153
+
154
+ describe "input object" do
155
+ let(:schema) {
156
+ GraphQL::Schema.from_definition(%|
157
+ type Query {
158
+ field(arg: Input): Int
159
+ }
160
+
161
+ input Input {
162
+ a: Int
163
+ b: Int!
164
+ }
165
+ |)
166
+ }
167
+ let(:query_string) {%|
168
+ query {
169
+ field(arg: {a: null, b: null})
170
+ }
171
+ |}
172
+
173
+ it "finds errors" do
174
+ assert_equal 2, errors.length
175
+
176
+ assert_includes errors, {
177
+ "message"=> "Argument 'arg' on Field 'field' has an invalid value. Expected type 'Input'.",
178
+ "locations"=>[{"line"=>3, "column"=>11}],
179
+ "fields"=>["query", "field", "arg"]
180
+ }
181
+
182
+ assert_includes errors, {
183
+ "message"=>"Argument 'b' on InputObject 'Input' has an invalid value. Expected type 'Int!'.",
184
+ "locations"=>[{"line"=>3, "column"=>22}],
185
+ "fields"=>["query", "field", "arg", "b"]
186
+ }
187
+ end
188
+ end
189
+ end
68
190
  end