graphql 1.4.3 → 1.4.4
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/argument.rb +13 -4
- data/lib/graphql/base_type.rb +10 -0
- data/lib/graphql/define/assign_argument.rb +2 -1
- data/lib/graphql/field.rb +7 -8
- data/lib/graphql/query/arguments.rb +3 -2
- data/lib/graphql/relay/base_connection.rb +1 -1
- data/lib/graphql/relay/connection_type.rb +7 -8
- data/lib/graphql/relay/mutation.rb +19 -2
- data/lib/graphql/relay/node.rb +4 -4
- data/lib/graphql/relay/relation_connection.rb +1 -1
- data/lib/graphql/schema/printer.rb +89 -37
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/argument_spec.rb +16 -0
- data/spec/graphql/base_type_spec.rb +36 -0
- data/spec/graphql/field_spec.rb +41 -9
- data/spec/graphql/query/arguments_spec.rb +17 -5
- data/spec/graphql/relay/array_connection_spec.rb +5 -0
- data/spec/graphql/relay/base_connection_spec.rb +16 -0
- data/spec/graphql/relay/mutation_spec.rb +94 -0
- data/spec/graphql/relay/node_spec.rb +88 -0
- data/spec/graphql/relay/relation_connection_spec.rb +5 -0
- data/spec/graphql/schema/printer_spec.rb +16 -0
- data/spec/support/dummy/schema.rb +2 -2
- data/spec/support/star_wars/schema.rb +8 -0
- data/spec/tmp/app/graphql/types/bird_type.rb +5 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e792ef4481683e6c02e730a49a55d514e3117bd
|
4
|
+
data.tar.gz: 60f5a4bd233004203b0a3249e91d1c1ac4c99fec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc9cc9c7581fb346daf8eaff35d1b7183c4ed89fc9c8c077827e71f5a63418c9d717983d2af661c700a5aade7a2c262714e01b809bd53a241c018b807de169cd
|
7
|
+
data.tar.gz: 631b382abd20a9e9060cda878287ea87df1d227e55cb410038755e99c38697007ab188fce7ff4ca7ed935e64912e663fe10e63c935c2727f53b19cbceda71570
|
data/lib/graphql/argument.rb
CHANGED
@@ -14,13 +14,17 @@ module GraphQL
|
|
14
14
|
# GraphQL::InputObjectType.define do
|
15
15
|
# argument :newName, !types.String
|
16
16
|
# end
|
17
|
-
|
17
|
+
|
18
18
|
class Argument
|
19
19
|
include GraphQL::Define::InstanceDefinable
|
20
|
-
accepts_definitions :name, :type, :description, :default_value
|
21
|
-
attr_accessor :type, :description, :default_value, :name
|
20
|
+
accepts_definitions :name, :type, :description, :default_value, :as
|
21
|
+
attr_accessor :type, :description, :default_value, :name, :as
|
22
|
+
|
23
|
+
ensure_defined(:name, :description, :default_value, :type=, :type, :as, :expose_as)
|
22
24
|
|
23
|
-
|
25
|
+
def initialize_copy(other)
|
26
|
+
@expose_as = nil
|
27
|
+
end
|
24
28
|
|
25
29
|
def default_value?
|
26
30
|
!!@has_default_value
|
@@ -44,5 +48,10 @@ module GraphQL
|
|
44
48
|
def type
|
45
49
|
@clean_type ||= GraphQL::BaseType.resolve_related_type(@dirty_type)
|
46
50
|
end
|
51
|
+
|
52
|
+
# @return [String] The name of this argument inside `resolve` functions
|
53
|
+
def expose_as
|
54
|
+
@expose_as ||= (@as || @name).to_s
|
55
|
+
end
|
47
56
|
end
|
48
57
|
end
|
data/lib/graphql/base_type.rb
CHANGED
@@ -158,5 +158,15 @@ module GraphQL
|
|
158
158
|
def define_edge(**kwargs, &block)
|
159
159
|
GraphQL::Relay::EdgeType.create_type(self, **kwargs, &block)
|
160
160
|
end
|
161
|
+
|
162
|
+
# Return a GraphQL string for the type definition
|
163
|
+
# @param schema [GraphQL::Schema]
|
164
|
+
# @param printer [GraphQL::Schema::Printer]
|
165
|
+
# @see {GraphQL::Schema::Printer#initialize for additional options}
|
166
|
+
# @return [String] type definition
|
167
|
+
def to_definition(schema, printer: nil, **args)
|
168
|
+
printer ||= GraphQL::Schema::Printer.new(schema, **args)
|
169
|
+
printer.print_type(self)
|
170
|
+
end
|
161
171
|
end
|
162
172
|
end
|
@@ -10,7 +10,7 @@ module GraphQL
|
|
10
10
|
GraphQL::Argument.new
|
11
11
|
end
|
12
12
|
|
13
|
-
unsupported_keys = rest.keys - [:default_value]
|
13
|
+
unsupported_keys = rest.keys - [:default_value, :as]
|
14
14
|
if unsupported_keys.any?
|
15
15
|
raise ArgumentError.new("unknown keyword#{unsupported_keys.length > 1 ? 's' : ''}: #{unsupported_keys.join(', ')}")
|
16
16
|
end
|
@@ -19,6 +19,7 @@ module GraphQL
|
|
19
19
|
type && argument.type = type
|
20
20
|
description && argument.description = description
|
21
21
|
rest.key?(:default_value) && argument.default_value = rest[:default_value]
|
22
|
+
argument.as = rest[:as]
|
22
23
|
|
23
24
|
target.arguments[name.to_s] = argument
|
24
25
|
end
|
data/lib/graphql/field.rb
CHANGED
@@ -213,15 +213,14 @@ module GraphQL
|
|
213
213
|
@clean_type ||= GraphQL::BaseType.resolve_related_type(@dirty_type)
|
214
214
|
end
|
215
215
|
|
216
|
-
# You can only set a field's name _once_ -- this to prevent
|
217
|
-
# passing the same {Field} to multiple `.field` calls.
|
218
|
-
#
|
219
|
-
# This is important because {#name} may be used by {#resolve}.
|
220
216
|
def name=(new_name)
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
217
|
+
old_name = @name
|
218
|
+
@name = new_name
|
219
|
+
|
220
|
+
if old_name != new_name && @resolve_proc.is_a?(Field::Resolve::NameResolve)
|
221
|
+
# Since the NameResolve would use the old field name,
|
222
|
+
# reset resolve proc when the name has changed
|
223
|
+
self.resolve = nil
|
225
224
|
end
|
226
225
|
end
|
227
226
|
|
@@ -10,9 +10,10 @@ module GraphQL
|
|
10
10
|
def initialize(values, argument_definitions:)
|
11
11
|
@original_values = values
|
12
12
|
@argument_values = values.inject({}) do |memo, (inner_key, inner_value)|
|
13
|
-
|
14
|
-
|
13
|
+
arg_defn = argument_definitions[inner_key.to_s]
|
14
|
+
|
15
15
|
arg_value = wrap_value(inner_value, arg_defn.type)
|
16
|
+
string_key = arg_defn.expose_as
|
16
17
|
memo[string_key] = ArgumentValue.new(string_key, arg_value, arg_defn)
|
17
18
|
memo
|
18
19
|
end
|
@@ -47,7 +47,7 @@ module GraphQL
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
attr_reader :nodes, :arguments, :max_page_size, :parent, :field
|
50
|
+
attr_reader :nodes, :arguments, :max_page_size, :parent, :field, :context
|
51
51
|
|
52
52
|
# Make a connection, wrapping `nodes`
|
53
53
|
# @param nodes [Object] The collection of nodes
|
@@ -10,14 +10,15 @@ module GraphQL
|
|
10
10
|
|
11
11
|
# Create a connection which exposes edges of this type
|
12
12
|
def self.create_type(wrapped_type, edge_type: nil, edge_class: nil, nodes_field: ConnectionType.default_nodes_field, &block)
|
13
|
-
edge_type ||= wrapped_type.edge_type
|
14
13
|
edge_class ||= GraphQL::Relay::Edge
|
15
|
-
|
16
|
-
connection_type_description = "The connection type for #{wrapped_type.name}."
|
14
|
+
edge_type ||= wrapped_type.edge_type
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
# Any call that would trigger `wrapped_type.ensure_defined`
|
17
|
+
# must be inside this lazy block, otherwise we get weird
|
18
|
+
# cyclical dependency errors :S
|
19
|
+
ObjectType.define do
|
20
|
+
name("#{wrapped_type.name}Connection")
|
21
|
+
description("The connection type for #{wrapped_type.name}.")
|
21
22
|
field :edges, types[edge_type] do
|
22
23
|
description "A list of edges."
|
23
24
|
resolve ->(obj, args, ctx) {
|
@@ -35,8 +36,6 @@ module GraphQL
|
|
35
36
|
field :pageInfo, !PageInfo, "Information to aid in pagination.", property: :page_info
|
36
37
|
block && instance_eval(&block)
|
37
38
|
end
|
38
|
-
|
39
|
-
connection_type
|
40
39
|
end
|
41
40
|
end
|
42
41
|
end
|
@@ -53,12 +53,19 @@ module GraphQL
|
|
53
53
|
accepts_definitions(
|
54
54
|
:name, :description, :resolve,
|
55
55
|
:return_type,
|
56
|
+
:return_interfaces,
|
56
57
|
input_field: GraphQL::Define::AssignArgument,
|
57
58
|
return_field: GraphQL::Define::AssignObjectField,
|
58
59
|
)
|
59
|
-
attr_accessor :name, :description, :fields, :arguments, :return_type
|
60
|
+
attr_accessor :name, :description, :fields, :arguments, :return_type, :return_interfaces
|
61
|
+
|
62
|
+
ensure_defined(
|
63
|
+
:input_fields, :return_fields, :name, :description,
|
64
|
+
:fields, :arguments, :return_type,
|
65
|
+
:return_interfaces, :resolve=,
|
66
|
+
:field, :result_class, :input_type
|
67
|
+
)
|
60
68
|
|
61
|
-
ensure_defined(:name, :description, :fields, :arguments, :return_type, :resolve=, :field, :result_class, :input_type)
|
62
69
|
# For backwards compat, but do we need this separate API?
|
63
70
|
alias :return_fields :fields
|
64
71
|
alias :input_fields :arguments
|
@@ -93,6 +100,10 @@ module GraphQL
|
|
93
100
|
end
|
94
101
|
end
|
95
102
|
|
103
|
+
def return_interfaces
|
104
|
+
@return_interfaces ||= []
|
105
|
+
end
|
106
|
+
|
96
107
|
def return_type
|
97
108
|
@return_type ||= begin
|
98
109
|
@has_generated_return_type = true
|
@@ -101,6 +112,7 @@ module GraphQL
|
|
101
112
|
name("#{relay_mutation.name}Payload")
|
102
113
|
description("Autogenerated return type of #{relay_mutation.name}")
|
103
114
|
field :clientMutationId, types.String, "A unique identifier for the client performing the mutation.", property: :client_mutation_id
|
115
|
+
interfaces relay_mutation.return_interfaces
|
104
116
|
relay_mutation.return_fields.each do |name, field_obj|
|
105
117
|
field name, field: field_obj
|
106
118
|
end
|
@@ -195,6 +207,11 @@ module GraphQL
|
|
195
207
|
end
|
196
208
|
|
197
209
|
if @wrap_result
|
210
|
+
if mutation_result && !mutation_result.is_a?(Hash)
|
211
|
+
raise StandardError, "Expected `#{mutation_result}` to be a Hash."\
|
212
|
+
" Return a hash when using `return_field` or specify a custom `return_type`."
|
213
|
+
end
|
214
|
+
|
198
215
|
@mutation.result_class.new(client_mutation_id: args[:input][:clientMutationId], result: mutation_result)
|
199
216
|
else
|
200
217
|
mutation_result
|
data/lib/graphql/relay/node.rb
CHANGED
@@ -4,7 +4,7 @@ module GraphQL
|
|
4
4
|
# Helpers for working with Relay-specific Node objects.
|
5
5
|
module Node
|
6
6
|
# @return [GraphQL::Field] a field for finding objects by their global ID.
|
7
|
-
def self.field
|
7
|
+
def self.field(resolve: nil)
|
8
8
|
# We have to define it fresh each time because
|
9
9
|
# its name will be modified and its description
|
10
10
|
# _may_ be modified.
|
@@ -12,17 +12,17 @@ module GraphQL
|
|
12
12
|
type(GraphQL::Relay::Node.interface)
|
13
13
|
description("Fetches an object given its ID.")
|
14
14
|
argument(:id, !types.ID, "ID of the object.")
|
15
|
-
resolve(GraphQL::Relay::Node::FindNode)
|
15
|
+
resolve(resolve || GraphQL::Relay::Node::FindNode)
|
16
16
|
relay_node_field(true)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
def self.plural_field
|
20
|
+
def self.plural_field(resolve: nil)
|
21
21
|
GraphQL::Field.define do
|
22
22
|
type(!types[GraphQL::Relay::Node.interface])
|
23
23
|
description("Fetches a list of objects given a list of IDs.")
|
24
24
|
argument(:ids, !types[!types.ID], "IDs of the objects.")
|
25
|
-
resolve(GraphQL::Relay::Node::FindNodes)
|
25
|
+
resolve(resolve || GraphQL::Relay::Node::FindNodes)
|
26
26
|
relay_nodes_field(true)
|
27
27
|
end
|
28
28
|
end
|
@@ -3,41 +3,91 @@ module GraphQL
|
|
3
3
|
class Schema
|
4
4
|
# Used to convert your {GraphQL::Schema} to a GraphQL schema string
|
5
5
|
#
|
6
|
-
# @example print your schema to standard output
|
6
|
+
# @example print your schema to standard output (via helper)
|
7
7
|
# MySchema = GraphQL::Schema.define(query: QueryType)
|
8
8
|
# puts GraphQL::Schema::Printer.print_schema(MySchema)
|
9
9
|
#
|
10
|
-
|
11
|
-
|
10
|
+
# @example print your schema to standard output
|
11
|
+
# MySchema = GraphQL::Schema.define(query: QueryType)
|
12
|
+
# puts GraphQL::Schema::Printer.new(MySchema).print_schema
|
13
|
+
#
|
14
|
+
# @example print a single type to standard output
|
15
|
+
# query_root = GraphQL::ObjectType.define do
|
16
|
+
# name "Query"
|
17
|
+
# description "The query root of this schema"
|
18
|
+
#
|
19
|
+
# field :post do
|
20
|
+
# type post_type
|
21
|
+
# resolve ->(obj, args, ctx) { Post.find(args["id"]) }
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# post_type = GraphQL::ObjectType.define do
|
26
|
+
# name "Post"
|
27
|
+
# description "A blog post"
|
28
|
+
#
|
29
|
+
# field :id, !types.ID
|
30
|
+
# field :title, !types.String
|
31
|
+
# field :body, !types.String
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# MySchema = GraphQL::Schema.define(query: query_root)
|
35
|
+
#
|
36
|
+
# printer = GraphQL::Schema::Printer.new(MySchema)
|
37
|
+
# puts printer.print_type(post_type)
|
38
|
+
#
|
39
|
+
class Printer
|
40
|
+
attr_reader :schema, :warden
|
12
41
|
|
13
|
-
#
|
42
|
+
# @param schema [GraphQL::Schema]
|
14
43
|
# @param context [Hash]
|
15
44
|
# @param only [<#call(member, ctx)>]
|
16
45
|
# @param except [<#call(member, ctx)>]
|
17
|
-
# @param
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
elsif except
|
22
|
-
->(m, ctx) { !IS_USER_DEFINED_MEMBER.call(m) || except.call(m, ctx) }
|
23
|
-
else
|
24
|
-
->(m, ctx) { !IS_USER_DEFINED_MEMBER.call(m) }
|
25
|
-
end
|
46
|
+
# @param introspection [Boolean] Should include the introspection types in the string?
|
47
|
+
def initialize(schema, context: nil, only: nil, except: nil, introspection: false)
|
48
|
+
@schema = schema
|
49
|
+
@context = context
|
26
50
|
|
27
|
-
|
28
|
-
|
29
|
-
print_filtered_schema(schema, warden: warden)
|
51
|
+
blacklist = build_blacklist(only, except, introspection: introspection)
|
52
|
+
@warden = GraphQL::Schema::Warden.new(blacklist, schema: @schema, context: @context)
|
30
53
|
end
|
31
54
|
|
32
55
|
# Return the GraphQL schema string for the introspection type system
|
33
|
-
def print_introspection_schema
|
56
|
+
def self.print_introspection_schema
|
34
57
|
query_root = ObjectType.define(name: "Root")
|
35
58
|
schema = GraphQL::Schema.define(query: query_root)
|
36
59
|
blacklist = ->(m, ctx) { m == query_root }
|
60
|
+
printer = new(schema, except: blacklist, introspection: true)
|
61
|
+
printer.print_schema
|
62
|
+
end
|
63
|
+
|
64
|
+
# Return a GraphQL schema string for the defined types in the schema
|
65
|
+
# @param schema [GraphQL::Schema]
|
66
|
+
# @param context [Hash]
|
67
|
+
# @param only [<#call(member, ctx)>]
|
68
|
+
# @param except [<#call(member, ctx)>]
|
69
|
+
def self.print_schema(schema, **args)
|
70
|
+
printer = new(schema, **args)
|
71
|
+
printer.print_schema
|
72
|
+
end
|
37
73
|
|
38
|
-
|
74
|
+
# Return a GraphQL schema string for the defined types in the schema
|
75
|
+
def print_schema
|
76
|
+
directive_definitions = warden.directives.map { |directive| print_directive(directive) }
|
77
|
+
|
78
|
+
printable_types = warden.types.reject(&:default_scalar?)
|
79
|
+
|
80
|
+
type_definitions = printable_types
|
81
|
+
.sort_by(&:name)
|
82
|
+
.map { |type| print_type(type) }
|
39
83
|
|
40
|
-
|
84
|
+
[print_schema_definition].compact
|
85
|
+
.concat(directive_definitions)
|
86
|
+
.concat(type_definitions).join("\n\n")
|
87
|
+
end
|
88
|
+
|
89
|
+
def print_type(type)
|
90
|
+
TypeKindPrinters::STRATEGIES.fetch(type.kind).print(warden, type)
|
41
91
|
end
|
42
92
|
|
43
93
|
private
|
@@ -56,21 +106,27 @@ module GraphQL
|
|
56
106
|
|
57
107
|
private_constant :IS_USER_DEFINED_MEMBER
|
58
108
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
109
|
+
def build_blacklist(only, except, introspection:)
|
110
|
+
if introspection
|
111
|
+
if only
|
112
|
+
->(m, ctx) { !only.call(m, ctx) }
|
113
|
+
elsif except
|
114
|
+
except
|
115
|
+
else
|
116
|
+
->(m, ctx) { false }
|
117
|
+
end
|
118
|
+
else
|
119
|
+
if only
|
120
|
+
->(m, ctx) { !(IS_USER_DEFINED_MEMBER.call(m) && only.call(m, ctx)) }
|
121
|
+
elsif except
|
122
|
+
->(m, ctx) { !IS_USER_DEFINED_MEMBER.call(m) || except.call(m, ctx) }
|
123
|
+
else
|
124
|
+
->(m, ctx) { !IS_USER_DEFINED_MEMBER.call(m) }
|
125
|
+
end
|
126
|
+
end
|
71
127
|
end
|
72
128
|
|
73
|
-
def print_schema_definition
|
129
|
+
def print_schema_definition
|
74
130
|
if (schema.query.nil? || schema.query.name == 'Query') &&
|
75
131
|
(schema.mutation.nil? || schema.mutation.name == 'Mutation') &&
|
76
132
|
(schema.subscription.nil? || schema.subscription.name == 'Subscription')
|
@@ -89,11 +145,7 @@ module GraphQL
|
|
89
145
|
"schema {\n#{operations}}"
|
90
146
|
end
|
91
147
|
|
92
|
-
def
|
93
|
-
TypeKindPrinters::STRATEGIES.fetch(type.kind).print(warden, type)
|
94
|
-
end
|
95
|
-
|
96
|
-
def print_directive(warden, directive)
|
148
|
+
def print_directive(directive)
|
97
149
|
TypeKindPrinters::DirectivePrinter.print(warden, directive)
|
98
150
|
end
|
99
151
|
|
data/lib/graphql/version.rb
CHANGED
@@ -41,4 +41,20 @@ describe GraphQL::Argument do
|
|
41
41
|
assert argument.default_value.nil?
|
42
42
|
assert !argument.default_value?
|
43
43
|
end
|
44
|
+
|
45
|
+
describe "#as, #exposed_as" do
|
46
|
+
it "accepts a `as` property to define the arg name at resolve time" do
|
47
|
+
argument = GraphQL::Argument.define(name: :favoriteFood, type: GraphQL::STRING_TYPE, as: :favFood)
|
48
|
+
assert_equal argument.as, :favFood
|
49
|
+
end
|
50
|
+
|
51
|
+
it "uses `name` or `as` for `expose_as`" do
|
52
|
+
arg_1 = GraphQL::Argument.define(name: :favoriteFood, type: GraphQL::STRING_TYPE, as: :favFood)
|
53
|
+
assert_equal arg_1.expose_as, "favFood"
|
54
|
+
arg_2 = GraphQL::Argument.define(name: :favoriteFood, type: GraphQL::STRING_TYPE)
|
55
|
+
assert_equal arg_2.expose_as, "favoriteFood"
|
56
|
+
arg_3 = arg_2.redefine { as :ff }
|
57
|
+
assert_equal arg_3.expose_as, "ff"
|
58
|
+
end
|
59
|
+
end
|
44
60
|
end
|
@@ -46,4 +46,40 @@ describe GraphQL::BaseType do
|
|
46
46
|
refute_equal obj_edge, obj_2.connection_type
|
47
47
|
end
|
48
48
|
end
|
49
|
+
|
50
|
+
describe "#to_definition" do
|
51
|
+
post_type = GraphQL::ObjectType.define do
|
52
|
+
name "Post"
|
53
|
+
description "A blog post"
|
54
|
+
|
55
|
+
field :id, !types.ID
|
56
|
+
field :title, !types.String
|
57
|
+
field :body, !types.String
|
58
|
+
end
|
59
|
+
|
60
|
+
query_root = GraphQL::ObjectType.define do
|
61
|
+
name "Query"
|
62
|
+
description "The query root of this schema"
|
63
|
+
|
64
|
+
field :post do
|
65
|
+
type post_type
|
66
|
+
resolve ->(obj, args, ctx) { Post.find(args["id"]) }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
schema = GraphQL::Schema.define(query: query_root)
|
71
|
+
|
72
|
+
expected = <<TYPE
|
73
|
+
# A blog post
|
74
|
+
type Post {
|
75
|
+
id: ID!
|
76
|
+
title: String!
|
77
|
+
body: String!
|
78
|
+
}
|
79
|
+
TYPE
|
80
|
+
|
81
|
+
it "prints the type definition" do
|
82
|
+
assert_equal expected.chomp, post_type.to_definition(schema)
|
83
|
+
end
|
84
|
+
end
|
49
85
|
end
|
data/spec/graphql/field_spec.rb
CHANGED
@@ -78,15 +78,6 @@ describe GraphQL::Field do
|
|
78
78
|
end
|
79
79
|
|
80
80
|
describe "#name" do
|
81
|
-
it "can't be reassigned" do
|
82
|
-
field = GraphQL::Field.define do
|
83
|
-
name("something")
|
84
|
-
end
|
85
|
-
assert_equal "something", field.name
|
86
|
-
assert_raises(RuntimeError) { field.name = "somethingelse" }
|
87
|
-
assert_equal "something", field.name
|
88
|
-
end
|
89
|
-
|
90
81
|
it "must be a string" do
|
91
82
|
dummy_query = GraphQL::ObjectType.define do
|
92
83
|
name "QueryType"
|
@@ -174,6 +165,47 @@ describe GraphQL::Field do
|
|
174
165
|
assert_equal 2, int_field_2.arguments.size
|
175
166
|
end
|
176
167
|
|
168
|
+
it "rebuilds when the resolve_proc is default NameResolve" do
|
169
|
+
int_field = GraphQL::Field.define do
|
170
|
+
name "a"
|
171
|
+
end
|
172
|
+
|
173
|
+
int_field_2 = int_field.redefine(name: "b")
|
174
|
+
|
175
|
+
object = Struct.new(:a, :b).new(1, 2)
|
176
|
+
|
177
|
+
assert_equal 1, int_field.resolve_proc.call(object, nil, nil)
|
178
|
+
assert_equal 2, int_field_2.resolve_proc.call(object, nil, nil)
|
179
|
+
end
|
180
|
+
|
181
|
+
it "keeps the same resolve_proc when it is not a NameResolve" do
|
182
|
+
int_field = GraphQL::Field.define do
|
183
|
+
name "a"
|
184
|
+
resolve ->(obj, _, _) { 'GraphQL is Kool' }
|
185
|
+
end
|
186
|
+
|
187
|
+
int_field_2 = int_field.redefine(name: "b")
|
188
|
+
|
189
|
+
assert_equal(
|
190
|
+
int_field.resolve_proc.call(nil, nil, nil),
|
191
|
+
int_field_2.resolve_proc.call(nil, nil, nil)
|
192
|
+
)
|
193
|
+
end
|
194
|
+
|
195
|
+
it "keeps the same resolve_proc when it is a built in property resolve" do
|
196
|
+
int_field = GraphQL::Field.define do
|
197
|
+
name "a"
|
198
|
+
property :c
|
199
|
+
end
|
200
|
+
|
201
|
+
int_field_2 = int_field.redefine(name: "b")
|
202
|
+
|
203
|
+
object = Struct.new(:a, :b, :c).new(1, 2, 3)
|
204
|
+
|
205
|
+
assert_equal 3, int_field.resolve_proc.call(object, nil, nil)
|
206
|
+
assert_equal 3, int_field_2.resolve_proc.call(object, nil, nil)
|
207
|
+
end
|
208
|
+
|
177
209
|
it "copies metadata, even out-of-bounds assignments" do
|
178
210
|
int_field = GraphQL::Field.define do
|
179
211
|
metadata(:a, 1)
|
@@ -13,7 +13,7 @@ describe GraphQL::Query::Arguments do
|
|
13
13
|
name "TestInput2"
|
14
14
|
argument :a, types.Int
|
15
15
|
argument :b, types.Int
|
16
|
-
argument :c, !test_input_1
|
16
|
+
argument :c, !test_input_1, as: :inputObject
|
17
17
|
end
|
18
18
|
|
19
19
|
GraphQL::Query::Arguments.new({
|
@@ -55,7 +55,7 @@ describe GraphQL::Query::Arguments do
|
|
55
55
|
expected_type_info =[
|
56
56
|
["a", 1, "Int"],
|
57
57
|
["b", 2, "Int"],
|
58
|
-
["
|
58
|
+
["inputObject", { d: 3, e: 4 }, "TestInput1"],
|
59
59
|
]
|
60
60
|
assert_equal expected_type_info, type_info
|
61
61
|
end
|
@@ -72,7 +72,7 @@ describe GraphQL::Query::Arguments do
|
|
72
72
|
expected_hash = {
|
73
73
|
"A" => 1,
|
74
74
|
"B" => 2,
|
75
|
-
"
|
75
|
+
"INPUTOBJECT" => { d: 3 , e: 4 },
|
76
76
|
}
|
77
77
|
assert_equal expected_hash, new_arguments.to_h
|
78
78
|
end
|
@@ -97,10 +97,14 @@ describe GraphQL::Query::Arguments do
|
|
97
97
|
end
|
98
98
|
|
99
99
|
describe "#[]" do
|
100
|
+
it "fetches using specified `as` keyword" do
|
101
|
+
assert arguments["inputObject"].is_a?(GraphQL::Query::Arguments)
|
102
|
+
end
|
103
|
+
|
100
104
|
it "returns the value at that key" do
|
101
105
|
assert_equal 1, arguments["a"]
|
102
106
|
assert_equal 1, arguments[:a]
|
103
|
-
assert arguments["
|
107
|
+
assert arguments["inputObject"].is_a?(GraphQL::Query::Arguments)
|
104
108
|
end
|
105
109
|
|
106
110
|
it "returns nil for missing keys" do
|
@@ -127,7 +131,7 @@ describe GraphQL::Query::Arguments do
|
|
127
131
|
field :argTest, types.Int do
|
128
132
|
argument :a, types.Int
|
129
133
|
argument :b, types.Int, default_value: 2
|
130
|
-
argument :c, types.Int
|
134
|
+
argument :c, types.Int, as: :specialKeyName
|
131
135
|
argument :d, test_input_type
|
132
136
|
resolve ->(obj, args, ctx) {
|
133
137
|
arg_values_array << args
|
@@ -146,6 +150,14 @@ describe GraphQL::Query::Arguments do
|
|
146
150
|
assert_equal false, arguments.key?("f")
|
147
151
|
end
|
148
152
|
|
153
|
+
it "detects keys using `as` to rename an arg at resolve time" do
|
154
|
+
schema.execute("{ argTest(c: 1) }")
|
155
|
+
|
156
|
+
last_args = arg_values.last
|
157
|
+
|
158
|
+
assert_equal true, last_args.key?(:specialKeyName)
|
159
|
+
end
|
160
|
+
|
149
161
|
it "works from query literals" do
|
150
162
|
schema.execute("{ argTest(a: 1) }")
|
151
163
|
|
@@ -82,6 +82,11 @@ describe GraphQL::Relay::ArrayConnection do
|
|
82
82
|
|
83
83
|
result = star_wars_query(query_string, "last" => 2)
|
84
84
|
assert_equal(["Millenium Falcon", "Home One"], get_names(result))
|
85
|
+
|
86
|
+
result = star_wars_query(query_string, "last" => 10)
|
87
|
+
assert_equal(["X-Wing", "Y-Wing", "A-Wing", "Millenium Falcon", "Home One"], get_names(result))
|
88
|
+
assert_equal(false, result["data"]["rebels"]["ships"]["pageInfo"]["hasNextPage"])
|
89
|
+
assert_equal(false, result["data"]["rebels"]["ships"]["pageInfo"]["hasPreviousPage"])
|
85
90
|
end
|
86
91
|
|
87
92
|
it 'handles cursors beyond the bounds of the array' do
|
@@ -15,6 +15,22 @@ describe GraphQL::Relay::BaseConnection do
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
describe "#context" do
|
19
|
+
module Encoder
|
20
|
+
module_function
|
21
|
+
def encode(str, nonce: false); str; end
|
22
|
+
def decode(str, nonce: false); str; end
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:schema) { OpenStruct.new(cursor_encoder: Encoder) }
|
26
|
+
let(:context) { OpenStruct.new(schema: schema) }
|
27
|
+
|
28
|
+
it "Has public access to the field context" do
|
29
|
+
conn = GraphQL::Relay::BaseConnection.new([], {}, context: context)
|
30
|
+
assert_equal context, conn.context
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
18
34
|
describe "#encode / #decode" do
|
19
35
|
module ReverseEncoder
|
20
36
|
module_function
|
@@ -49,6 +49,36 @@ describe GraphQL::Relay::Mutation do
|
|
49
49
|
assert_equal "Slave II", result["data"]["introduceShip"]["shipEdge"]["node"]["name"]
|
50
50
|
end
|
51
51
|
|
52
|
+
it "raises when using a generated return type and resolve doesnt return a Hash" do
|
53
|
+
bad_mutation = GraphQL::Relay::Mutation.define do
|
54
|
+
name 'BadMutation'
|
55
|
+
description 'A mutation type that doesnt return a hash'
|
56
|
+
|
57
|
+
input_field :input, types.String
|
58
|
+
return_field :return, types.String
|
59
|
+
|
60
|
+
resolve ->(_, _, _) {
|
61
|
+
# Should have been { return: 'my_bad_return_value' }
|
62
|
+
'my_bad_return_value'
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
root = GraphQL::ObjectType.define do
|
67
|
+
name "MutationRoot"
|
68
|
+
field :bad, bad_mutation.field
|
69
|
+
end
|
70
|
+
|
71
|
+
schema = GraphQL::Schema.define { mutation(root) }
|
72
|
+
|
73
|
+
exception = assert_raises do
|
74
|
+
puts schema.execute('mutation { bad(input: { input: "graphql" }) { return } }')
|
75
|
+
end
|
76
|
+
|
77
|
+
expected_message = "Expected `my_bad_return_value` to be a Hash."\
|
78
|
+
" Return a hash when using `return_field` or specify a custom `return_type`."
|
79
|
+
assert_equal expected_message, exception.message
|
80
|
+
end
|
81
|
+
|
52
82
|
it "returns the result & clientMutationId" do
|
53
83
|
result = star_wars_query(query_string, "clientMutationId" => "1234")
|
54
84
|
expected = {"data" => {
|
@@ -167,6 +197,70 @@ describe GraphQL::Relay::Mutation do
|
|
167
197
|
end
|
168
198
|
end
|
169
199
|
|
200
|
+
describe "specifying return interfaces" do
|
201
|
+
let(:result_interface) {
|
202
|
+
GraphQL::InterfaceType.define do
|
203
|
+
name "ResultInterface"
|
204
|
+
field :success, !types.Boolean
|
205
|
+
field :notice, types.String
|
206
|
+
end
|
207
|
+
}
|
208
|
+
|
209
|
+
let(:error_interface) {
|
210
|
+
GraphQL::InterfaceType.define do
|
211
|
+
name "ErrorInterface"
|
212
|
+
field :error, types.String
|
213
|
+
end
|
214
|
+
}
|
215
|
+
|
216
|
+
let(:mutation) {
|
217
|
+
interfaces = [result_interface, error_interface]
|
218
|
+
GraphQL::Relay::Mutation.define do
|
219
|
+
name "ReturnTypeWithInterfaceTest"
|
220
|
+
|
221
|
+
return_field :name, types.String
|
222
|
+
|
223
|
+
return_interfaces interfaces
|
224
|
+
|
225
|
+
resolve ->(obj, input, ctx) {
|
226
|
+
{
|
227
|
+
name: "Type Specific Field",
|
228
|
+
success: true,
|
229
|
+
notice: "Success Interface Field",
|
230
|
+
error: "Error Interface Field"
|
231
|
+
}
|
232
|
+
}
|
233
|
+
end
|
234
|
+
}
|
235
|
+
|
236
|
+
let(:schema) {
|
237
|
+
mutation_field = mutation.field
|
238
|
+
|
239
|
+
mutation_root = GraphQL::ObjectType.define do
|
240
|
+
name "Mutation"
|
241
|
+
field :custom, mutation_field
|
242
|
+
end
|
243
|
+
|
244
|
+
GraphQL::Schema.define do
|
245
|
+
mutation(mutation_root)
|
246
|
+
resolve_type ->(obj, ctx) { "not really used" }
|
247
|
+
end
|
248
|
+
}
|
249
|
+
|
250
|
+
it 'makes the mutation type implement the interfaces' do
|
251
|
+
assert_equal [result_interface, error_interface], mutation.return_type.interfaces
|
252
|
+
end
|
253
|
+
|
254
|
+
it "returns interface values and specific ones" do
|
255
|
+
result = schema.execute('mutation { custom(input: {clientMutationId: "123"}) { name, success, notice, error, clientMutationId } }')
|
256
|
+
assert_equal "Type Specific Field", result["data"]["custom"]["name"]
|
257
|
+
assert_equal "Success Interface Field", result["data"]["custom"]["notice"]
|
258
|
+
assert_equal true, result["data"]["custom"]["success"]
|
259
|
+
assert_equal "Error Interface Field", result["data"]["custom"]["error"]
|
260
|
+
assert_equal "123", result["data"]["custom"]["clientMutationId"]
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
170
264
|
describe "handling errors" do
|
171
265
|
it "supports returning an error in resolve" do
|
172
266
|
result = star_wars_query(query_string, "clientMutationId" => "5678", "shipName" => "Millennium Falcon")
|
@@ -9,6 +9,45 @@ describe GraphQL::Relay::Node do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
describe ".field" do
|
12
|
+
describe "with custom resolver" do
|
13
|
+
it "executes the custom resolve instead of relay default" do
|
14
|
+
id = "resolver_is_hardcoded_so_this_does_not_matter"
|
15
|
+
|
16
|
+
result = star_wars_query(%|{
|
17
|
+
nodeWithCustomResolver(id: "#{id}") {
|
18
|
+
id,
|
19
|
+
... on Faction {
|
20
|
+
name
|
21
|
+
ships(first: 1) {
|
22
|
+
edges {
|
23
|
+
node {
|
24
|
+
name
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}|)
|
31
|
+
|
32
|
+
expected = {"data" => {
|
33
|
+
"nodeWithCustomResolver"=>{
|
34
|
+
"id"=>"RmFjdGlvbi0x",
|
35
|
+
"name"=>"Alliance to Restore the Republic",
|
36
|
+
"ships"=>{
|
37
|
+
"edges"=>[
|
38
|
+
{"node"=>{
|
39
|
+
"name" => "X-Wing"
|
40
|
+
}
|
41
|
+
}
|
42
|
+
]
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}}
|
46
|
+
|
47
|
+
assert_equal(expected, result)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
12
51
|
describe "Custom global IDs" do
|
13
52
|
before do
|
14
53
|
# TODO: make the schema eager-load so we can remove this
|
@@ -92,6 +131,55 @@ describe GraphQL::Relay::Node do
|
|
92
131
|
end
|
93
132
|
|
94
133
|
describe ".plural_identifying_field" do
|
134
|
+
describe "with custom resolver" do
|
135
|
+
it "executes the custom resolve instead of relay default" do
|
136
|
+
id = ["resolver_is_hardcoded_so_this_does_not_matter", "another_id"]
|
137
|
+
|
138
|
+
result = star_wars_query(%|{
|
139
|
+
nodesWithCustomResolver(ids: ["#{id[0]}", "#{id[1]}"]) {
|
140
|
+
id,
|
141
|
+
... on Faction {
|
142
|
+
name
|
143
|
+
ships(first: 1) {
|
144
|
+
edges {
|
145
|
+
node {
|
146
|
+
name
|
147
|
+
}
|
148
|
+
}
|
149
|
+
}
|
150
|
+
}
|
151
|
+
}
|
152
|
+
}|)
|
153
|
+
|
154
|
+
expected = {
|
155
|
+
"data" => {
|
156
|
+
"nodesWithCustomResolver" => [
|
157
|
+
{
|
158
|
+
"id" => "RmFjdGlvbi0x",
|
159
|
+
"name" => "Alliance to Restore the Republic",
|
160
|
+
"ships" => {
|
161
|
+
"edges"=>[
|
162
|
+
{ "node" => { "name" => "X-Wing" } }
|
163
|
+
]
|
164
|
+
}
|
165
|
+
},
|
166
|
+
{
|
167
|
+
"id" => "RmFjdGlvbi0y",
|
168
|
+
"name" => "Galactic Empire",
|
169
|
+
"ships" => {
|
170
|
+
"edges"=>[
|
171
|
+
{ "node" => { "name" => "TIE Fighter" } }
|
172
|
+
]
|
173
|
+
}
|
174
|
+
},
|
175
|
+
]
|
176
|
+
}
|
177
|
+
}
|
178
|
+
|
179
|
+
assert_equal(expected, result)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
95
183
|
it 'finds objects by ids' do
|
96
184
|
id = GraphQL::Schema::UniqueWithinType.encode("Faction", "1")
|
97
185
|
id2 = GraphQL::Schema::UniqueWithinType.encode("Faction", "2")
|
@@ -97,6 +97,11 @@ describe GraphQL::Relay::RelationConnection do
|
|
97
97
|
|
98
98
|
result = star_wars_query(query_string, "last" => 2)
|
99
99
|
assert_equal(["Shield Generator", "Headquarters"], get_names(result))
|
100
|
+
|
101
|
+
result = star_wars_query(query_string, "last" => 10)
|
102
|
+
assert_equal(["Death Star", "Shield Generator", "Headquarters"], get_names(result))
|
103
|
+
assert_equal(false, result["data"]["empire"]["bases"]["pageInfo"]["hasNextPage"])
|
104
|
+
assert_equal(false, result["data"]["empire"]["bases"]["pageInfo"]["hasPreviousPage"])
|
100
105
|
end
|
101
106
|
|
102
107
|
it 'handles cursors beyond the bounds of the array' do
|
@@ -590,4 +590,20 @@ SCHEMA
|
|
590
590
|
context = { names: ["Varied", "Image", "Sub"] }
|
591
591
|
assert_equal expected.chomp, schema.to_definition(context: context, except: except_filter)
|
592
592
|
end
|
593
|
+
|
594
|
+
describe "#print_type" do
|
595
|
+
it "returns the type schema as a string" do
|
596
|
+
expected = <<SCHEMA
|
597
|
+
# A blog post
|
598
|
+
type Post {
|
599
|
+
id: ID!
|
600
|
+
title: String!
|
601
|
+
body: String!
|
602
|
+
comments: [Comment!]
|
603
|
+
comments_count: Int! @deprecated(reason: \"Use \\\"comments\\\".\")
|
604
|
+
}
|
605
|
+
SCHEMA
|
606
|
+
assert_equal expected.chomp, GraphQL::Schema::Printer.new(schema).print_type(schema.types['Post'])
|
607
|
+
end
|
608
|
+
end
|
593
609
|
end
|
@@ -328,9 +328,9 @@ module Dummy
|
|
328
328
|
description "The root for mutations in this schema"
|
329
329
|
field :pushValue, !types[!types.Int] do
|
330
330
|
description("Push a value onto a global array :D")
|
331
|
-
argument :value, !types.Int
|
331
|
+
argument :value, !types.Int, as: :val
|
332
332
|
resolve ->(o, args, ctx) {
|
333
|
-
GLOBAL_VALUES << args[:
|
333
|
+
GLOBAL_VALUES << args[:val]
|
334
334
|
GLOBAL_VALUES
|
335
335
|
}
|
336
336
|
end
|
@@ -8,6 +8,8 @@ module StarWars
|
|
8
8
|
interfaces [GraphQL::Relay::Node.interface]
|
9
9
|
global_id_field :id
|
10
10
|
field :name, types.String
|
11
|
+
# Test cyclical connection types:
|
12
|
+
connection :ships, Ship.connection_type
|
11
13
|
end
|
12
14
|
|
13
15
|
BaseType = GraphQL::ObjectType.define do
|
@@ -218,7 +220,13 @@ module StarWars
|
|
218
220
|
end
|
219
221
|
|
220
222
|
field :node, GraphQL::Relay::Node.field
|
223
|
+
field :nodeWithCustomResolver, GraphQL::Relay::Node.field(
|
224
|
+
resolve: ->(_, _, _) { StarWars::DATA["Faction"]["1"] }
|
225
|
+
)
|
221
226
|
field :nodes, GraphQL::Relay::Node.plural_field
|
227
|
+
field :nodesWithCustomResolver, GraphQL::Relay::Node.plural_field(
|
228
|
+
resolve: ->(_, _, _) { [StarWars::DATA["Faction"]["1"], StarWars::DATA["Faction"]["2"]] }
|
229
|
+
)
|
222
230
|
end
|
223
231
|
|
224
232
|
MutationType = GraphQL::ObjectType.define do
|
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.4.
|
4
|
+
version: 1.4.4
|
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-02-
|
11
|
+
date: 2017-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: codeclimate-test-reporter
|
@@ -553,6 +553,7 @@ files:
|
|
553
553
|
- spec/support/star_wars/data.rb
|
554
554
|
- spec/support/star_wars/schema.rb
|
555
555
|
- spec/support/static_validation_helpers.rb
|
556
|
+
- spec/tmp/app/graphql/types/bird_type.rb
|
556
557
|
homepage: http://github.com/rmosolgo/graphql-ruby
|
557
558
|
licenses:
|
558
559
|
- MIT
|
@@ -573,7 +574,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
573
574
|
version: '0'
|
574
575
|
requirements: []
|
575
576
|
rubyforge_project:
|
576
|
-
rubygems_version: 2.
|
577
|
+
rubygems_version: 2.6.10
|
577
578
|
signing_key:
|
578
579
|
specification_version: 4
|
579
580
|
summary: A GraphQL server implementation for Ruby
|
@@ -682,3 +683,4 @@ test_files:
|
|
682
683
|
- spec/support/star_wars/data.rb
|
683
684
|
- spec/support/star_wars/schema.rb
|
684
685
|
- spec/support/static_validation_helpers.rb
|
686
|
+
- spec/tmp/app/graphql/types/bird_type.rb
|