graphql 1.5.4 → 1.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/define/instance_definable.rb +54 -29
  3. data/lib/graphql/enum_type.rb +0 -1
  4. data/lib/graphql/execution/execute.rb +15 -9
  5. data/lib/graphql/execution/field_result.rb +3 -6
  6. data/lib/graphql/execution/lazy/lazy_method_map.rb +66 -12
  7. data/lib/graphql/language.rb +15 -1
  8. data/lib/graphql/language/nodes.rb +17 -1
  9. data/lib/graphql/language/parser.rb +8 -8
  10. data/lib/graphql/language/parser.y +8 -8
  11. data/lib/graphql/query.rb +3 -4
  12. data/lib/graphql/query/literal_input.rb +2 -0
  13. data/lib/graphql/relay/mutation.rb +2 -3
  14. data/lib/graphql/schema.rb +1 -3
  15. data/lib/graphql/schema/loader.rb +26 -4
  16. data/lib/graphql/static_validation/all_rules.rb +1 -0
  17. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  18. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +32 -0
  19. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +11 -5
  20. data/lib/graphql/static_validation/validation_context.rb +0 -1
  21. data/lib/graphql/version.rb +1 -1
  22. data/spec/graphql/define/instance_definable_spec.rb +21 -0
  23. data/spec/graphql/execution/execute_spec.rb +61 -0
  24. data/spec/graphql/execution/lazy/lazy_method_map_spec.rb +57 -0
  25. data/spec/graphql/input_object_type_spec.rb +2 -2
  26. data/spec/graphql/introspection/input_value_type_spec.rb +3 -1
  27. data/spec/graphql/introspection/type_type_spec.rb +1 -0
  28. data/spec/graphql/query_spec.rb +69 -10
  29. data/spec/graphql/schema/loader_spec.rb +7 -3
  30. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +0 -1
  31. data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +6 -6
  32. data/spec/graphql/static_validation/rules/no_definitions_are_present_spec.rb +28 -0
  33. data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +17 -0
  34. data/spec/spec_helper.rb +2 -1
  35. data/spec/support/dummy/schema.rb +11 -0
  36. data/spec/support/star_wars/data.rb +16 -2
  37. metadata +21 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eacb69bcf39a028dc32debd2a0f266a6f920fd3d
4
- data.tar.gz: 921436c519b6b6b8aff3e4dc03845f8d6108a275
3
+ metadata.gz: ef472807dc31f5d5bc215491e5c4a512d853b517
4
+ data.tar.gz: 151d4556ac7c07a270e37e40a71616dfad89129b
5
5
  SHA512:
6
- metadata.gz: b8e1d965b82383e8cee0423fa06b7f20ea5fde13bedb8e9cd2316686c7f7e72dd409a55667b81ea9f0e502906bee45e7957cbdd79c44e96618a405bebf81fc98
7
- data.tar.gz: 56c92576bee82ce626d332df1070079579c02007ad78718831301de4c815a6d61c57a17464e71498d9e6a4c51412e80de50e2090aca40fed8266b282b6c12a58
6
+ metadata.gz: 825fdd6d9aac63cf8904e1a52be462499286063ccab8799497affabf52d1a0b8b2afe090e5fef84a564c087803ca4b4b38d4dc7fa898e145047e749aa1d0e12c
7
+ data.tar.gz: 43c0d734ac9014bd7df9f9e26e70052e62f2ae0d7a84b59642622e2786ffd806fbc076638411ce4621b2dd355c7479ac05c7e9fed8993701126611c8ebe428d6
@@ -116,18 +116,7 @@ module GraphQL
116
116
  def define(**kwargs, &block)
117
117
  # make sure the previous definition_proc was executed:
118
118
  ensure_defined
119
-
120
- method_names = self.class.ensure_defined_method_names
121
- @pending_methods = method_names.map { |n| self.class.instance_method(n) }
122
- self.singleton_class.class_eval do
123
- method_names.each do |method_name|
124
- define_method(method_name) { |*args, &block|
125
- ensure_defined
126
- self.send(method_name, *args, &block)
127
- }
128
- end
129
- end
130
-
119
+ stash_dependent_methods
131
120
  @pending_definition = Definition.new(kwargs, block)
132
121
  nil
133
122
  end
@@ -156,31 +145,67 @@ module GraphQL
156
145
  # @return [void]
157
146
  def ensure_defined
158
147
  if @pending_definition
159
-
160
148
  defn = @pending_definition
161
149
  @pending_definition = nil
162
150
 
163
- pending_methods = @pending_methods
164
- self.singleton_class.class_eval {
165
- pending_methods.each do |method|
166
- define_method(method.name, method)
167
- end
168
- }
169
- @pending_methods = nil
151
+ revive_dependent_methods
170
152
 
171
- defn_proxy = DefinedObjectProxy.new(self)
172
- # Apply definition from `define(...)` kwargs
173
- defn.define_keywords.each do |keyword, value|
174
- defn_proxy.public_send(keyword, value)
175
- end
176
- # and/or apply definition from `define { ... }` block
177
- if defn.define_proc
178
- defn_proxy.instance_eval(&defn.define_proc)
153
+ begin
154
+ defn_proxy = DefinedObjectProxy.new(self)
155
+ # Apply definition from `define(...)` kwargs
156
+ defn.define_keywords.each do |keyword, value|
157
+ defn_proxy.public_send(keyword, value)
158
+ end
159
+ # and/or apply definition from `define { ... }` block
160
+ if defn.define_proc
161
+ defn_proxy.instance_eval(&defn.define_proc)
162
+ end
163
+ rescue StandardError
164
+ # The definition block failed to run, so make this object pending again:
165
+ stash_dependent_methods
166
+ @pending_definition = defn
167
+ raise
179
168
  end
169
+ end
170
+ nil
171
+ end
180
172
 
173
+ # Take the pending methods and put them back on this object's singleton class.
174
+ # This reverts the process done by {#stash_dependent_methods}
175
+ # @return [void]
176
+ def revive_dependent_methods
177
+ pending_methods = @pending_methods
178
+ self.singleton_class.class_eval {
179
+ pending_methods.each do |method|
180
+ define_method(method.name, method)
181
+ end
182
+ }
183
+ @pending_methods = nil
184
+ end
181
185
 
186
+ # Find the method names which were declared as definition-dependent,
187
+ # then grab the method definitions off of this object's class
188
+ # and store them for later.
189
+ #
190
+ # Then make a dummy method for each of those method names which:
191
+ #
192
+ # - Triggers the pending definition, if there is one
193
+ # - Calls the same method again.
194
+ #
195
+ # It's assumed that {#ensure_defined} will put the original method definitions
196
+ # back in place with {#revive_dependent_methods}.
197
+ # @return [void]
198
+ def stash_dependent_methods
199
+ method_names = self.class.ensure_defined_method_names
200
+ @pending_methods = method_names.map { |n| self.class.instance_method(n) }
201
+ self.singleton_class.class_eval do
202
+ method_names.each do |method_name|
203
+ define_method(method_name) { |*args, &block|
204
+ ensure_defined
205
+ self.send(method_name, *args, &block)
206
+ }
207
+ end
182
208
  end
183
- nil
184
209
  end
185
210
 
186
211
  class Definition
@@ -85,7 +85,6 @@ module GraphQL
85
85
  self.values = other.values.values
86
86
  end
87
87
 
88
-
89
88
  # @param new_values [Array<EnumValue>] The set of values contained in this type
90
89
  def values=(new_values)
91
90
  @values_by_name = {}
@@ -72,22 +72,22 @@ module GraphQL
72
72
 
73
73
  result = if query.schema.lazy?(raw_value)
74
74
  field.prepare_lazy(raw_value, arguments, field_ctx).then { |inner_value|
75
- continue_resolve_field(selection, parent_type, field, inner_value, field_ctx)
75
+ continue_resolve_field(owner, selection, parent_type, field, inner_value, field_ctx)
76
76
  }
77
77
  elsif raw_value.is_a?(GraphQL::Execution::Lazy)
78
78
  # It came from a connection resolve, assume it was already instrumented
79
79
  raw_value.then { |inner_value|
80
- continue_resolve_field(selection, parent_type, field, inner_value, field_ctx)
80
+ continue_resolve_field(owner, selection, parent_type, field, inner_value, field_ctx)
81
81
  }
82
82
  else
83
- continue_resolve_field(selection, parent_type, field, raw_value, field_ctx)
83
+ continue_resolve_field(owner, selection, parent_type, field, raw_value, field_ctx)
84
84
  end
85
85
 
86
86
  case result
87
87
  when PROPAGATE_NULL, GraphQL::Execution::Lazy, SelectionResult
88
88
  FieldResult.new(
89
89
  owner: owner,
90
- field: field,
90
+ type: field.type,
91
91
  value: result,
92
92
  )
93
93
  else
@@ -95,7 +95,7 @@ module GraphQL
95
95
  end
96
96
  end
97
97
 
98
- def continue_resolve_field(selection, parent_type, field, raw_value, field_ctx)
98
+ def continue_resolve_field(owner, selection, parent_type, field, raw_value, field_ctx)
99
99
  query = field_ctx.query
100
100
 
101
101
  case raw_value
@@ -115,6 +115,7 @@ module GraphQL
115
115
  end
116
116
 
117
117
  resolve_value(
118
+ owner,
118
119
  parent_type,
119
120
  field,
120
121
  field.type,
@@ -124,7 +125,7 @@ module GraphQL
124
125
  )
125
126
  end
126
127
 
127
- def resolve_value(parent_type, field_defn, field_type, value, selection, field_ctx)
128
+ def resolve_value(owner, parent_type, field_defn, field_type, value, selection, field_ctx)
128
129
  if value.nil?
129
130
  if field_type.kind.non_null?
130
131
  type_error = GraphQL::InvalidNullError.new(parent_type, field_defn, value)
@@ -146,7 +147,7 @@ module GraphQL
146
147
  when GraphQL::TypeKinds::ENUM
147
148
  field_type.coerce_result(value, field_ctx.query.warden)
148
149
  when GraphQL::TypeKinds::LIST
149
- wrapped_type = field_type.of_type
150
+ inner_type = field_type.of_type
150
151
  i = 0
151
152
  result = []
152
153
  value.each do |inner_value|
@@ -157,20 +158,24 @@ module GraphQL
157
158
  field: field_defn,
158
159
  )
159
160
 
160
- result << resolve_value(
161
+ inner_result = resolve_value(
162
+ owner,
161
163
  parent_type,
162
164
  field_defn,
163
- wrapped_type,
165
+ inner_type,
164
166
  inner_value,
165
167
  selection,
166
168
  inner_ctx,
167
169
  )
170
+
171
+ result << GraphQL::Execution::FieldResult.new(type: inner_type, owner: owner, value: inner_result)
168
172
  i += 1
169
173
  end
170
174
  result
171
175
  when GraphQL::TypeKinds::NON_NULL
172
176
  wrapped_type = field_type.of_type
173
177
  inner_value = resolve_value(
178
+ owner,
174
179
  parent_type,
175
180
  field_defn,
176
181
  wrapped_type,
@@ -196,6 +201,7 @@ module GraphQL
196
201
  PROPAGATE_NULL
197
202
  else
198
203
  resolve_value(
204
+ owner,
199
205
  parent_type,
200
206
  field_defn,
201
207
  resolved_type,
@@ -7,14 +7,11 @@ module GraphQL
7
7
  # @return [Any, Lazy] the GraphQL-ready response value, or a {Lazy} instance
8
8
  attr_reader :value
9
9
 
10
- # @return [GraphQL::Field] The field which resolved this value
11
- attr_reader :field
12
-
13
10
  # @return [SelectionResult] The result object that this field belongs to
14
11
  attr_reader :owner
15
12
 
16
- def initialize(field:, value:, owner:)
17
- @field = field
13
+ def initialize(type:, value:, owner:)
14
+ @type = type
18
15
  @owner = owner
19
16
  self.value = value
20
17
  end
@@ -35,7 +32,7 @@ module GraphQL
35
32
  end
36
33
 
37
34
  if new_value == GraphQL::Execution::Execute::PROPAGATE_NULL
38
- if field.type.kind.non_null?
35
+ if @type.kind.non_null?
39
36
  @owner.propagate_null
40
37
  else
41
38
  @value = nil
@@ -1,22 +1,27 @@
1
1
  # frozen_string_literal: true
2
+ require 'thread'
3
+ begin
4
+ require 'concurrent'
5
+ rescue LoadError
6
+ # no problem, we'll fallback to our own map
7
+ end
8
+
2
9
  module GraphQL
3
10
  module Execution
4
11
  class Lazy
5
12
  # {GraphQL::Schema} uses this to match returned values to lazy resolution methods.
6
13
  # Methods may be registered for classes, they apply to its subclasses also.
7
14
  # The result of this lookup is cached for future resolutions.
15
+ # Instances of this class are thread-safe.
8
16
  # @api private
9
17
  # @see {Schema#lazy?} looks up values from this map
10
18
  class LazyMethodMap
11
- def initialize
12
- @storage = Hash.new do |h, value_class|
13
- registered_superclass = h.each_key.find { |lazy_class| value_class < lazy_class }
14
- if registered_superclass.nil?
15
- h[value_class] = nil
16
- else
17
- h[value_class] = h[registered_superclass]
18
- end
19
- end
19
+ def initialize(use_concurrent: defined?(Concurrent::Map))
20
+ @storage = use_concurrent ? Concurrent::Map.new : ConcurrentishMap.new
21
+ end
22
+
23
+ def initialize_copy(other)
24
+ @storage = other.storage.dup
20
25
  end
21
26
 
22
27
  # @param lazy_class [Class] A class which represents a lazy value (subclasses may also be used)
@@ -28,11 +33,60 @@ module GraphQL
28
33
  # @param value [Object] an object which may have a `lazy_value_method` registered for its class or superclasses
29
34
  # @return [Symbol, nil] The `lazy_value_method` for this object, or nil
30
35
  def get(value)
31
- @storage[value.class]
36
+ @storage.compute_if_absent(value.class) { find_superclass_method(value.class) }
32
37
  end
33
38
 
34
- def each
35
- @storage.each { |k, v| yield(k,v) }
39
+ protected
40
+
41
+ attr_reader :storage
42
+
43
+ private
44
+
45
+ def find_superclass_method(value_class)
46
+ @storage.each { |lazy_class, lazy_value_method|
47
+ return lazy_value_method if value_class < lazy_class
48
+ }
49
+ nil
50
+ end
51
+
52
+ # Mock the Concurrent::Map API
53
+ class ConcurrentishMap
54
+ extend Forwardable
55
+ # Technically this should be under the mutex too,
56
+ # but I know it's only used when the lock is already acquired.
57
+ def_delegators :@storage, :each, :size
58
+
59
+ def initialize
60
+ @semaphore = Mutex.new
61
+ # Access to this hash must always be managed by the mutex
62
+ # since it may be modified at runtime
63
+ @storage = {}
64
+ end
65
+
66
+ def []=(key, value)
67
+ @semaphore.synchronize {
68
+ @storage[key] = value
69
+ }
70
+ end
71
+
72
+ def compute_if_absent(key)
73
+ @semaphore.synchronize {
74
+ @storage.fetch(key) { @storage[key] = yield }
75
+ }
76
+ end
77
+
78
+ def initialize_copy(other)
79
+ @semaphore = Mutex.new
80
+ @storage = other.copy_storage
81
+ end
82
+
83
+ protected
84
+
85
+ def copy_storage
86
+ @semaphore.synchronize {
87
+ @storage.dup
88
+ }
89
+ end
36
90
  end
37
91
  end
38
92
  end
@@ -12,7 +12,21 @@ module GraphQL
12
12
  module Language
13
13
  # @api private
14
14
  def self.serialize(value)
15
- JSON.generate(value, quirks_mode: true)
15
+ if value.is_a?(Hash)
16
+ serialized_hash = value.map do |k, v|
17
+ "#{k}:#{serialize v}"
18
+ end.join(",")
19
+
20
+ "{#{serialized_hash}}"
21
+ elsif value.is_a?(Array)
22
+ serialized_array = value.map do |v|
23
+ serialize v
24
+ end.join(",")
25
+
26
+ "[#{serialized_array}]"
27
+ else
28
+ JSON.generate(value, quirks_mode: true)
29
+ end
16
30
  end
17
31
  end
18
32
  end
@@ -268,10 +268,26 @@ module GraphQL
268
268
  def to_h(options={})
269
269
  arguments.inject({}) do |memo, pair|
270
270
  v = pair.value
271
- memo[pair.name] = v.is_a?(InputObject) ? v.to_h : v
271
+ memo[pair.name] = serialize_value_for_hash v
272
272
  memo
273
273
  end
274
274
  end
275
+
276
+ private
277
+
278
+ def serialize_value_for_hash(value)
279
+ if value.is_a? InputObject
280
+ value.to_h
281
+ elsif value.is_a? Array
282
+ value.map do |v|
283
+ serialize_value_for_hash v
284
+ end
285
+ elsif value.is_a? NullValue
286
+ nil
287
+ else
288
+ value
289
+ end
290
+ end
275
291
  end
276
292
 
277
293
 
@@ -1383,7 +1383,7 @@ module_eval(<<'.,.,', 'parser.y', 258)
1383
1383
 
1384
1384
  module_eval(<<'.,.,', 'parser.y', 267)
1385
1385
  def _reduce_106(val, _values, result)
1386
- return make_node(:SchemaDefinition, val[2])
1386
+ return make_node(:SchemaDefinition, position_source: val[0], **val[2])
1387
1387
  result
1388
1388
  end
1389
1389
  .,.,
@@ -1418,14 +1418,14 @@ module_eval(<<'.,.,', 'parser.y', 274)
1418
1418
 
1419
1419
  module_eval(<<'.,.,', 'parser.y', 284)
1420
1420
  def _reduce_116(val, _values, result)
1421
- return make_node(:ScalarTypeDefinition, name: val[1], directives: val[2], description: get_description(val[0]))
1421
+ return make_node(:ScalarTypeDefinition, name: val[1], directives: val[2], description: get_description(val[0]), position_source: val[0])
1422
1422
  result
1423
1423
  end
1424
1424
  .,.,
1425
1425
 
1426
1426
  module_eval(<<'.,.,', 'parser.y', 288)
1427
1427
  def _reduce_117(val, _values, result)
1428
- return make_node(:ObjectTypeDefinition, name: val[1], interfaces: val[2], directives: val[3], fields: val[5], description: get_description(val[0]))
1428
+ return make_node(:ObjectTypeDefinition, name: val[1], interfaces: val[2], directives: val[3], fields: val[5], description: get_description(val[0]), position_source: val[0])
1429
1429
 
1430
1430
  result
1431
1431
  end
@@ -1505,7 +1505,7 @@ module_eval(<<'.,.,', 'parser.y', 315)
1505
1505
 
1506
1506
  module_eval(<<'.,.,', 'parser.y', 319)
1507
1507
  def _reduce_128(val, _values, result)
1508
- return make_node(:InterfaceTypeDefinition, name: val[1], directives: val[2], fields: val[4], description: get_description(val[0]))
1508
+ return make_node(:InterfaceTypeDefinition, name: val[1], directives: val[2], fields: val[4], description: get_description(val[0]), position_source: val[0])
1509
1509
 
1510
1510
  result
1511
1511
  end
@@ -1527,7 +1527,7 @@ module_eval(<<'.,.,', 'parser.y', 324)
1527
1527
 
1528
1528
  module_eval(<<'.,.,', 'parser.y', 328)
1529
1529
  def _reduce_131(val, _values, result)
1530
- return make_node(:UnionTypeDefinition, name: val[1], directives: val[2], types: val[4], description: get_description(val[0]))
1530
+ return make_node(:UnionTypeDefinition, name: val[1], directives: val[2], types: val[4], description: get_description(val[0]), position_source: val[0])
1531
1531
 
1532
1532
  result
1533
1533
  end
@@ -1535,7 +1535,7 @@ module_eval(<<'.,.,', 'parser.y', 328)
1535
1535
 
1536
1536
  module_eval(<<'.,.,', 'parser.y', 333)
1537
1537
  def _reduce_132(val, _values, result)
1538
- return make_node(:EnumTypeDefinition, name: val[1], directives: val[2], values: val[4], description: get_description(val[0]))
1538
+ return make_node(:EnumTypeDefinition, name: val[1], directives: val[2], values: val[4], description: get_description(val[0]), position_source: val[0])
1539
1539
 
1540
1540
  result
1541
1541
  end
@@ -1543,7 +1543,7 @@ module_eval(<<'.,.,', 'parser.y', 333)
1543
1543
 
1544
1544
  module_eval(<<'.,.,', 'parser.y', 338)
1545
1545
  def _reduce_133(val, _values, result)
1546
- return make_node(:InputObjectTypeDefinition, name: val[1], directives: val[2], fields: val[4], description: get_description(val[0]))
1546
+ return make_node(:InputObjectTypeDefinition, name: val[1], directives: val[2], fields: val[4], description: get_description(val[0]), position_source: val[0])
1547
1547
 
1548
1548
  result
1549
1549
  end
@@ -1551,7 +1551,7 @@ module_eval(<<'.,.,', 'parser.y', 338)
1551
1551
 
1552
1552
  module_eval(<<'.,.,', 'parser.y', 343)
1553
1553
  def _reduce_134(val, _values, result)
1554
- return make_node(:DirectiveDefinition, name: val[2], arguments: val[3], locations: val[5], description: get_description(val[0]))
1554
+ return make_node(:DirectiveDefinition, name: val[2], arguments: val[3], locations: val[5], description: get_description(val[0]), position_source: val[0])
1555
1555
 
1556
1556
  result
1557
1557
  end