graphql 1.6.1 → 1.6.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7468a99f3a243670d95dc1b6423d6de98aeacd20
4
- data.tar.gz: ac2dd7a0264557a07349df7a5a7c84eca070d27c
3
+ metadata.gz: dcc96523b4e6938e39aeae90c15728985618c95b
4
+ data.tar.gz: da73ad353f7891bf9f4146e8941200747a928c39
5
5
  SHA512:
6
- metadata.gz: 43b4b7f8618b984e00f6c646d3ed547f2cdbc0417ea83c8bed1461d10482d48ade39938facde49636c7f73f945b360eb964516bd8eeb648b03af4941db0098f1
7
- data.tar.gz: a82abfa0257bae1387fffb3fb06eead5010236b4670c9093db38e0343f81b9bccb08a3140887f4a109be67da773eb97593b669752cb17b124494bdf555fd27c4
6
+ metadata.gz: b709b3b6833dd60102f6bfa7ebc7efd7a1a36ef7af8b0aa5723b1b421cc4c14516ef2480d058c885d9fb9de34013c0e3d0e6d0bbaa22850df7ff2bf9c50535c8
7
+ data.tar.gz: 0a4b99378090cfbce6916a725ccadb8b91238cbeaf8a00c28e0fcbcedc43d1b8c238b3f2c502ece948e5379a2d33cb2265e8210c2e44ae466d570f177fb44059
@@ -40,7 +40,27 @@ module GraphQL
40
40
  run_queries(schema, queries, *rest)
41
41
  end
42
42
 
43
+ # @param schema [GraphQL::Schema]
44
+ # @param queries [Array<GraphQL::Query>]
45
+ # @param context [Hash]
46
+ # @param max_complexity [Integer]
47
+ # @return [Array<Hash>] One result per query
43
48
  def run_queries(schema, queries, context: {}, max_complexity: nil)
49
+ has_custom_strategy = schema.query_execution_strategy || schema.mutation_execution_strategy || schema.subscription_execution_strategy
50
+ if has_custom_strategy
51
+ if queries.length == 1
52
+ return [run_one_legacy(schema, queries.first)]
53
+ else
54
+ raise ArgumentError, "Multiplexing doesn't support custom execution strategies, run one query at a time instead"
55
+ end
56
+ else
57
+ run_as_multiplex(schema, queries, context: context, max_complexity: max_complexity)
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def run_as_multiplex(schema, queries, context:, max_complexity:)
44
64
  query_instrumenters = schema.instrumenters[:query]
45
65
  multiplex_instrumenters = schema.instrumenters[:multiplex]
46
66
  multiplex = self.new(schema: schema, queries: queries, context: context)
@@ -80,8 +100,6 @@ module GraphQL
80
100
  multiplex_instrumenters.reverse_each { |i| i.after_multiplex(multiplex) }
81
101
  end
82
102
 
83
- private
84
-
85
103
  # @param query [GraphQL::Query]
86
104
  # @return [Hash] The initial result (may not be finished if there are lazy values)
87
105
  def begin_query(query)
@@ -128,6 +146,24 @@ module GraphQL
128
146
  result
129
147
  end
130
148
  end
149
+
150
+ # use the old `query_execution_strategy` etc to run this query
151
+ def run_one_legacy(schema, query)
152
+ instrumenters = schema.instrumenters[:query]
153
+ instrumenters.each { |i| i.before_query(query) }
154
+ query.result = if !query.valid?
155
+ all_errors = query.validation_errors + query.analysis_errors + query.context.errors
156
+ if all_errors.any?
157
+ { "errors" => all_errors.map(&:to_h) }
158
+ else
159
+ nil
160
+ end
161
+ else
162
+ GraphQL::Query::Executor.new(query).result
163
+ end
164
+ ensure
165
+ instrumenters.reverse_each { |i| i.after_query(query) }
166
+ end
131
167
  end
132
168
  end
133
169
  end
data/lib/graphql/query.rb CHANGED
@@ -188,7 +188,11 @@ module GraphQL
188
188
  end
189
189
 
190
190
  def mutation?
191
- @mutation
191
+ with_prepared_ast { @mutation }
192
+ end
193
+
194
+ def query?
195
+ with_prepared_ast { @query }
192
196
  end
193
197
 
194
198
  # @return [void]
@@ -260,6 +264,7 @@ module GraphQL
260
264
  end
261
265
  @ast_variables = @selected_operation.variables
262
266
  @mutation = @selected_operation.operation_type == "mutation"
267
+ @query = @selected_operation.operation_type == "query"
263
268
  end
264
269
  end
265
270
 
@@ -8,7 +8,6 @@ module GraphQL
8
8
  attr_reader :query
9
9
 
10
10
  def initialize(query)
11
- warn("Executor is deprecated; use Schema#execute")
12
11
  @query = query
13
12
  end
14
13
 
@@ -57,13 +57,13 @@ module GraphQL
57
57
  # @param parent [Object] The object which this collection belongs to
58
58
  # @param context [GraphQL::Query::Context] The context from the field being resolved
59
59
  def initialize(nodes, arguments, field: nil, max_page_size: nil, parent: nil, context: nil)
60
+ @context = context
60
61
  @nodes = nodes
61
62
  @arguments = arguments
62
- @max_page_size = max_page_size
63
63
  @field = field
64
64
  @parent = parent
65
- @context = context
66
65
  @encoder = context ? @context.schema.cursor_encoder : GraphQL::Schema::Base64Encoder
66
+ @max_page_size = max_page_size.nil? && context ? @context.schema.default_max_page_size : max_page_size
67
67
  end
68
68
 
69
69
  def encode(data)
@@ -83,19 +83,17 @@ module GraphQL
83
83
  end
84
84
 
85
85
  def relation_offset(relation)
86
- case relation
87
- when ActiveRecord::Relation
86
+ if relation.respond_to?(:offset_value)
88
87
  relation.offset_value
89
- when Sequel::Dataset
88
+ else
90
89
  relation.opts[:offset]
91
90
  end
92
91
  end
93
92
 
94
93
  def relation_limit(relation)
95
- case relation
96
- when ActiveRecord::Relation
94
+ if relation.respond_to?(:limit_value)
97
95
  relation.limit_value
98
- when Sequel::Dataset
96
+ else
99
97
  relation.opts[:limit]
100
98
  end
101
99
  end
@@ -55,7 +55,7 @@ module GraphQL
55
55
  accepts_definitions \
56
56
  :query, :mutation, :subscription,
57
57
  :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
58
- :max_depth, :max_complexity,
58
+ :max_depth, :max_complexity, :default_max_page_size,
59
59
  :orphan_types, :resolve_type, :type_error, :parse_error,
60
60
  :raise_definition_error,
61
61
  :object_from_id, :id_from_object,
@@ -77,7 +77,7 @@ module GraphQL
77
77
  attr_accessor \
78
78
  :query, :mutation, :subscription,
79
79
  :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
80
- :max_depth, :max_complexity,
80
+ :max_depth, :max_complexity, :default_max_page_size,
81
81
  :orphan_types, :directives,
82
82
  :query_analyzers, :multiplex_analyzers, :instrumenters, :lazy_methods,
83
83
  :cursor_encoder,
@@ -98,7 +98,7 @@ module GraphQL
98
98
  GraphQL::Filter.new(except: default_mask)
99
99
  end
100
100
 
101
- self.default_execution_strategy = GraphQL::Execution::Execute
101
+ self.default_execution_strategy = nil
102
102
 
103
103
  BUILT_IN_TYPES = Hash[[INT_TYPE, STRING_TYPE, FLOAT_TYPE, BOOLEAN_TYPE, ID_TYPE].map{ |type| [type.name, type] }]
104
104
  DIRECTIVES = [GraphQL::Directive::IncludeDirective, GraphQL::Directive::SkipDirective, GraphQL::Directive::DeprecatedDirective]
@@ -256,11 +256,22 @@ module GraphQL
256
256
 
257
257
  # Resolve field named `field_name` for type `parent_type`.
258
258
  # Handles dynamic fields `__typename`, `__type` and `__schema`, too
259
- # @see [GraphQL::Schema::Warden] Restricted access to members of a schema
259
+ # @param parent_type [String, GraphQL::BaseType]
260
+ # @param field_name [String]
260
261
  # @return [GraphQL::Field, nil] The field named `field_name` on `parent_type`
262
+ # @see [GraphQL::Schema::Warden] Restricted access to members of a schema
261
263
  def get_field(parent_type, field_name)
262
264
  with_definition_error_check do
263
- defined_field = @instrumented_field_map.get(parent_type.name, field_name)
265
+ parent_type_name = case parent_type
266
+ when GraphQL::BaseType
267
+ parent_type.name
268
+ when String
269
+ parent_type
270
+ else
271
+ raise "Unexpected parent_type: #{parent_type}"
272
+ end
273
+
274
+ defined_field = @instrumented_field_map.get(parent_type_name, field_name)
264
275
  if defined_field
265
276
  defined_field
266
277
  elsif field_name == "__typename"
@@ -15,18 +15,18 @@ module GraphQL
15
15
  # @example Rescue from not-found by telling the user
16
16
  # MySchema.rescue_from(ActiveRecord::RecordNotFound) { "An item could not be found" }
17
17
  #
18
- # @param error_class [Class] a class of error to rescue from
19
- # @yield [err] A handler to return a message for this error instance
18
+ # @param error_classes [Class] one or more classes of errors to rescue from
19
+ # @yield [err] A handler to return a message for these error instances
20
20
  # @yieldparam [Exception] an error that was rescued
21
21
  # @yieldreturn [String] message to put in GraphQL response
22
- def rescue_from(error_class, &block)
23
- rescue_table[error_class] = block
22
+ def rescue_from(*error_classes, &block)
23
+ error_classes.map{ |error_class| rescue_table[error_class] = block }
24
24
  end
25
25
 
26
- # Remove the handler for `error_class`
26
+ # Remove the handler for `error_classs`
27
27
  # @param error_class [Class] the error class whose handler should be removed
28
- def remove_handler(error_class)
29
- rescue_table.delete(error_class)
28
+ def remove_handler(*error_classes)
29
+ error_classes.map{ |error_class| rescue_table.delete(error_class) }
30
30
  end
31
31
 
32
32
  # Implement the requirement for {GraphQL::Schema::MiddlewareChain}
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.6.1"
3
+ VERSION = "1.6.2"
4
4
  end
@@ -500,6 +500,24 @@ describe GraphQL::Query do
500
500
  end
501
501
  end
502
502
 
503
+ describe "#mutation?" do
504
+ let(:query_string) { <<-GRAPHQL
505
+ query Q { __typename }
506
+ mutation M { pushValue(value: 1) }
507
+ GRAPHQL
508
+ }
509
+
510
+ it "returns true if the selected operation is a mutation" do
511
+ query_query = GraphQL::Query.new(schema, query_string, operation_name: "Q")
512
+ assert_equal false, query_query.mutation?
513
+ assert_equal true, query_query.query?
514
+
515
+ mutation_query = GraphQL::Query.new(schema, query_string, operation_name: "M")
516
+ assert_equal true, mutation_query.mutation?
517
+ assert_equal false, mutation_query.query?
518
+ end
519
+ end
520
+
503
521
  describe 'NullValue type arguments' do
504
522
  let(:schema_definition) {
505
523
  <<-GRAPHQL
@@ -578,4 +596,30 @@ describe GraphQL::Query do
578
596
  assert_kind_of GraphQL::InternalRepresentation::Node, query.internal_representation.fragment_definitions["dairyFields"]
579
597
  end
580
598
  end
599
+
600
+ describe "query_execution_strategy" do
601
+ let(:custom_execution_schema) { schema.redefine(query_execution_strategy: DummyStrategy) }
602
+
603
+ class DummyStrategy
604
+ def execute(ast_operation, root_type, query_object)
605
+ { "dummy" => true }
606
+ end
607
+ end
608
+
609
+ it "is used for running a query, if it's present and not the default" do
610
+ result = custom_execution_schema.execute(" { __typename }")
611
+ assert_equal({"data"=>{"dummy"=>true}}, result)
612
+ end
613
+
614
+ it "can't run a multiplex" do
615
+ err = assert_raises ArgumentError do
616
+ custom_execution_schema.multiplex([
617
+ {query: " { __typename }"},
618
+ {query: " { __typename }"},
619
+ ])
620
+ end
621
+ msg = "Multiplexing doesn't support custom execution strategies, run one query at a time instead"
622
+ assert_equal msg, err.message
623
+ end
624
+ end
581
625
  end
@@ -196,5 +196,65 @@ describe GraphQL::Relay::ArrayConnection do
196
196
  assert_equal(first_and_second_names, get_names(result))
197
197
  end
198
198
  end
199
+
200
+ describe "applying default_max_page_size" do
201
+ def get_names(result)
202
+ result["data"]["rebels"]["bases"]["edges"].map { |e| e["node"]["name"] }
203
+ end
204
+
205
+ def get_page_info(result)
206
+ result["data"]["rebels"]["bases"]["pageInfo"]
207
+ end
208
+
209
+ let(:query_string) {%|
210
+ query getShips($first: Int, $after: String, $last: Int, $before: String){
211
+ rebels {
212
+ bases: basesWithDefaultMaxLimitArray(first: $first, after: $after, last: $last, before: $before) {
213
+ edges {
214
+ cursor
215
+ node {
216
+ name
217
+ }
218
+ }
219
+ pageInfo {
220
+ hasNextPage
221
+ hasPreviousPage
222
+ }
223
+ }
224
+ }
225
+ }
226
+ |}
227
+
228
+ it "applies to queries by `first`" do
229
+ result = star_wars_query(query_string, "first" => 100)
230
+ assert_equal(["Yavin", "Echo Base", "Secret Hideout"], get_names(result))
231
+ assert_equal(true, get_page_info(result)["hasNextPage"])
232
+
233
+ # Max page size is applied _without_ `first`, also
234
+ result = star_wars_query(query_string)
235
+ assert_equal(["Yavin", "Echo Base", "Secret Hideout"], get_names(result))
236
+ assert_equal(false, get_page_info(result)["hasNextPage"], "hasNextPage is false when first is not specified")
237
+ end
238
+
239
+ it "applies to queries by `last`" do
240
+ last_cursor = "Ng=="
241
+
242
+ result = star_wars_query(query_string, "last" => 100, "before" => last_cursor)
243
+ assert_equal(["Secret Hideout", "Death Star", "Shield Generator"], get_names(result))
244
+ assert_equal(true, get_page_info(result)["hasPreviousPage"])
245
+
246
+ result = star_wars_query(query_string, "before" => last_cursor)
247
+ assert_equal(["Yavin", "Echo Base", "Secret Hideout"], get_names(result))
248
+ assert_equal(false, get_page_info(result)["hasPreviousPage"], "hasPreviousPage is false when last is not specified")
249
+
250
+ fourth_cursor = "NA=="
251
+ first_second_and_third_names = ["Yavin", "Echo Base", "Secret Hideout"]
252
+ result = star_wars_query(query_string, "last" => 100, "before" => fourth_cursor)
253
+ assert_equal(first_second_and_third_names, get_names(result))
254
+
255
+ result = star_wars_query(query_string, "before" => fourth_cursor)
256
+ assert_equal(first_second_and_third_names, get_names(result))
257
+ end
258
+ end
199
259
  end
200
260
  end
@@ -225,6 +225,65 @@ describe GraphQL::Relay::RelationConnection do
225
225
  assert_equal(first_and_second_names, get_names(result))
226
226
  end
227
227
  end
228
+
229
+ describe "applying default_max_page_size" do
230
+ let(:query_string) {%|
231
+ query getBases($first: Int, $after: String, $last: Int, $before: String){
232
+ empire {
233
+ bases: basesWithDefaultMaxLimitRelation(first: $first, after: $after, last: $last, before: $before) {
234
+ ... basesConnection
235
+ }
236
+ }
237
+ }
238
+
239
+ fragment basesConnection on BaseConnection {
240
+ edges {
241
+ cursor
242
+ node {
243
+ name
244
+ }
245
+ },
246
+ pageInfo {
247
+ hasNextPage
248
+ hasPreviousPage
249
+ startCursor
250
+ endCursor
251
+ }
252
+ }
253
+ |}
254
+
255
+ it "applies to queries by `first`" do
256
+ result = star_wars_query(query_string, "first" => 100)
257
+ assert_equal(3, result["data"]["empire"]["bases"]["edges"].size)
258
+ assert_equal(true, result["data"]["empire"]["bases"]["pageInfo"]["hasNextPage"])
259
+
260
+ # Max page size is applied _without_ `first`, also
261
+ result = star_wars_query(query_string)
262
+ assert_equal(3, result["data"]["empire"]["bases"]["edges"].size)
263
+ assert_equal(false, result["data"]["empire"]["bases"]["pageInfo"]["hasNextPage"], "hasNextPage is false when first is not specified")
264
+ end
265
+
266
+ it "applies to queries by `last`" do
267
+ second_to_last_three_names = ["Secret Hideout", "Death Star", "Shield Generator"]
268
+ first_second_and_third_names = ["Yavin", "Echo Base", "Secret Hideout"]
269
+
270
+ last_cursor = "Ng=="
271
+ result = star_wars_query(query_string, "last" => 100, "before" => last_cursor)
272
+ assert_equal(second_to_last_three_names, get_names(result))
273
+ assert_equal(true, result["data"]["empire"]["bases"]["pageInfo"]["hasPreviousPage"])
274
+
275
+ result = star_wars_query(query_string, "before" => last_cursor)
276
+ assert_equal(first_second_and_third_names, get_names(result))
277
+ assert_equal(false, result["data"]["empire"]["bases"]["pageInfo"]["hasPreviousPage"], "hasPreviousPage is false when last is not specified")
278
+
279
+ fourth_cursor = "NA=="
280
+ result = star_wars_query(query_string, "last" => 100, "before" => fourth_cursor)
281
+ assert_equal(first_second_and_third_names, get_names(result))
282
+
283
+ result = star_wars_query(query_string, "before" => fourth_cursor)
284
+ assert_equal(first_second_and_third_names, get_names(result))
285
+ end
286
+ end
228
287
  end
229
288
 
230
289
  describe "without a block" do
@@ -2,6 +2,7 @@
2
2
  require "spec_helper"
3
3
 
4
4
  class SpecExampleError < StandardError; end
5
+ class SecondSpecExampleError < StandardError; end
5
6
 
6
7
  describe GraphQL::Schema::RescueMiddleware do
7
8
  let(:error_middleware) { ->{ raise(error_class) } }
@@ -23,6 +24,20 @@ describe GraphQL::Schema::RescueMiddleware do
23
24
  assert_equal("there was an example error: SpecExampleError", result.message)
24
25
  assert_equal(GraphQL::ExecutionError, result.class)
25
26
  end
27
+
28
+ describe "with multiple error classes" do
29
+ let(:error_class) { SecondSpecExampleError }
30
+ let(:rescue_middleware) do
31
+ middleware = GraphQL::Schema::RescueMiddleware.new
32
+ middleware.rescue_from(SpecExampleError, SecondSpecExampleError) { |err| "there was an example error: #{err.class.name}" }
33
+ middleware
34
+ end
35
+
36
+ it "handles errors for all of the classes" do
37
+ result = middleware_chain.invoke([])
38
+ assert_equal("there was an example error: SecondSpecExampleError", result.message)
39
+ end
40
+ end
26
41
  end
27
42
 
28
43
  describe "unknown errors" do
@@ -31,4 +46,18 @@ describe GraphQL::Schema::RescueMiddleware do
31
46
  assert_raises(RuntimeError) { middleware_chain.invoke([]) }
32
47
  end
33
48
  end
49
+
50
+ describe "removing multiple error handlers" do
51
+ let(:error_class) { SpecExampleError }
52
+ let(:rescue_middleware) do
53
+ middleware = GraphQL::Schema::RescueMiddleware.new
54
+ middleware.rescue_from(SpecExampleError, SecondSpecExampleError) { |err| "there was an example error: #{err.class.name}" }
55
+ middleware.remove_handler(SpecExampleError, SecondSpecExampleError)
56
+ middleware
57
+ end
58
+
59
+ it "no longer handles those errors" do
60
+ assert_raises(SpecExampleError) { middleware_chain.invoke([]) }
61
+ end
62
+ end
34
63
  end
@@ -360,4 +360,13 @@ type Query {
360
360
  assert_equal result, JSON.parse(schema.to_json)
361
361
  end
362
362
  end
363
+
364
+ describe "#get_field" do
365
+ it "returns fields by type or type name" do
366
+ field = schema.get_field("Cheese", "id")
367
+ assert_instance_of GraphQL::Field, field
368
+ field_2 = schema.get_field(Dummy::CheeseType, "id")
369
+ assert_equal field, field_2
370
+ end
371
+ end
363
372
  end
@@ -96,7 +96,7 @@ module StarWars
96
96
 
97
97
  field :id, !types.ID, resolve: GraphQL::Relay::GlobalIdResolve.new(type: Faction)
98
98
  field :name, types.String
99
- connection :ships, ShipConnectionWithParentType do
99
+ connection :ships, ShipConnectionWithParentType, max_page_size: 1000 do
100
100
  resolve ->(obj, args, ctx) {
101
101
  all_ships = obj.ships.map {|ship_id| StarWars::DATA["Ship"][ship_id] }
102
102
  if args[:nameIncludes]
@@ -159,7 +159,15 @@ module StarWars
159
159
  resolve ->(object, args, context) { Base.all.to_a }
160
160
  end
161
161
 
162
- connection :basesAsSequelDataset, BaseConnectionWithTotalCountType do
162
+ connection :basesWithDefaultMaxLimitRelation, BaseType.connection_type do
163
+ resolve ->(object, args, context) { Base.all }
164
+ end
165
+
166
+ connection :basesWithDefaultMaxLimitArray, BaseType.connection_type do
167
+ resolve ->(object, args, context) { Base.all.to_a }
168
+ end
169
+
170
+ connection :basesAsSequelDataset, BaseConnectionWithTotalCountType, max_page_size: 1000 do
163
171
  argument :nameIncludes, types.String
164
172
  resolve ->(obj, args, ctx) {
165
173
  all_bases = SequelBase.where(faction_id: obj.id)
@@ -332,6 +340,7 @@ module StarWars
332
340
  Schema = GraphQL::Schema.define do
333
341
  query(QueryType)
334
342
  mutation(MutationType)
343
+ default_max_page_size 3
335
344
 
336
345
  resolve_type ->(object, ctx) {
337
346
  if object == :test_error
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.1
4
+ version: 1.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-28 00:00:00.000000000 Z
11
+ date: 2017-06-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips