graphql 1.5.4 → 1.5.5

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.
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