graphql 1.3.0 → 1.4.0

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