graphql 1.5.9 → 1.5.10
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 +4 -4
- data/lib/graphql/input_object_type.rb +17 -6
- data/lib/graphql/relay/mutation.rb +5 -81
- data/lib/graphql/relay/mutation/instrumentation.rb +24 -0
- data/lib/graphql/relay/mutation/resolve.rb +52 -0
- data/lib/graphql/relay/mutation/result.rb +38 -0
- data/lib/graphql/schema.rb +1 -1
- data/lib/graphql/string_encoding_error.rb +1 -1
- data/lib/graphql/string_type.rb +4 -4
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/query/variables_spec.rb +26 -0
- data/spec/graphql/relay/mutation_spec.rb +15 -0
- data/spec/graphql/string_type_spec.rb +31 -7
- data/spec/support/star_wars/schema.rb +3 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e87f7a05371764ee8744616a781f61f103f761c
|
4
|
+
data.tar.gz: e8d2eafe80fbe962c1dd5c017201b82b86def0ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b83d8c335d98f4149a6ce531b6dc61254fc96bbb5fec35beb2c0fece8eb4fff58693cd7bd48fb89511da84d69cfcec5e639b64372add0c8070c5a9c871804af0
|
7
|
+
data.tar.gz: ef672d60a38c0e1b5ec87af598d7ec221e36c6511ed92f72840bb32be813a0d434d9a0580d2e57e18ad8ad48f486f150d36b72399dbba347a4915686148b1c4e
|
@@ -98,16 +98,27 @@ module GraphQL
|
|
98
98
|
GraphQL::Query::Arguments.new(input_values, argument_definitions: arguments)
|
99
99
|
end
|
100
100
|
|
101
|
+
# @api private
|
102
|
+
INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key, value object responding to `to_h` or `to_unsafe_h`."
|
103
|
+
|
101
104
|
def validate_non_null_input(input, ctx)
|
102
105
|
warden = ctx.warden
|
103
106
|
result = GraphQL::Query::InputValidationResult.new
|
104
107
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
108
|
+
# We're not actually _using_ the coerced result, we're just
|
109
|
+
# using these methods to make sure that the object will
|
110
|
+
# behave like a hash below, when we call `each` on it.
|
111
|
+
begin
|
112
|
+
input.to_h
|
113
|
+
rescue
|
114
|
+
begin
|
115
|
+
# Handle ActionController::Parameters:
|
116
|
+
input.to_unsafe_h
|
117
|
+
rescue
|
118
|
+
# We're not sure it'll act like a hash, so reject it:
|
119
|
+
result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
|
120
|
+
return result
|
121
|
+
end
|
111
122
|
end
|
112
123
|
|
113
124
|
|
@@ -1,4 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require "graphql/relay/mutation/instrumentation"
|
3
|
+
require "graphql/relay/mutation/resolve"
|
4
|
+
require "graphql/relay/mutation/result"
|
5
|
+
|
2
6
|
module GraphQL
|
3
7
|
module Relay
|
4
8
|
# Define a Relay mutation:
|
@@ -178,9 +182,7 @@ module GraphQL
|
|
178
182
|
end
|
179
183
|
|
180
184
|
def result_class
|
181
|
-
@result_class ||=
|
182
|
-
Result.define_subclass(self)
|
183
|
-
end
|
185
|
+
@result_class ||= Result.define_subclass(self)
|
184
186
|
end
|
185
187
|
|
186
188
|
private
|
@@ -193,84 +195,6 @@ module GraphQL
|
|
193
195
|
callable.method(:call).arity
|
194
196
|
end
|
195
197
|
end
|
196
|
-
|
197
|
-
# Use this when the mutation's return type was generated from `return_field`s.
|
198
|
-
# It delegates field lookups to the hash returned from `resolve`.
|
199
|
-
class Result
|
200
|
-
attr_reader :client_mutation_id
|
201
|
-
def initialize(client_mutation_id:, result:)
|
202
|
-
@client_mutation_id = client_mutation_id
|
203
|
-
result && result.each do |key, value|
|
204
|
-
self.public_send("#{key}=", value)
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
class << self
|
209
|
-
attr_accessor :mutation
|
210
|
-
end
|
211
|
-
|
212
|
-
def self.define_subclass(mutation_defn)
|
213
|
-
subclass = Class.new(self) do
|
214
|
-
attr_accessor(*mutation_defn.return_type.all_fields.map(&:name))
|
215
|
-
self.mutation = mutation_defn
|
216
|
-
end
|
217
|
-
subclass
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
module MutationInstrumentation
|
222
|
-
def self.instrument(type, field)
|
223
|
-
if field.mutation
|
224
|
-
new_resolve = MutationResolve.new(field.mutation, field.resolve_proc)
|
225
|
-
new_lazy_resolve = MutationResolve.new(field.mutation, field.lazy_resolve_proc)
|
226
|
-
field.redefine(resolve: new_resolve, lazy_resolve: new_lazy_resolve)
|
227
|
-
else
|
228
|
-
field
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
class MutationResolve
|
234
|
-
def initialize(mutation, resolve)
|
235
|
-
@mutation = mutation
|
236
|
-
@resolve = resolve
|
237
|
-
@wrap_result = mutation.has_generated_return_type?
|
238
|
-
end
|
239
|
-
|
240
|
-
def call(obj, args, ctx)
|
241
|
-
begin
|
242
|
-
mutation_result = @resolve.call(obj, args[:input], ctx)
|
243
|
-
rescue GraphQL::ExecutionError => err
|
244
|
-
mutation_result = err
|
245
|
-
end
|
246
|
-
|
247
|
-
if ctx.schema.lazy?(mutation_result)
|
248
|
-
mutation_result
|
249
|
-
else
|
250
|
-
build_result(mutation_result, args, ctx)
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
private
|
255
|
-
|
256
|
-
def build_result(mutation_result, args, ctx)
|
257
|
-
if mutation_result.is_a?(GraphQL::ExecutionError)
|
258
|
-
ctx.add_error(mutation_result)
|
259
|
-
mutation_result = nil
|
260
|
-
end
|
261
|
-
|
262
|
-
if @wrap_result
|
263
|
-
if mutation_result && !mutation_result.is_a?(Hash)
|
264
|
-
raise StandardError, "Expected `#{mutation_result}` to be a Hash."\
|
265
|
-
" Return a hash when using `return_field` or specify a custom `return_type`."
|
266
|
-
end
|
267
|
-
|
268
|
-
@mutation.result_class.new(client_mutation_id: args[:input][:clientMutationId], result: mutation_result)
|
269
|
-
else
|
270
|
-
mutation_result
|
271
|
-
end
|
272
|
-
end
|
273
|
-
end
|
274
198
|
end
|
275
199
|
end
|
276
200
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
module Relay
|
4
|
+
class Mutation
|
5
|
+
# @api private
|
6
|
+
module Instrumentation
|
7
|
+
# Modify mutation `return_field` resolves by wrapping the returned object
|
8
|
+
# in a {Mutation::Result}.
|
9
|
+
#
|
10
|
+
# By using an instrumention, we can apply our wrapper _last_,
|
11
|
+
# giving users access to the original resolve function in earlier instrumentation.
|
12
|
+
def self.instrument(type, field)
|
13
|
+
if field.mutation
|
14
|
+
new_resolve = Mutation::Resolve.new(field.mutation, field.resolve_proc)
|
15
|
+
new_lazy_resolve = Mutation::Resolve.new(field.mutation, field.lazy_resolve_proc)
|
16
|
+
field.redefine(resolve: new_resolve, lazy_resolve: new_lazy_resolve)
|
17
|
+
else
|
18
|
+
field
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
module Relay
|
4
|
+
class Mutation
|
5
|
+
# Wrap a user-provided resolve function,
|
6
|
+
# wrapping the returned value in a {Mutation::Result}.
|
7
|
+
# Also, pass the `clientMutationId` to that result object.
|
8
|
+
# @api private
|
9
|
+
class Resolve
|
10
|
+
def initialize(mutation, resolve)
|
11
|
+
@mutation = mutation
|
12
|
+
@resolve = resolve
|
13
|
+
@wrap_result = mutation.has_generated_return_type?
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(obj, args, ctx)
|
17
|
+
begin
|
18
|
+
mutation_result = @resolve.call(obj, args[:input], ctx)
|
19
|
+
rescue GraphQL::ExecutionError => err
|
20
|
+
mutation_result = err
|
21
|
+
end
|
22
|
+
|
23
|
+
if ctx.schema.lazy?(mutation_result)
|
24
|
+
mutation_result
|
25
|
+
else
|
26
|
+
build_result(mutation_result, args, ctx)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def build_result(mutation_result, args, ctx)
|
33
|
+
if mutation_result.is_a?(GraphQL::ExecutionError)
|
34
|
+
ctx.add_error(mutation_result)
|
35
|
+
mutation_result = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
if @wrap_result
|
39
|
+
if mutation_result && !mutation_result.is_a?(Hash)
|
40
|
+
raise StandardError, "Expected `#{mutation_result}` to be a Hash."\
|
41
|
+
" Return a hash when using `return_field` or specify a custom `return_type`."
|
42
|
+
end
|
43
|
+
|
44
|
+
@mutation.result_class.new(client_mutation_id: args[:input][:clientMutationId], result: mutation_result)
|
45
|
+
else
|
46
|
+
mutation_result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
module Relay
|
4
|
+
class Mutation
|
5
|
+
# Use this when the mutation's return type was generated from `return_field`s.
|
6
|
+
# It delegates field lookups to the hash returned from `resolve`.
|
7
|
+
# @api private
|
8
|
+
class Result
|
9
|
+
attr_reader :client_mutation_id
|
10
|
+
def initialize(client_mutation_id:, result:)
|
11
|
+
@client_mutation_id = client_mutation_id
|
12
|
+
result && result.each do |key, value|
|
13
|
+
self.public_send("#{key}=", value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_accessor :mutation
|
19
|
+
end
|
20
|
+
|
21
|
+
# Build a subclass whose instances have a method
|
22
|
+
# for each of `mutation_defn`'s `return_field`s
|
23
|
+
# @param mutation_defn [GraphQL::Relay::Mutation]
|
24
|
+
# @return [Class]
|
25
|
+
def self.define_subclass(mutation_defn)
|
26
|
+
subclass = Class.new(self) do
|
27
|
+
mutation_result_methods = mutation_defn.return_type.all_fields.map do |f|
|
28
|
+
f.property || f.name
|
29
|
+
end
|
30
|
+
attr_accessor(*mutation_result_methods)
|
31
|
+
self.mutation = mutation_defn
|
32
|
+
end
|
33
|
+
subclass
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/graphql/schema.rb
CHANGED
@@ -445,7 +445,7 @@ module GraphQL
|
|
445
445
|
def build_instrumented_field_map
|
446
446
|
all_instrumenters = @instrumenters[:field] + [
|
447
447
|
GraphQL::Relay::ConnectionInstrumentation,
|
448
|
-
GraphQL::Relay::Mutation::
|
448
|
+
GraphQL::Relay::Mutation::Instrumentation,
|
449
449
|
]
|
450
450
|
@instrumented_field_map = InstrumentedFieldMap.new(self, all_instrumenters)
|
451
451
|
end
|
@@ -4,7 +4,7 @@ module GraphQL
|
|
4
4
|
attr_reader :string
|
5
5
|
def initialize(str)
|
6
6
|
@string = str
|
7
|
-
super("String \"#{str}\" was encoded as #{str.encoding}! GraphQL requires UTF-8
|
7
|
+
super("String \"#{str}\" was encoded as #{str.encoding}! GraphQL requires an encoding compatible with UTF-8.")
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
data/lib/graphql/string_type.rb
CHANGED
@@ -4,10 +4,10 @@ GraphQL::STRING_TYPE = GraphQL::ScalarType.define do
|
|
4
4
|
description "Represents textual data as UTF-8 character sequences. This type is most often used by GraphQL to represent free-form human-readable text."
|
5
5
|
|
6
6
|
coerce_result ->(value, ctx) {
|
7
|
-
|
8
|
-
|
9
|
-
str
|
10
|
-
|
7
|
+
begin
|
8
|
+
str = value.to_s
|
9
|
+
str.encoding == Encoding::UTF_8 ? str : str.encode(Encoding::UTF_8)
|
10
|
+
rescue EncodingError
|
11
11
|
err = GraphQL::StringEncodingError.new(str)
|
12
12
|
ctx.schema.type_error(err, ctx)
|
13
13
|
end
|
data/lib/graphql/version.rb
CHANGED
@@ -248,4 +248,30 @@ describe GraphQL::Query::Variables do
|
|
248
248
|
end
|
249
249
|
end
|
250
250
|
end
|
251
|
+
|
252
|
+
if ActionPack::VERSION::MAJOR > 3
|
253
|
+
describe "with a ActionController::Parameters" do
|
254
|
+
let(:query_string) { <<-GRAPHQL
|
255
|
+
query getCheeses($source: DairyAnimal!, $fatContent: Float!){
|
256
|
+
searchDairy(product: [{source: $source, fatContent: $fatContent}]) {
|
257
|
+
... on Cheese { flavor }
|
258
|
+
}
|
259
|
+
}
|
260
|
+
GRAPHQL
|
261
|
+
}
|
262
|
+
let(:params) do
|
263
|
+
ActionController::Parameters.new(
|
264
|
+
"variables" => {
|
265
|
+
"source" => "COW",
|
266
|
+
"fatContent" => 0.4,
|
267
|
+
}
|
268
|
+
)
|
269
|
+
end
|
270
|
+
|
271
|
+
it "works" do
|
272
|
+
res = schema.execute(query_string, variables: params["variables"])
|
273
|
+
assert_equal 1, res["data"]["searchDairy"].length
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
251
277
|
end
|
@@ -113,6 +113,21 @@ describe GraphQL::Relay::Mutation do
|
|
113
113
|
assert_equal StarWars::IntroduceShipMutation, StarWars::IntroduceShipMutation.result_class.mutation
|
114
114
|
end
|
115
115
|
|
116
|
+
describe "return_field ... property:" do
|
117
|
+
it "resolves correctly" do
|
118
|
+
query_str = <<-GRAPHQL
|
119
|
+
mutation {
|
120
|
+
introduceShip(input: {shipName: "Bagel", factionId: "1"}) {
|
121
|
+
aliasedFaction { name }
|
122
|
+
}
|
123
|
+
}
|
124
|
+
GRAPHQL
|
125
|
+
result = star_wars_query(query_str)
|
126
|
+
faction_name = result["data"]["introduceShip"]["aliasedFaction"]["name"]
|
127
|
+
assert_equal("Alliance to Restore the Republic", faction_name)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
116
131
|
describe "aliased methods" do
|
117
132
|
describe "on an unreached mutation" do
|
118
133
|
it 'still ensures definitions' do
|
@@ -9,17 +9,41 @@ describe GraphQL::STRING_TYPE do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
describe "coerce_result" do
|
12
|
+
let(:utf_8_str) { "foobar" }
|
13
|
+
let(:ascii_str) { "foobar".encode(Encoding::ASCII_8BIT) }
|
12
14
|
let(:binary_str) { "\0\0\0foo\255\255\255".dup.force_encoding("BINARY") }
|
13
|
-
it "requires string to be encoded as UTF-8" do
|
14
|
-
err = assert_raises(GraphQL::StringEncodingError) {
|
15
|
-
string_type.coerce_isolated_result(binary_str)
|
16
|
-
}
|
17
15
|
|
18
|
-
|
19
|
-
|
16
|
+
describe "encoding" do
|
17
|
+
subject { string_type.coerce_isolated_result(string) }
|
18
|
+
|
19
|
+
describe "when result is encoded as UTF-8" do
|
20
|
+
let(:string) { utf_8_str }
|
21
|
+
|
22
|
+
it "returns the string" do
|
23
|
+
assert_equal subject, string
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "when the result is not UTF-8 but can be transcoded" do
|
28
|
+
let(:string) { ascii_str }
|
29
|
+
|
30
|
+
it "returns the string transcoded to UTF-8" do
|
31
|
+
assert_equal subject, string.encode(Encoding::UTF_8)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "when the result is not UTF-8 and cannot be transcoded" do
|
36
|
+
let(:string) { binary_str }
|
37
|
+
|
38
|
+
it "raises GraphQL::StringEncodingError" do
|
39
|
+
err = assert_raises(GraphQL::StringEncodingError) { subject }
|
40
|
+
assert_equal "String \"#{string}\" was encoded as ASCII-8BIT! GraphQL requires an encoding compatible with UTF-8.", err.message
|
41
|
+
assert_equal string, err.string
|
42
|
+
end
|
43
|
+
end
|
20
44
|
end
|
21
45
|
|
22
|
-
describe "when the schema defines a custom
|
46
|
+
describe "when the schema defines a custom handler" do
|
23
47
|
let(:schema) {
|
24
48
|
GraphQL::Schema.define do
|
25
49
|
query(GraphQL::ObjectType.define(name: "Query"))
|
@@ -183,6 +183,7 @@ module StarWars
|
|
183
183
|
# Result may have access to these fields:
|
184
184
|
return_field :shipEdge, Ship.edge_type
|
185
185
|
return_field :faction, Faction
|
186
|
+
return_field :aliasedFaction, Faction, property: :aliased_faction
|
186
187
|
|
187
188
|
# Here's the mutation operation:
|
188
189
|
resolve ->(root_obj, inputs, ctx) {
|
@@ -216,7 +217,8 @@ module StarWars
|
|
216
217
|
ship_edge = GraphQL::Relay::Edge.new(ship, ships_connection)
|
217
218
|
result = {
|
218
219
|
shipEdge: ship_edge,
|
219
|
-
faction: faction
|
220
|
+
faction: faction,
|
221
|
+
aliased_faction: faction,
|
220
222
|
}
|
221
223
|
if args["shipName"] == "Slave II"
|
222
224
|
LazyWrapper.new(result)
|
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.5.
|
4
|
+
version: 1.5.10
|
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-04-
|
11
|
+
date: 2017-04-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: benchmark-ips
|
@@ -428,6 +428,9 @@ files:
|
|
428
428
|
- lib/graphql/relay/edge_type.rb
|
429
429
|
- lib/graphql/relay/global_id_resolve.rb
|
430
430
|
- lib/graphql/relay/mutation.rb
|
431
|
+
- lib/graphql/relay/mutation/instrumentation.rb
|
432
|
+
- lib/graphql/relay/mutation/resolve.rb
|
433
|
+
- lib/graphql/relay/mutation/result.rb
|
431
434
|
- lib/graphql/relay/node.rb
|
432
435
|
- lib/graphql/relay/page_info.rb
|
433
436
|
- lib/graphql/relay/range_add.rb
|