graphql 1.5.15 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +4 -19
  3. data/lib/graphql/analysis/analyze_query.rb +27 -2
  4. data/lib/graphql/analysis/query_complexity.rb +10 -11
  5. data/lib/graphql/argument.rb +7 -6
  6. data/lib/graphql/backwards_compatibility.rb +47 -0
  7. data/lib/graphql/compatibility/execution_specification.rb +14 -0
  8. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +6 -1
  9. data/lib/graphql/compatibility/lazy_execution_specification.rb +19 -0
  10. data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +15 -6
  11. data/lib/graphql/directive.rb +1 -6
  12. data/lib/graphql/execution.rb +1 -0
  13. data/lib/graphql/execution/execute.rb +174 -160
  14. data/lib/graphql/execution/field_result.rb +5 -1
  15. data/lib/graphql/execution/lazy.rb +2 -2
  16. data/lib/graphql/execution/lazy/resolve.rb +8 -11
  17. data/lib/graphql/execution/multiplex.rb +134 -0
  18. data/lib/graphql/execution/selection_result.rb +5 -0
  19. data/lib/graphql/field.rb +1 -8
  20. data/lib/graphql/filter.rb +53 -0
  21. data/lib/graphql/internal_representation/node.rb +11 -6
  22. data/lib/graphql/internal_representation/rewrite.rb +3 -3
  23. data/lib/graphql/query.rb +160 -78
  24. data/lib/graphql/query/arguments.rb +14 -25
  25. data/lib/graphql/query/arguments_cache.rb +6 -13
  26. data/lib/graphql/query/context.rb +28 -10
  27. data/lib/graphql/query/executor.rb +1 -0
  28. data/lib/graphql/query/literal_input.rb +10 -4
  29. data/lib/graphql/query/null_context.rb +1 -1
  30. data/lib/graphql/query/serial_execution/field_resolution.rb +5 -1
  31. data/lib/graphql/query/validation_pipeline.rb +12 -7
  32. data/lib/graphql/query/variables.rb +1 -1
  33. data/lib/graphql/rake_task.rb +140 -0
  34. data/lib/graphql/relay/array_connection.rb +29 -48
  35. data/lib/graphql/relay/base_connection.rb +9 -7
  36. data/lib/graphql/relay/mutation.rb +0 -11
  37. data/lib/graphql/relay/mutation/instrumentation.rb +2 -2
  38. data/lib/graphql/relay/mutation/resolve.rb +7 -10
  39. data/lib/graphql/relay/relation_connection.rb +98 -61
  40. data/lib/graphql/scalar_type.rb +1 -15
  41. data/lib/graphql/schema.rb +90 -25
  42. data/lib/graphql/schema/build_from_definition.rb +22 -23
  43. data/lib/graphql/schema/build_from_definition/resolve_map.rb +70 -0
  44. data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +47 -0
  45. data/lib/graphql/schema/middleware_chain.rb +1 -1
  46. data/lib/graphql/schema/printer.rb +2 -1
  47. data/lib/graphql/schema/timeout_middleware.rb +6 -6
  48. data/lib/graphql/schema/type_map.rb +1 -1
  49. data/lib/graphql/schema/warden.rb +5 -9
  50. data/lib/graphql/static_validation/definition_dependencies.rb +1 -1
  51. data/lib/graphql/version.rb +1 -1
  52. data/spec/graphql/analysis/analyze_query_spec.rb +2 -2
  53. data/spec/graphql/analysis/max_query_complexity_spec.rb +28 -0
  54. data/spec/graphql/argument_spec.rb +3 -3
  55. data/spec/graphql/execution/lazy_spec.rb +8 -114
  56. data/spec/graphql/execution/multiplex_spec.rb +131 -0
  57. data/spec/graphql/internal_representation/rewrite_spec.rb +10 -0
  58. data/spec/graphql/query/arguments_spec.rb +14 -16
  59. data/spec/graphql/query/context_spec.rb +14 -1
  60. data/spec/graphql/query/literal_input_spec.rb +19 -13
  61. data/spec/graphql/query/variables_spec.rb +1 -1
  62. data/spec/graphql/query_spec.rb +12 -1
  63. data/spec/graphql/rake_task_spec.rb +57 -0
  64. data/spec/graphql/relay/array_connection_spec.rb +24 -3
  65. data/spec/graphql/relay/connection_instrumentation_spec.rb +23 -0
  66. data/spec/graphql/relay/mutation_spec.rb +2 -10
  67. data/spec/graphql/relay/page_info_spec.rb +2 -2
  68. data/spec/graphql/relay/relation_connection_spec.rb +167 -3
  69. data/spec/graphql/schema/build_from_definition_spec.rb +93 -19
  70. data/spec/graphql/schema/warden_spec.rb +80 -0
  71. data/spec/graphql/schema_spec.rb +26 -2
  72. data/spec/spec_helper.rb +4 -2
  73. data/spec/support/lazy_helpers.rb +152 -0
  74. data/spec/support/star_wars/schema.rb +23 -0
  75. metadata +28 -3
  76. data/lib/graphql/schema/mask.rb +0 -55
@@ -57,4 +57,27 @@ describe GraphQL::Relay::ConnectionInstrumentation do
57
57
  assert_instance_of GraphQL::Relay::ConnectionResolve, connection_field.resolve_proc
58
58
  assert_instance_of GraphQL::Relay::ConnectionResolve, redefined_connection_field.resolve_proc
59
59
  end
60
+
61
+ describe "after_built_ins instrumentation" do
62
+ it "has access to connection objects" do
63
+ query_str = <<-GRAPHQL
64
+ {
65
+ rebels {
66
+ ships {
67
+ pageInfo {
68
+ __typename
69
+ }
70
+ }
71
+ }
72
+ }
73
+ GRAPHQL
74
+ ctx = { before_built_ins: [], after_built_ins: [] }
75
+ star_wars_query(query_str, {}, context: ctx)
76
+ # The second item is different here:
77
+ # Before the object is wrapped in a connection, the instrumentation sees `Array`
78
+ assert_equal ["StarWars::FactionRecord", "Array", "GraphQL::Relay::ArrayConnection"], ctx[:before_built_ins]
79
+ # After the object is wrapped in a connection, it sees the connection object
80
+ assert_equal ["StarWars::FactionRecord", "GraphQL::Relay::ArrayConnection", "GraphQL::Relay::ArrayConnection"], ctx[:after_built_ins]
81
+ end
82
+ end
60
83
  end
@@ -282,11 +282,7 @@ describe GraphQL::Relay::Mutation do
282
282
 
283
283
  expected = {
284
284
  "data" => {
285
- "introduceShip" => {
286
- "clientMutationId" => "5678",
287
- "shipEdge" => nil,
288
- "faction" => nil,
289
- }
285
+ "introduceShip" => nil,
290
286
  },
291
287
  "errors" => [
292
288
  {
@@ -305,11 +301,7 @@ describe GraphQL::Relay::Mutation do
305
301
 
306
302
  expected = {
307
303
  "data" => {
308
- "introduceShip" => {
309
- "clientMutationId" => "5678",
310
- "shipEdge" => nil,
311
- "faction" => nil,
312
- }
304
+ "introduceShip" => nil,
313
305
  },
314
306
  "errors" => [
315
307
  {
@@ -75,8 +75,8 @@ describe GraphQL::Relay::PageInfo do
75
75
  result = star_wars_query(query_string, "last" => 1, "first" => 1, "before" => cursor_of_last_base)
76
76
  assert_equal(true, get_page_info(result)["hasNextPage"])
77
77
  assert_equal(true, get_page_info(result)["hasPreviousPage"])
78
- assert_equal("Mg==", get_page_info(result)["startCursor"])
79
- assert_equal("Mg==", get_page_info(result)["endCursor"])
78
+ assert_equal("MQ==", get_page_info(result)["startCursor"])
79
+ assert_equal("MQ==", get_page_info(result)["endCursor"])
80
80
  end
81
81
 
82
82
  it "startCursor and endCursor are the cursors of the first and last edge" do
@@ -104,6 +104,25 @@ describe GraphQL::Relay::RelationConnection do
104
104
  assert_equal(false, result["data"]["empire"]["bases"]["pageInfo"]["hasPreviousPage"])
105
105
  end
106
106
 
107
+ it 'works with before and after specified together' do
108
+ result = star_wars_query(query_string, "first" => 2)
109
+ assert_equal(["Death Star", "Shield Generator"], get_names(result))
110
+
111
+ first_cursor = get_last_cursor(result)
112
+
113
+ # There is no records between before and after if they point to the same cursor
114
+ result = star_wars_query(query_string, "before" => first_cursor, "after" => first_cursor, "last" => 2)
115
+ assert_equal([], get_names(result))
116
+
117
+ result = star_wars_query(query_string, "after" => first_cursor, "first" => 2)
118
+ assert_equal(["Headquarters"], get_names(result))
119
+
120
+ second_cursor = get_last_cursor(result)
121
+
122
+ result = star_wars_query(query_string, "after" => first_cursor, "before" => second_cursor, "first" => 3)
123
+ assert_equal([], get_names(result))
124
+ end
125
+
107
126
  it 'handles cursors beyond the bounds of the array' do
108
127
  overreaching_cursor = Base64.strict_encode64("100")
109
128
  result = star_wars_query(query_string, "after" => overreaching_cursor, "first" => 2)
@@ -186,18 +205,19 @@ describe GraphQL::Relay::RelationConnection do
186
205
  end
187
206
 
188
207
  it "applies to queries by `last`" do
189
- last_cursor = "Ng=="
190
208
  second_to_last_two_names = ["Death Star", "Shield Generator"]
209
+ first_and_second_names = ["Yavin", "Echo Base"]
210
+
211
+ last_cursor = "Ng=="
191
212
  result = star_wars_query(query_string, "last" => 100, "before" => last_cursor)
192
213
  assert_equal(second_to_last_two_names, get_names(result))
193
214
  assert_equal(true, result["data"]["empire"]["bases"]["pageInfo"]["hasPreviousPage"])
194
215
 
195
216
  result = star_wars_query(query_string, "before" => last_cursor)
196
- assert_equal(second_to_last_two_names, get_names(result))
217
+ assert_equal(first_and_second_names, get_names(result))
197
218
  assert_equal(false, result["data"]["empire"]["bases"]["pageInfo"]["hasPreviousPage"], "hasPreviousPage is false when last is not specified")
198
219
 
199
220
  third_cursor = "Mw=="
200
- first_and_second_names = ["Yavin", "Echo Base"]
201
221
  result = star_wars_query(query_string, "last" => 100, "before" => third_cursor)
202
222
  assert_equal(first_and_second_names, get_names(result))
203
223
 
@@ -400,4 +420,148 @@ describe GraphQL::Relay::RelationConnection do
400
420
  connection = GraphQL::Relay::BaseConnection.connection_for_nodes(relation)
401
421
  assert_equal GraphQL::Relay::RelationConnection, connection
402
422
  end
423
+
424
+ describe "for an ActiveRecord::Relation" do
425
+ describe "#edge_nodes" do
426
+ it "returns the nodes for the current page" do
427
+ # Offset
428
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), {})
429
+ assert_equal [StarWars::Base.find(3), StarWars::Base.find(4), StarWars::Base.find(5), StarWars::Base.find(6)], connection.edge_nodes,
430
+
431
+ cursor1 = connection.cursor_from_node(StarWars::Base.find(3))
432
+ cursor2 = connection.cursor_from_node(StarWars::Base.find(4))
433
+ cursor3 = connection.cursor_from_node(StarWars::Base.find(5))
434
+ cursor4 = connection.cursor_from_node(StarWars::Base.find(6))
435
+
436
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { first: 3 })
437
+ assert_equal [StarWars::Base.find(3), StarWars::Base.find(4), StarWars::Base.find(5)], connection.edge_nodes
438
+
439
+ assert_equal cursor1, connection.cursor_from_node(StarWars::Base.find(3))
440
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(4))
441
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(5))
442
+
443
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { last: 3 })
444
+ assert_equal [StarWars::Base.find(4), StarWars::Base.find(5), StarWars::Base.find(6)], connection.edge_nodes
445
+
446
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(4))
447
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(5))
448
+ assert_equal cursor4, connection.cursor_from_node(StarWars::Base.find(6))
449
+
450
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { last: 2 })
451
+ assert_equal [StarWars::Base.find(5), StarWars::Base.find(6)], connection.edge_nodes
452
+
453
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(5))
454
+ assert_equal cursor4, connection.cursor_from_node(StarWars::Base.find(6))
455
+
456
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { first: 3, last: 1 })
457
+ assert_equal [StarWars::Base.find(5)], connection.edge_nodes
458
+
459
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(5))
460
+
461
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { first: 2, last: 1 })
462
+ assert_equal [StarWars::Base.find(4)], connection.edge_nodes
463
+
464
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(4))
465
+
466
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { after: cursor1 })
467
+ assert_equal [StarWars::Base.find(4), StarWars::Base.find(5), StarWars::Base.find(6)], connection.edge_nodes
468
+
469
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(4))
470
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(5))
471
+ assert_equal cursor4, connection.cursor_from_node(StarWars::Base.find(6))
472
+
473
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { after: cursor1, before: cursor1 })
474
+ assert_equal [], connection.edge_nodes
475
+
476
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { after: cursor1, before: cursor3 })
477
+ assert_equal [StarWars::Base.find(4)], connection.edge_nodes
478
+
479
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(4))
480
+
481
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { after: cursor1, before: cursor4 })
482
+ assert_equal [StarWars::Base.find(4), StarWars::Base.find(5)], connection.edge_nodes
483
+
484
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(4))
485
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(5))
486
+
487
+
488
+ # Limit
489
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), {})
490
+ assert_equal [StarWars::Base.find(1), StarWars::Base.find(2), StarWars::Base.find(3), StarWars::Base.find(4), StarWars::Base.find(5)], connection.edge_nodes
491
+
492
+ cursor1 = connection.cursor_from_node(StarWars::Base.find(1))
493
+ cursor2 = connection.cursor_from_node(StarWars::Base.find(2))
494
+ cursor3 = connection.cursor_from_node(StarWars::Base.find(3))
495
+ cursor4 = connection.cursor_from_node(StarWars::Base.find(4))
496
+
497
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { first: 2 })
498
+ assert_equal [StarWars::Base.find(1), StarWars::Base.find(2)], connection.edge_nodes
499
+
500
+ assert_equal cursor1, connection.cursor_from_node(StarWars::Base.find(1))
501
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(2))
502
+
503
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { first: 2, last: 1 })
504
+ assert_equal [StarWars::Base.find(2)], connection.edge_nodes
505
+
506
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(2))
507
+
508
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { after: cursor2, first: 2 })
509
+ assert_equal [StarWars::Base.find(3), StarWars::Base.find(4)], connection.edge_nodes
510
+
511
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(3))
512
+ assert_equal cursor4, connection.cursor_from_node(StarWars::Base.find(4))
513
+
514
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { after: cursor2, first: 2, last: 1 })
515
+ assert_equal [StarWars::Base.find(4)], connection.edge_nodes
516
+
517
+ assert_equal cursor4, connection.cursor_from_node(StarWars::Base.find(4))
518
+
519
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { first: 2, last: 5 })
520
+ assert_equal [StarWars::Base.find(1), StarWars::Base.find(2)], connection.edge_nodes
521
+
522
+ assert_equal cursor1, connection.cursor_from_node(StarWars::Base.find(1))
523
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(2))
524
+
525
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { first: 1, last: 5 })
526
+ assert_equal [StarWars::Base.find(1)], connection.edge_nodes
527
+
528
+ assert_equal cursor1, connection.cursor_from_node(StarWars::Base.find(1))
529
+
530
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { after: cursor1, before: cursor1 })
531
+ assert_equal [], connection.edge_nodes
532
+
533
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { after: cursor1, before: cursor3 })
534
+ assert_equal [StarWars::Base.find(2)], connection.edge_nodes
535
+
536
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(2))
537
+
538
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { after: cursor1, before: cursor4 })
539
+ assert_equal [StarWars::Base.find(2), StarWars::Base.find(3)], connection.edge_nodes
540
+
541
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(2))
542
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(3))
543
+
544
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { last: 2, before: cursor4 })
545
+ assert_equal [StarWars::Base.find(2), StarWars::Base.find(3)], connection.edge_nodes
546
+
547
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(2))
548
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(3))
549
+
550
+
551
+ # Limit and offset
552
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2).limit(3), { first: 2 })
553
+ assert_equal [StarWars::Base.find(3), StarWars::Base.find(4)], connection.edge_nodes
554
+
555
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2).limit(3), { first: 2, last: 1 })
556
+ assert_equal [StarWars::Base.find(4)], connection.edge_nodes
557
+
558
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2).limit(3), { first: 2, last: 5 })
559
+ assert_equal [StarWars::Base.find(3), StarWars::Base.find(4)], connection.edge_nodes
560
+
561
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2).limit(3), { first: 1, last: 5 })
562
+ assert_equal [StarWars::Base.find(3)], connection.edge_nodes
563
+
564
+ end
565
+ end
566
+ end
403
567
  end
@@ -701,6 +701,90 @@ SCHEMA
701
701
  end
702
702
  end
703
703
 
704
+ describe "executable schema with resolver maps" do
705
+ class Something
706
+ def capitalize(args)
707
+ args[:word].upcase
708
+ end
709
+ end
710
+
711
+ let(:definition) {
712
+ <<-GRAPHQL
713
+ scalar Date
714
+ scalar UndefinedScalar
715
+ type Something { capitalize(word:String!): String }
716
+ type A { a: String }
717
+ type B { b: String }
718
+ union Thing = A | B
719
+ type Query {
720
+ hello: Something
721
+ thing: Thing
722
+ add_week(in: Date!): Date!
723
+ undefined_scalar(str: String, int: Int): UndefinedScalar
724
+ }
725
+ GRAPHQL
726
+ }
727
+
728
+ let(:resolvers) {
729
+ {
730
+ Date: {
731
+ coerce_input: ->(val, ctx) {
732
+ Time.at(Float(val))
733
+ },
734
+ coerce_result: ->(val, ctx) {
735
+ val.to_f
736
+ }
737
+ },
738
+ resolve_type: ->(obj, ctx) {
739
+ return ctx.schema.types['A']
740
+ },
741
+ Query: {
742
+ add_week: ->(o,a,c) {
743
+ raise "No Time" unless a[:in].is_a? Time
744
+ a[:in]
745
+ },
746
+ hello: ->(o,a,c) {
747
+ Something.new
748
+ },
749
+ thing: ->(o,a,c) {
750
+ OpenStruct.new({a: "a"})
751
+ },
752
+ undefined_scalar: ->(o,a,c) {
753
+ a.values.first
754
+ }
755
+ }
756
+ }
757
+ }
758
+
759
+ let(:schema) { GraphQL::Schema.from_definition(definition, default_resolve: resolvers) }
760
+
761
+ it "resolves unions" do
762
+ result = schema.execute("query { thing { ... on A { a } } }")
763
+ assert_equal(result.to_json,'{"data":{"thing":{"a":"a"}}}')
764
+ end
765
+
766
+ it "resolves scalars" do
767
+ result = schema.execute("query { add_week(in: 392277600.0) }")
768
+ assert_equal(result.to_json,'{"data":{"add_week":392277600.0}}')
769
+ end
770
+
771
+ it "passes args from graphql to the object" do
772
+ result = schema.execute("query { hello { capitalize(word: \"hello\") }}")
773
+ assert_equal(result.to_json,'{"data":{"hello":{"capitalize":"HELLO"}}}')
774
+ end
775
+
776
+ it "handles undefined scalar resolution with identity function" do
777
+ result = schema.execute <<-GRAPHQL
778
+ {
779
+ str: undefined_scalar(str: "abc")
780
+ int: undefined_scalar(int: 123)
781
+ }
782
+ GRAPHQL
783
+
784
+ assert_equal({ "str" => "abc", "int" => 123 }, result["data"])
785
+ end
786
+ end
787
+
704
788
  describe "executable schemas from string" do
705
789
  let(:schema_defn) {
706
790
  <<-GRAPHQL
@@ -738,7 +822,7 @@ SCHEMA
738
822
  assert_equal(result.to_json, '{"data":{"allTodos":[{"text":"Pay the bills.","from_context":null},{"text":"Buy Milk","from_context":"bar"}]}}')
739
823
  end
740
824
 
741
- describe "hash of resolvers" do
825
+ describe "hash of resolvers with defaults" do
742
826
  let(:todos) { [Todo.new("Pay the bills.")] }
743
827
  let(:schema) { GraphQL::Schema.from_definition(schema_defn, default_resolve: resolve_hash) }
744
828
  let(:resolve_hash) {
@@ -753,26 +837,16 @@ SCHEMA
753
837
  }
754
838
  h
755
839
  }
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
840
 
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
841
+ let(:base_hash) {
842
+ # Fallback is to resolve by sending the field name
843
+ Hash.new { |h, k| h[k] = Hash.new { |h2, k2| ->(obj, args, ctx) { obj.public_send(k2) } } }
844
+ }
768
845
 
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
846
+ it "accepts a hash of resolve functions" do
847
+ schema.execute("mutation { todoAdd: todo_add(text: \"Buy Milk\") { text } }", context: {context_value: "bar"}, root_value: todos)
848
+ result = schema.execute("query { allTodos: all_todos { text, from_context } }", root_value: todos)
849
+ assert_equal(result.to_json, '{"data":{"allTodos":[{"text":"Pay the bills.","from_context":null},{"text":"Buy Milk","from_context":"bar"}]}}')
776
850
  end
777
851
  end
778
852
 
@@ -103,11 +103,25 @@ module MaskHelpers
103
103
  end
104
104
  end
105
105
 
106
+ module FilterInstrumentation
107
+ def self.before_query(query)
108
+ if query.context[:filters]
109
+ query.merge_filters(
110
+ only: query.context[:filters][:only],
111
+ except: query.context[:filters][:except],
112
+ )
113
+ end
114
+ end
115
+
116
+ def self.after_query(q); end
117
+ end
118
+
106
119
  Schema = GraphQL::Schema.define do
107
120
  query QueryType
108
121
  mutation MutationType
109
122
  subscription MutationType
110
123
  resolve_type ->(obj, ctx) { PhonemeType }
124
+ instrument :query, FilterInstrumentation
111
125
  end
112
126
 
113
127
  module Data
@@ -591,4 +605,70 @@ describe GraphQL::Schema::Warden do
591
605
  refute_includes enum_values, "TRILL"
592
606
  end
593
607
  end
608
+
609
+ describe "multiple filters" do
610
+ let(:visible_enum_value) { ->(member, ctx) { !member.metadata[:hidden_enum_value] } }
611
+ let(:visible_abstract_type) { ->(member, ctx) { !member.metadata[:hidden_abstract_type] } }
612
+ let(:hidden_input_object) { ->(member, ctx) { member.metadata[:hidden_input_object_type] } }
613
+ let(:hidden_type) { ->(member, ctx) { member.metadata[:hidden_type] } }
614
+
615
+ let(:query_str) { <<-GRAPHQL
616
+ {
617
+ enum: __type(name: "Manner") { enumValues { name } }
618
+ input: __type(name: "WithinInput") { name }
619
+ abstractType: __type(name: "Grapheme") { interfaces { name } }
620
+ type: __type(name: "Phoneme") { name }
621
+ }
622
+ GRAPHQL
623
+ }
624
+
625
+ describe "multiple filters for execution" do
626
+ it "applies all of them" do
627
+ res = MaskHelpers.run_query(
628
+ query_str,
629
+ only: [visible_enum_value, visible_abstract_type],
630
+ except: [hidden_input_object, hidden_type],
631
+ )
632
+ assert_equal nil, res["data"]["input"]
633
+ enum_values = res["data"]["enum"]["enumValues"].map { |v| v["name"] }
634
+ assert_equal 5, enum_values.length
635
+ refute_includes enum_values, "TRILL"
636
+ # These are also filtered out:
637
+ assert_equal 0, res["data"]["abstractType"]["interfaces"].length
638
+ assert_equal nil, res["data"]["type"]
639
+ end
640
+ end
641
+
642
+ describe "adding filters in instrumentation" do
643
+ it "applies only/except filters" do
644
+ filters = {
645
+ only: visible_enum_value,
646
+ except: hidden_input_object,
647
+ }
648
+ res = MaskHelpers.run_query(query_str, context: { filters: filters })
649
+ assert_equal nil, res["data"]["input"]
650
+ enum_values = res["data"]["enum"]["enumValues"].map { |v| v["name"] }
651
+ assert_equal 5, enum_values.length
652
+ refute_includes enum_values, "TRILL"
653
+ # These are unaffected:
654
+ assert_includes res["data"]["abstractType"]["interfaces"].map { |i| i["name"] }, "LanguageMember"
655
+ assert_equal "Phoneme", res["data"]["type"]["name"]
656
+ end
657
+
658
+ it "applies multiple filters" do
659
+ filters = {
660
+ only: [visible_enum_value, visible_abstract_type],
661
+ except: [hidden_input_object, hidden_type],
662
+ }
663
+ res = MaskHelpers.run_query(query_str, context: { filters: filters })
664
+ assert_equal nil, res["data"]["input"]
665
+ enum_values = res["data"]["enum"]["enumValues"].map { |v| v["name"] }
666
+ assert_equal 5, enum_values.length
667
+ refute_includes enum_values, "TRILL"
668
+ # These are also filtered out:
669
+ assert_equal 0, res["data"]["abstractType"]["interfaces"].length
670
+ assert_equal nil, res["data"]["type"]
671
+ end
672
+ end
673
+ end
594
674
  end