graphql 1.5.15 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql.rb +4 -19
- data/lib/graphql/analysis/analyze_query.rb +27 -2
- data/lib/graphql/analysis/query_complexity.rb +10 -11
- data/lib/graphql/argument.rb +7 -6
- data/lib/graphql/backwards_compatibility.rb +47 -0
- data/lib/graphql/compatibility/execution_specification.rb +14 -0
- data/lib/graphql/compatibility/execution_specification/specification_schema.rb +6 -1
- data/lib/graphql/compatibility/lazy_execution_specification.rb +19 -0
- data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +15 -6
- data/lib/graphql/directive.rb +1 -6
- data/lib/graphql/execution.rb +1 -0
- data/lib/graphql/execution/execute.rb +174 -160
- data/lib/graphql/execution/field_result.rb +5 -1
- data/lib/graphql/execution/lazy.rb +2 -2
- data/lib/graphql/execution/lazy/resolve.rb +8 -11
- data/lib/graphql/execution/multiplex.rb +134 -0
- data/lib/graphql/execution/selection_result.rb +5 -0
- data/lib/graphql/field.rb +1 -8
- data/lib/graphql/filter.rb +53 -0
- data/lib/graphql/internal_representation/node.rb +11 -6
- data/lib/graphql/internal_representation/rewrite.rb +3 -3
- data/lib/graphql/query.rb +160 -78
- data/lib/graphql/query/arguments.rb +14 -25
- data/lib/graphql/query/arguments_cache.rb +6 -13
- data/lib/graphql/query/context.rb +28 -10
- data/lib/graphql/query/executor.rb +1 -0
- data/lib/graphql/query/literal_input.rb +10 -4
- data/lib/graphql/query/null_context.rb +1 -1
- data/lib/graphql/query/serial_execution/field_resolution.rb +5 -1
- data/lib/graphql/query/validation_pipeline.rb +12 -7
- data/lib/graphql/query/variables.rb +1 -1
- data/lib/graphql/rake_task.rb +140 -0
- data/lib/graphql/relay/array_connection.rb +29 -48
- data/lib/graphql/relay/base_connection.rb +9 -7
- data/lib/graphql/relay/mutation.rb +0 -11
- data/lib/graphql/relay/mutation/instrumentation.rb +2 -2
- data/lib/graphql/relay/mutation/resolve.rb +7 -10
- data/lib/graphql/relay/relation_connection.rb +98 -61
- data/lib/graphql/scalar_type.rb +1 -15
- data/lib/graphql/schema.rb +90 -25
- data/lib/graphql/schema/build_from_definition.rb +22 -23
- data/lib/graphql/schema/build_from_definition/resolve_map.rb +70 -0
- data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +47 -0
- data/lib/graphql/schema/middleware_chain.rb +1 -1
- data/lib/graphql/schema/printer.rb +2 -1
- data/lib/graphql/schema/timeout_middleware.rb +6 -6
- data/lib/graphql/schema/type_map.rb +1 -1
- data/lib/graphql/schema/warden.rb +5 -9
- data/lib/graphql/static_validation/definition_dependencies.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/analysis/analyze_query_spec.rb +2 -2
- data/spec/graphql/analysis/max_query_complexity_spec.rb +28 -0
- data/spec/graphql/argument_spec.rb +3 -3
- data/spec/graphql/execution/lazy_spec.rb +8 -114
- data/spec/graphql/execution/multiplex_spec.rb +131 -0
- data/spec/graphql/internal_representation/rewrite_spec.rb +10 -0
- data/spec/graphql/query/arguments_spec.rb +14 -16
- data/spec/graphql/query/context_spec.rb +14 -1
- data/spec/graphql/query/literal_input_spec.rb +19 -13
- data/spec/graphql/query/variables_spec.rb +1 -1
- data/spec/graphql/query_spec.rb +12 -1
- data/spec/graphql/rake_task_spec.rb +57 -0
- data/spec/graphql/relay/array_connection_spec.rb +24 -3
- data/spec/graphql/relay/connection_instrumentation_spec.rb +23 -0
- data/spec/graphql/relay/mutation_spec.rb +2 -10
- data/spec/graphql/relay/page_info_spec.rb +2 -2
- data/spec/graphql/relay/relation_connection_spec.rb +167 -3
- data/spec/graphql/schema/build_from_definition_spec.rb +93 -19
- data/spec/graphql/schema/warden_spec.rb +80 -0
- data/spec/graphql/schema_spec.rb +26 -2
- data/spec/spec_helper.rb +4 -2
- data/spec/support/lazy_helpers.rb +152 -0
- data/spec/support/star_wars/schema.rb +23 -0
- metadata +28 -3
- 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("
|
79
|
-
assert_equal("
|
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(
|
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
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
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
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
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
|