graphql 1.4.3 → 1.4.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cda98acc4cef5d2d8e4c9c77cd9ac5f5139cc38a
4
- data.tar.gz: 184b72d444de078b443453bca6f0a4d64d8cf0b9
3
+ metadata.gz: 3e792ef4481683e6c02e730a49a55d514e3117bd
4
+ data.tar.gz: 60f5a4bd233004203b0a3249e91d1c1ac4c99fec
5
5
  SHA512:
6
- metadata.gz: cf7c368fd8d9ef704580aa2956e2813db1e3efbd748f1d9836d58b17ae5afaca3df2d623750dd9546688e42ab1cb5c0be26f6bbe46ea3f7d837788c8fc00fe92
7
- data.tar.gz: ce531eb4ad6cd87b947c9c5fbd68d5cd14fef266950a3448e02659d02e8bc7db10c7d9d206ea12751d059c01392ed952facf938c3cddfd53dfb21404b554d83f
6
+ metadata.gz: fc9cc9c7581fb346daf8eaff35d1b7183c4ed89fc9c8c077827e71f5a63418c9d717983d2af661c700a5aade7a2c262714e01b809bd53a241c018b807de169cd
7
+ data.tar.gz: 631b382abd20a9e9060cda878287ea87df1d227e55cb410038755e99c38697007ab188fce7ff4ca7ed935e64912e663fe10e63c935c2727f53b19cbceda71570
@@ -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
- ensure_defined(:name, :description, :default_value, :type=, :type)
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
@@ -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
@@ -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
- if @name.nil?
222
- @name = new_name
223
- elsif @name != new_name
224
- raise("Can't rename an already-named field. (Tried to rename \"#{@name}\" to \"#{new_name}\".) If you're passing a field with the `field:` argument, make sure it's an unused instance of GraphQL::Field.")
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
- string_key = inner_key.to_s
14
- arg_defn = argument_definitions[string_key]
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
- connection_type_name = "#{wrapped_type.name}Connection"
16
- connection_type_description = "The connection type for #{wrapped_type.name}."
14
+ edge_type ||= wrapped_type.edge_type
17
15
 
18
- connection_type = ObjectType.define do
19
- name(connection_type_name)
20
- description(connection_type_description)
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
@@ -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
@@ -51,7 +51,7 @@ module GraphQL
51
51
  if before
52
52
  [previous_offset, 0].max
53
53
  elsif last
54
- count(nodes) - last
54
+ [count(nodes) - last, 0].max
55
55
  else
56
56
  previous_offset
57
57
  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
- module Printer
11
- extend self
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
- # Return a GraphQL schema string for the defined types in the schema
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 schema [GraphQL::Schema]
18
- def print_schema(schema, context: nil, only: nil, except: nil)
19
- blacklist = if only
20
- ->(m, ctx) { !(IS_USER_DEFINED_MEMBER.call(m) && only.call(m, ctx)) }
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
- warden = GraphQL::Schema::Warden.new(blacklist, schema: schema, context: context)
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
- warden = GraphQL::Schema::Warden.new(blacklist, schema: schema, context: nil)
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
- print_filtered_schema(schema, warden: warden)
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 print_filtered_schema(schema, warden:)
60
- directive_definitions = warden.directives.map { |directive| print_directive(warden, directive) }
61
-
62
- printable_types = warden.types.reject(&:default_scalar?)
63
-
64
- type_definitions = printable_types
65
- .sort_by(&:name)
66
- .map { |type| print_type(warden, type) }
67
-
68
- [print_schema_definition(warden, schema)].compact
69
- .concat(directive_definitions)
70
- .concat(type_definitions).join("\n\n")
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(warden, schema)
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 print_type(warden, type)
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
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.4.3"
3
+ VERSION = "1.4.4"
4
4
  end
@@ -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
@@ -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
- ["c", { d: 3, e: 4 }, "TestInput1"],
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
- "C" => { d: 3 , e: 4 },
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["c"].is_a?(GraphQL::Query::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[:value]
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
@@ -0,0 +1,5 @@
1
+ Types::BirdType = GraphQL::InterfaceType.define do
2
+ name "Bird"
3
+ field :wingspan, !types.Int
4
+ field :foliage, types[Types::ColorType]
5
+ end
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.3
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-08 00:00:00.000000000 Z
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.5.1
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