graphql 1.3.0 → 1.4.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/base_type.rb +33 -5
  3. data/lib/graphql/boolean_type.rb +1 -0
  4. data/lib/graphql/compatibility/execution_specification.rb +1 -1
  5. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +1 -0
  6. data/lib/graphql/directive.rb +10 -2
  7. data/lib/graphql/directive/deprecated_directive.rb +1 -0
  8. data/lib/graphql/directive/include_directive.rb +1 -0
  9. data/lib/graphql/directive/skip_directive.rb +1 -0
  10. data/lib/graphql/enum_type.rb +1 -0
  11. data/lib/graphql/execution/execute.rb +1 -13
  12. data/lib/graphql/float_type.rb +1 -0
  13. data/lib/graphql/id_type.rb +1 -0
  14. data/lib/graphql/input_object_type.rb +12 -2
  15. data/lib/graphql/int_type.rb +1 -0
  16. data/lib/graphql/interface_type.rb +1 -0
  17. data/lib/graphql/introspection/directive_location_enum.rb +1 -0
  18. data/lib/graphql/introspection/directive_type.rb +1 -0
  19. data/lib/graphql/introspection/enum_value_type.rb +1 -0
  20. data/lib/graphql/introspection/field_type.rb +1 -0
  21. data/lib/graphql/introspection/input_fields_field.rb +1 -1
  22. data/lib/graphql/introspection/input_value_type.rb +1 -0
  23. data/lib/graphql/introspection/schema_type.rb +2 -0
  24. data/lib/graphql/introspection/type_kind_enum.rb +1 -0
  25. data/lib/graphql/introspection/type_type.rb +1 -0
  26. data/lib/graphql/list_type.rb +1 -0
  27. data/lib/graphql/non_null_type.rb +1 -0
  28. data/lib/graphql/object_type.rb +1 -0
  29. data/lib/graphql/query.rb +50 -13
  30. data/lib/graphql/query/context.rb +5 -4
  31. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -22
  32. data/lib/graphql/relay/array_connection.rb +3 -1
  33. data/lib/graphql/relay/connection_type.rb +15 -1
  34. data/lib/graphql/relay/node.rb +1 -0
  35. data/lib/graphql/relay/page_info.rb +1 -0
  36. data/lib/graphql/relay/relation_connection.rb +2 -0
  37. data/lib/graphql/schema.rb +21 -13
  38. data/lib/graphql/schema/catchall_middleware.rb +2 -2
  39. data/lib/graphql/schema/middleware_chain.rb +71 -13
  40. data/lib/graphql/schema/null_mask.rb +10 -0
  41. data/lib/graphql/schema/printer.rb +85 -59
  42. data/lib/graphql/schema/rescue_middleware.rb +2 -2
  43. data/lib/graphql/schema/timeout_middleware.rb +2 -2
  44. data/lib/graphql/schema/validation.rb +1 -12
  45. data/lib/graphql/schema/warden.rb +48 -24
  46. data/lib/graphql/static_validation/literal_validator.rb +2 -2
  47. data/lib/graphql/string_type.rb +1 -0
  48. data/lib/graphql/union_type.rb +7 -1
  49. data/lib/graphql/version.rb +1 -1
  50. data/readme.md +0 -19
  51. data/spec/graphql/directive/skip_directive_spec.rb +8 -0
  52. data/spec/graphql/directive_spec.rb +9 -3
  53. data/spec/graphql/input_object_type_spec.rb +67 -0
  54. data/spec/graphql/query/variables_spec.rb +1 -1
  55. data/spec/graphql/relay/array_connection_spec.rb +9 -0
  56. data/spec/graphql/relay/connection_type_spec.rb +20 -0
  57. data/spec/graphql/relay/node_spec.rb +6 -0
  58. data/spec/graphql/relay/page_info_spec.rb +4 -0
  59. data/spec/graphql/relay/relation_connection_spec.rb +8 -0
  60. data/spec/graphql/scalar_type_spec.rb +4 -0
  61. data/spec/graphql/schema/middleware_chain_spec.rb +27 -13
  62. data/spec/graphql/schema/printer_spec.rb +121 -6
  63. data/spec/graphql/schema/rescue_middleware_spec.rb +4 -4
  64. data/spec/graphql/schema/warden_spec.rb +16 -12
  65. data/spec/graphql/schema_spec.rb +9 -0
  66. data/spec/graphql/string_type_spec.rb +10 -4
  67. data/spec/spec_helper.rb +2 -1
  68. data/spec/support/star_wars_schema.rb +2 -2
  69. metadata +19 -2
@@ -37,6 +37,10 @@ describe GraphQL::Relay::PageInfo do
37
37
  }
38
38
  |}
39
39
 
40
+ it "is a default relay type" do
41
+ assert_equal true, GraphQL::Relay::PageInfo.default_relay?
42
+ end
43
+
40
44
  describe 'hasNextPage / hasPreviousPage' do
41
45
  it "hasNextPage is true if there are more items" do
42
46
  result = star_wars_query(query_string, "first" => 2)
@@ -95,6 +95,14 @@ describe GraphQL::Relay::RelationConnection do
95
95
  result = star_wars_query(query_string, "before" => last_cursor, "last" => 10)
96
96
  assert_equal(["Death Star", "Shield Generator"], get_names(result))
97
97
 
98
+ result = star_wars_query(query_string, "last" => 2)
99
+ assert_equal(["Shield Generator", "Headquarters"], get_names(result))
100
+ end
101
+
102
+ it 'handles cursors beyond the bounds of the array' do
103
+ overreaching_cursor = Base64.strict_encode64("100")
104
+ result = star_wars_query(query_string, "after" => overreaching_cursor, "first" => 2)
105
+ assert_equal([], get_names(result))
98
106
  end
99
107
 
100
108
  it "applies custom arguments" do
@@ -11,6 +11,10 @@ describe GraphQL::ScalarType do
11
11
  }
12
12
  let(:bignum) { 2 ** 128 }
13
13
 
14
+ it "is not a default scalar" do
15
+ assert_equal(false, custom_scalar.default_scalar?)
16
+ end
17
+
14
18
  it "coerces nil into nil" do
15
19
  assert_equal(nil, custom_scalar.coerce_input(nil))
16
20
  end
@@ -2,40 +2,54 @@
2
2
  require "spec_helper"
3
3
 
4
4
  describe GraphQL::Schema::MiddlewareChain do
5
- let(:step_1) { ->(step_values, next_step) { step_values << 1; next_step.call } }
6
- let(:step_2) { ->(step_values, next_step) { step_values << 2; next_step.call } }
7
- let(:step_3) { ->(step_values, next_step) { step_values << 3; :return_value } }
5
+ let(:step_1) { ->(step_values, &next_step) { step_values << 1; next_step.call } }
6
+ let(:step_2) { ->(step_values, &next_step) { step_values << 2; next_step.call } }
7
+ let(:step_3) { ->(step_values, &next_step) { step_values << 3; :return_value } }
8
8
  let(:steps) { [step_1, step_2, step_3] }
9
9
  let(:step_values) { [] }
10
10
  let(:arguments) { [step_values] }
11
- let(:middleware_chain) { GraphQL::Schema::MiddlewareChain.new(steps: steps, arguments: arguments)}
11
+ let(:middleware_chain) { GraphQL::Schema::MiddlewareChain.new(steps: steps)}
12
12
 
13
- describe "#call" do
13
+ describe "#invoke" do
14
14
  it "runs steps in order" do
15
- middleware_chain.call
15
+ middleware_chain.invoke(arguments)
16
16
  assert_equal([1,2,3], step_values)
17
17
  end
18
18
 
19
19
  it "returns the value of the last middleware" do
20
- assert_equal(:return_value, middleware_chain.call)
20
+ assert_equal(:return_value, middleware_chain.invoke(arguments))
21
+ end
22
+
23
+ describe "when there is a final step" do
24
+ let(:final_step) { ->(step_values) { step_values << :final; :final_value } }
25
+ let(:middleware_chain) { GraphQL::Schema::MiddlewareChain.new(steps: [step_1, step_2], final_step: final_step) }
26
+
27
+ it "calls the final step" do
28
+ middleware_chain.invoke(arguments)
29
+ assert_equal([1, 2, :final], step_values)
30
+ end
31
+
32
+ it "returns the value from the final step" do
33
+ assert_equal(:final_value, middleware_chain.invoke(arguments))
34
+ end
21
35
  end
22
36
 
23
37
  describe "when a step returns early" do
24
- let(:early_return_step) { ->(step_values, next_step) { :early_return } }
38
+ let(:early_return_step) { ->(step_values, &next_step) { :early_return } }
25
39
  it "doesn't continue the chain" do
26
40
  steps.insert(2, early_return_step)
27
- assert_equal(:early_return, middleware_chain.call)
41
+ assert_equal(:early_return, middleware_chain.invoke(arguments))
28
42
  assert_equal([1,2], step_values)
29
43
  end
30
44
  end
31
45
 
32
46
  describe "when a step provides alternate arguments" do
33
47
  it "passes the new arguments to the next step" do
34
- step_1 = ->(test_arg, next_step) { assert_equal(test_arg, 'HELLO'); next_step.call(['WORLD']) }
35
- step_2 = ->(test_arg, next_step) { assert_equal(test_arg, 'WORLD'); test_arg }
48
+ step_1 = ->(test_arg, &next_step) { assert_equal(test_arg, 'HELLO'); next_step.call(['WORLD']) }
49
+ step_2 = ->(test_arg, &next_step) { assert_equal(test_arg, 'WORLD'); test_arg }
36
50
 
37
- chain = GraphQL::Schema::MiddlewareChain.new(steps: [step_1, step_2], arguments: ['HELLO'])
38
- result = chain.call
51
+ chain = GraphQL::Schema::MiddlewareChain.new(steps: [step_1, step_2])
52
+ result = chain.invoke(['HELLO'])
39
53
  assert_equal(result, 'WORLD')
40
54
  end
41
55
  end
@@ -336,7 +336,8 @@ SCHEMA
336
336
 
337
337
  describe ".print_schema" do
338
338
  it "includes schema definition when query root name doesn't match convention" do
339
- schema.query.name = 'MyQueryRoot'
339
+ custom_query = schema.query.redefine(name: "MyQueryRoot")
340
+ custom_schema = schema.redefine(query: custom_query)
340
341
 
341
342
  expected = <<SCHEMA
342
343
  schema {
@@ -346,11 +347,13 @@ schema {
346
347
  }
347
348
  SCHEMA
348
349
 
349
- assert_match expected, GraphQL::Schema::Printer.print_schema(schema)
350
+ assert_match expected, GraphQL::Schema::Printer.print_schema(custom_schema)
350
351
  end
351
352
 
352
353
  it "includes schema definition when mutation root name doesn't match convention" do
353
- schema.mutation.name = 'MyMutationRoot'
354
+ custom_mutation = schema.mutation.redefine(name: "MyMutationRoot")
355
+ custom_schema = schema.redefine(mutation: custom_mutation)
356
+
354
357
 
355
358
  expected = <<SCHEMA
356
359
  schema {
@@ -360,11 +363,12 @@ schema {
360
363
  }
361
364
  SCHEMA
362
365
 
363
- assert_match expected, GraphQL::Schema::Printer.print_schema(schema)
366
+ assert_match expected, GraphQL::Schema::Printer.print_schema(custom_schema)
364
367
  end
365
368
 
366
369
  it "includes schema definition when subscription root name doesn't match convention" do
367
- schema.subscription.name = 'MySubscriptionRoot'
370
+ custom_subscription = schema.subscription.redefine(name: "MySubscriptionRoot")
371
+ custom_schema = schema.redefine(subscription: custom_subscription)
368
372
 
369
373
  expected = <<SCHEMA
370
374
  schema {
@@ -374,7 +378,7 @@ schema {
374
378
  }
375
379
  SCHEMA
376
380
 
377
- assert_match expected, GraphQL::Schema::Printer.print_schema(schema)
381
+ assert_match expected, GraphQL::Schema::Printer.print_schema(custom_schema)
378
382
  end
379
383
 
380
384
  it "returns the schema as a string for the defined types" do
@@ -475,4 +479,115 @@ SCHEMA
475
479
  assert_equal expected.chomp, GraphQL::Schema::Printer.print_schema(schema)
476
480
  end
477
481
  end
482
+
483
+ it "applies an `only` filter" do
484
+ expected = <<SCHEMA
485
+ enum Choice {
486
+ FOO
487
+ BAR
488
+ }
489
+
490
+ type Subscription {
491
+
492
+ }
493
+
494
+ input Varied {
495
+ int: Int
496
+ float: Float
497
+ bool: Boolean
498
+ enum: Choice = FOO
499
+ }
500
+ SCHEMA
501
+
502
+ only_filter = -> (member, ctx) {
503
+ case member
504
+ when GraphQL::ScalarType
505
+ true
506
+ when GraphQL::BaseType
507
+ ctx[:names].include?(member.name)
508
+ when GraphQL::Argument
509
+ member.name != "id"
510
+ else
511
+ member.deprecation_reason.nil?
512
+ end
513
+ }
514
+
515
+ context = { names: ["Varied", "Choice", "Subscription"] }
516
+ assert_equal expected.chomp, schema.to_definition(context: context, only: only_filter)
517
+ end
518
+
519
+
520
+ it "applies an `except` filter" do
521
+ expected = <<SCHEMA
522
+ type Audio {
523
+ id: ID!
524
+ name: String!
525
+ duration: Int!
526
+ }
527
+
528
+ enum Choice {
529
+ FOO
530
+ BAR
531
+ }
532
+
533
+ # A blog comment
534
+ type Comment implements Node {
535
+ id: ID!
536
+ }
537
+
538
+ # Autogenerated input type of CreatePost
539
+ input CreatePostInput {
540
+ # A unique identifier for the client performing the mutation.
541
+ clientMutationId: String
542
+ title: String!
543
+ body: String!
544
+ }
545
+
546
+ # Autogenerated return type of CreatePost
547
+ type CreatePostPayload {
548
+ # A unique identifier for the client performing the mutation.
549
+ clientMutationId: String
550
+ post: Post
551
+ }
552
+
553
+ # Media objects
554
+ union Media = Audio
555
+
556
+ type Mutation {
557
+ # Create a blog post
558
+ createPost(input: CreatePostInput!): CreatePostPayload
559
+ }
560
+
561
+ interface Node {
562
+ id: ID!
563
+ }
564
+
565
+ # A blog post
566
+ type Post {
567
+ id: ID!
568
+ title: String!
569
+ body: String!
570
+ comments: [Comment!]
571
+ }
572
+
573
+ # The query root of this schema
574
+ type Query {
575
+ post(
576
+ # Post ID
577
+ id: ID!
578
+ ): Post
579
+ }
580
+
581
+ type Subscription {
582
+ post(id: ID!): Post
583
+ }
584
+ SCHEMA
585
+
586
+ except_filter = -> (member, ctx) {
587
+ ctx[:names].include?(member.name) || (member.respond_to?(:deprecation_reason) && member.deprecation_reason)
588
+ }
589
+
590
+ context = { names: ["Varied", "Image", "Sub"] }
591
+ assert_equal expected.chomp, schema.to_definition(context: context, except: except_filter)
592
+ end
478
593
  end
@@ -4,7 +4,7 @@ require "spec_helper"
4
4
  class SpecExampleError < StandardError; end
5
5
 
6
6
  describe GraphQL::Schema::RescueMiddleware do
7
- let(:error_middleware) { ->(next_middleware) { raise(error_class) } }
7
+ let(:error_middleware) { ->{ raise(error_class) } }
8
8
 
9
9
  let(:rescue_middleware) do
10
10
  middleware = GraphQL::Schema::RescueMiddleware.new
@@ -14,12 +14,12 @@ describe GraphQL::Schema::RescueMiddleware do
14
14
 
15
15
  let(:steps) { [rescue_middleware, error_middleware] }
16
16
 
17
- let(:middleware_chain) { GraphQL::Schema::MiddlewareChain.new(steps: steps, arguments: [])}
17
+ let(:middleware_chain) { GraphQL::Schema::MiddlewareChain.new(steps: steps) }
18
18
 
19
19
  describe "known errors" do
20
20
  let(:error_class) { SpecExampleError }
21
21
  it "handles them as execution errors" do
22
- result = middleware_chain.call
22
+ result = middleware_chain.invoke([])
23
23
  assert_equal("there was an example error: SpecExampleError", result.message)
24
24
  assert_equal(GraphQL::ExecutionError, result.class)
25
25
  end
@@ -28,7 +28,7 @@ describe GraphQL::Schema::RescueMiddleware do
28
28
  describe "unknown errors" do
29
29
  let(:error_class) { RuntimeError }
30
30
  it "re-raises them" do
31
- assert_raises(RuntimeError) { middleware_chain.call }
31
+ assert_raises(RuntimeError) { middleware_chain.invoke([]) }
32
32
  end
33
33
  end
34
34
  end
@@ -109,7 +109,11 @@ module MaskHelpers
109
109
  end
110
110
 
111
111
  def self.query_with_mask(str, mask, variables: {})
112
- Schema.execute(str, except: mask, root_value: Data, variables: variables)
112
+ run_query(str, except: mask, root_value: Data, variables: variables)
113
+ end
114
+
115
+ def self.run_query(str, options = {})
116
+ Schema.execute(str, options.merge(root_value: Data))
113
117
  end
114
118
  end
115
119
 
@@ -149,7 +153,7 @@ describe GraphQL::Schema::Warden do
149
153
 
150
154
  describe "hiding fields" do
151
155
  let(:mask) {
152
- -> (member) { member.metadata[:hidden_field] || member.metadata[:hidden_type] }
156
+ -> (member, ctx) { member.metadata[:hidden_field] || member.metadata[:hidden_type] }
153
157
  }
154
158
 
155
159
  it "causes validation errors" do
@@ -194,8 +198,8 @@ describe GraphQL::Schema::Warden do
194
198
  end
195
199
 
196
200
  describe "hiding types" do
197
- let(:mask) {
198
- -> (member) { member.metadata[:hidden_type] }
201
+ let(:whitelist) {
202
+ -> (member, ctx) { !member.metadata[:hidden_type] }
199
203
  }
200
204
 
201
205
  it "hides types from introspection" do
@@ -227,7 +231,7 @@ describe GraphQL::Schema::Warden do
227
231
  }
228
232
  |
229
233
 
230
- res = MaskHelpers.query_with_mask(query_string, mask)
234
+ res = MaskHelpers.run_query(query_string, only: whitelist)
231
235
 
232
236
  # It's not visible by name
233
237
  assert_equal nil, res["data"]["Phoneme"]
@@ -258,7 +262,7 @@ describe GraphQL::Schema::Warden do
258
262
  }
259
263
  |
260
264
 
261
- res = MaskHelpers.query_with_mask(query_string, mask)
265
+ res = MaskHelpers.run_query(query_string, only: whitelist)
262
266
 
263
267
  expected_errors = [
264
268
  "No such type Phoneme, so it can't be a fragment condition",
@@ -275,13 +279,13 @@ describe GraphQL::Schema::Warden do
275
279
  |
276
280
 
277
281
  assert_raises(GraphQL::UnresolvedTypeError) {
278
- MaskHelpers.query_with_mask(query_string, mask)
282
+ MaskHelpers.run_query(query_string, only: whitelist)
279
283
  }
280
284
  end
281
285
 
282
286
  describe "hiding an abstract type" do
283
287
  let(:mask) {
284
- -> (member) { member.metadata[:hidden_abstract_type] }
288
+ -> (member, ctx) { member.metadata[:hidden_abstract_type] }
285
289
  }
286
290
 
287
291
  it "isn't present in a type's interfaces" do
@@ -303,7 +307,7 @@ describe GraphQL::Schema::Warden do
303
307
 
304
308
  describe "hiding arguments" do
305
309
  let(:mask) {
306
- -> (member) { member.metadata[:hidden_argument] || member.metadata[:hidden_input_type] }
310
+ -> (member, ctx) { member.metadata[:hidden_argument] || member.metadata[:hidden_input_type] }
307
311
  }
308
312
 
309
313
  it "isn't present in introspection" do
@@ -339,7 +343,7 @@ describe GraphQL::Schema::Warden do
339
343
 
340
344
  describe "hidding input type arguments" do
341
345
  let(:mask) {
342
- -> (member) { member.metadata[:hidden_input_field] }
346
+ -> (member, ctx) { member.metadata[:hidden_input_field] }
343
347
  }
344
348
 
345
349
  it "isn't present in introspection" do
@@ -388,7 +392,7 @@ describe GraphQL::Schema::Warden do
388
392
 
389
393
  describe "hidding input types" do
390
394
  let(:mask) {
391
- -> (member) { member.metadata[:hidden_input_object_type] }
395
+ -> (member, ctx) { member.metadata[:hidden_input_object_type] }
392
396
  }
393
397
 
394
398
  it "isn't present in introspection" do
@@ -433,7 +437,7 @@ describe GraphQL::Schema::Warden do
433
437
 
434
438
  describe "hiding enum values" do
435
439
  let(:mask) {
436
- -> (member) { member.metadata[:hidden_enum_value] }
440
+ -> (member, ctx) { member.metadata[:hidden_enum_value] }
437
441
  }
438
442
 
439
443
  it "isn't present in introspection" do
@@ -17,6 +17,12 @@ describe GraphQL::Schema do
17
17
  end
18
18
  end
19
19
 
20
+ describe "#to_definition" do
21
+ it "prints out the schema definition" do
22
+ assert_equal schema.to_definition, GraphQL::Schema::Printer.print_schema(schema)
23
+ end
24
+ end
25
+
20
26
  describe "#subscription" do
21
27
  it "calls fields on the subscription type" do
22
28
  res = schema.execute("subscription { test }")
@@ -308,6 +314,9 @@ type Query {
308
314
 
309
315
  refute schema_2.middleware.equal?(schema.middleware)
310
316
  assert_equal schema_2.middleware, schema.middleware
317
+
318
+ schema_2.middleware << ->(*args) { :noop }
319
+ refute_equal schema_2.middleware, schema.middleware
311
320
  end
312
321
  end
313
322
  end
@@ -2,15 +2,21 @@
2
2
  require "spec_helper"
3
3
 
4
4
  describe GraphQL::STRING_TYPE do
5
+ let(:string_type) { GraphQL::STRING_TYPE }
6
+
7
+ it "is a default scalar" do
8
+ assert_equal(true, string_type.default_scalar?)
9
+ end
10
+
5
11
  describe "coerce_input" do
6
12
  it "accepts strings" do
7
- assert_equal "str", GraphQL::STRING_TYPE.coerce_input("str")
13
+ assert_equal "str", string_type.coerce_input("str")
8
14
  end
9
15
 
10
16
  it "doesn't accept other types" do
11
- assert_equal nil, GraphQL::STRING_TYPE.coerce_input(100)
12
- assert_equal nil, GraphQL::STRING_TYPE.coerce_input(true)
13
- assert_equal nil, GraphQL::STRING_TYPE.coerce_input(0.999)
17
+ assert_equal nil, string_type.coerce_input(100)
18
+ assert_equal nil, string_type.coerce_input(true)
19
+ assert_equal nil, string_type.coerce_input(0.999)
14
20
  end
15
21
  end
16
22
  end