graphql 1.5.9 → 1.5.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|