graphql 1.4.0 → 1.4.1
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/introspection/schema_type.rb +3 -3
- data/lib/graphql/language/nodes.rb +1 -1
- data/lib/graphql/query.rb +39 -27
- data/lib/graphql/query/executor.rb +1 -1
- data/lib/graphql/query/variables.rb +21 -28
- data/lib/graphql/relay/connection_type.rb +2 -0
- data/lib/graphql/relay/relation_connection.rb +8 -2
- data/lib/graphql/schema.rb +3 -0
- data/lib/graphql/schema/warden.rb +9 -0
- data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/analysis/analyze_query_spec.rb +15 -15
- data/spec/graphql/analysis/field_usage_spec.rb +1 -1
- data/spec/graphql/analysis/max_query_complexity_spec.rb +8 -8
- data/spec/graphql/analysis/max_query_depth_spec.rb +7 -7
- data/spec/graphql/analysis/query_complexity_spec.rb +2 -2
- data/spec/graphql/analysis/query_depth_spec.rb +1 -1
- data/spec/graphql/base_type_spec.rb +19 -11
- data/spec/graphql/directive_spec.rb +1 -1
- data/spec/graphql/enum_type_spec.rb +2 -2
- data/spec/graphql/execution/typecast_spec.rb +19 -19
- data/spec/graphql/execution_error_spec.rb +1 -1
- data/spec/graphql/field_spec.rb +15 -7
- data/spec/graphql/id_type_spec.rb +1 -1
- data/spec/graphql/input_object_type_spec.rb +16 -16
- data/spec/graphql/interface_type_spec.rb +6 -6
- data/spec/graphql/internal_representation/rewrite_spec.rb +34 -34
- data/spec/graphql/introspection/directive_type_spec.rb +1 -1
- data/spec/graphql/introspection/input_value_type_spec.rb +2 -2
- data/spec/graphql/introspection/introspection_query_spec.rb +1 -1
- data/spec/graphql/introspection/schema_type_spec.rb +2 -2
- data/spec/graphql/introspection/type_type_spec.rb +1 -1
- data/spec/graphql/language/parser_spec.rb +1 -1
- data/spec/graphql/non_null_type_spec.rb +3 -3
- data/spec/graphql/object_type_spec.rb +8 -8
- data/spec/graphql/query/executor_spec.rb +4 -4
- data/spec/graphql/query/variables_spec.rb +20 -4
- data/spec/graphql/query_spec.rb +20 -2
- data/spec/graphql/relay/connection_type_spec.rb +1 -1
- data/spec/graphql/relay/mutation_spec.rb +9 -9
- data/spec/graphql/relay/node_spec.rb +8 -8
- data/spec/graphql/relay/relation_connection_spec.rb +24 -6
- data/spec/graphql/schema/catchall_middleware_spec.rb +3 -3
- data/spec/graphql/schema/reduce_types_spec.rb +9 -9
- data/spec/graphql/schema/type_expression_spec.rb +3 -3
- data/spec/graphql/schema/validation_spec.rb +1 -1
- data/spec/graphql/schema/warden_spec.rb +79 -0
- data/spec/graphql/schema_spec.rb +2 -2
- data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +1 -1
- data/spec/graphql/static_validation/type_stack_spec.rb +2 -2
- data/spec/graphql/static_validation/validator_spec.rb +2 -2
- data/spec/graphql/union_type_spec.rb +2 -2
- data/spec/spec_helper.rb +1 -1
- data/spec/support/dummy/data.rb +27 -0
- data/spec/support/dummy/schema.rb +369 -0
- data/spec/support/star_wars/data.rb +81 -0
- data/spec/support/star_wars/schema.rb +250 -0
- data/spec/support/static_validation_helpers.rb +2 -2
- metadata +10 -10
- data/spec/support/dairy_app.rb +0 -369
- data/spec/support/dairy_data.rb +0 -26
- data/spec/support/star_wars_data.rb +0 -80
- data/spec/support/star_wars_schema.rb +0 -242
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'ostruct'
|
3
|
+
module StarWars
|
4
|
+
names = [
|
5
|
+
'X-Wing',
|
6
|
+
'Y-Wing',
|
7
|
+
'A-Wing',
|
8
|
+
'Millenium Falcon',
|
9
|
+
'Home One',
|
10
|
+
'TIE Fighter',
|
11
|
+
'TIE Interceptor',
|
12
|
+
'Executor',
|
13
|
+
]
|
14
|
+
|
15
|
+
# ActiveRecord::Base.logger = Logger.new(STDOUT)
|
16
|
+
`rm -f ./_test_.db`
|
17
|
+
# Set up "Bases" in ActiveRecord
|
18
|
+
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: "./_test_.db")
|
19
|
+
|
20
|
+
ActiveRecord::Schema.define do
|
21
|
+
self.verbose = false
|
22
|
+
create_table :bases do |t|
|
23
|
+
t.column :name, :string
|
24
|
+
t.column :planet, :string
|
25
|
+
t.column :faction_id, :integer
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Base < ActiveRecord::Base
|
30
|
+
end
|
31
|
+
|
32
|
+
Base.create!(name: "Yavin", planet: "Yavin 4", faction_id: 1)
|
33
|
+
Base.create!(name: "Echo Base", planet: "Hoth", faction_id: 1)
|
34
|
+
Base.create!(name: "Secret Hideout", planet: "Dantooine", faction_id: 1)
|
35
|
+
Base.create!(name: "Death Star", planet: nil, faction_id: 2)
|
36
|
+
Base.create!(name: "Shield Generator", planet: "Endor", faction_id: 2)
|
37
|
+
Base.create!(name: "Headquarters", planet: "Coruscant", faction_id: 2)
|
38
|
+
|
39
|
+
# Also, set up Bases with Sequel
|
40
|
+
DB = Sequel.sqlite("./_test_.db")
|
41
|
+
class SequelBase < Sequel::Model(:bases)
|
42
|
+
end
|
43
|
+
|
44
|
+
rebels = OpenStruct.new({
|
45
|
+
id: '1',
|
46
|
+
name: 'Alliance to Restore the Republic',
|
47
|
+
ships: ['1', '2', '3', '4', '5'],
|
48
|
+
bases: Base.where(faction_id: 1),
|
49
|
+
basesClone: Base.where(faction_id: 1),
|
50
|
+
})
|
51
|
+
|
52
|
+
|
53
|
+
empire = OpenStruct.new({
|
54
|
+
id: '2',
|
55
|
+
name: 'Galactic Empire',
|
56
|
+
ships: ['6', '7', '8'],
|
57
|
+
bases: Base.where(faction_id: 2),
|
58
|
+
basesClone: Base.where(faction_id: 2),
|
59
|
+
})
|
60
|
+
|
61
|
+
DATA = {
|
62
|
+
"Faction" => {
|
63
|
+
"1" => rebels,
|
64
|
+
"2" => empire,
|
65
|
+
},
|
66
|
+
"Ship" => names.each_with_index.reduce({}) do |memo, (name, idx)|
|
67
|
+
id = (idx + 1).to_s
|
68
|
+
memo[id] = OpenStruct.new(name: name, id: id)
|
69
|
+
memo
|
70
|
+
end,
|
71
|
+
"Base" => Hash.new { |h, k| h[k] = Base.find(k) }
|
72
|
+
}
|
73
|
+
|
74
|
+
def DATA.create_ship(name, faction_id)
|
75
|
+
new_id = (self["Ship"].keys.map(&:to_i).max + 1).to_s
|
76
|
+
new_ship = OpenStruct.new(id: new_id, name: name)
|
77
|
+
self["Ship"][new_id] = new_ship
|
78
|
+
self["Faction"][faction_id].ships << new_id
|
79
|
+
new_ship
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module StarWars
|
3
|
+
# Adapted from graphql-relay-js
|
4
|
+
# https://github.com/graphql/graphql-relay-js/blob/master/src/__tests__/starWarsSchema.js
|
5
|
+
|
6
|
+
Ship = GraphQL::ObjectType.define do
|
7
|
+
name "Ship"
|
8
|
+
interfaces [GraphQL::Relay::Node.interface]
|
9
|
+
global_id_field :id
|
10
|
+
field :name, types.String
|
11
|
+
end
|
12
|
+
|
13
|
+
BaseType = GraphQL::ObjectType.define do
|
14
|
+
name "Base"
|
15
|
+
interfaces [GraphQL::Relay::Node.interface]
|
16
|
+
global_id_field :id
|
17
|
+
field :name, types.String
|
18
|
+
field :planet, types.String
|
19
|
+
end
|
20
|
+
|
21
|
+
# Use an optional block to add fields to the connection type:
|
22
|
+
BaseConnectionWithTotalCountType = BaseType.define_connection(nodes_field: true) do
|
23
|
+
name "BasesConnectionWithTotalCount"
|
24
|
+
field :totalCount do
|
25
|
+
type types.Int
|
26
|
+
resolve ->(obj, args, ctx) { obj.nodes.count }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class CustomBaseEdge < GraphQL::Relay::Edge
|
31
|
+
def upcased_name
|
32
|
+
node.name.upcase
|
33
|
+
end
|
34
|
+
|
35
|
+
def upcased_parent_name
|
36
|
+
parent.name.upcase
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
CustomBaseEdgeType = BaseType.define_edge do
|
41
|
+
name "CustomBaseEdge"
|
42
|
+
field :upcasedName, types.String, property: :upcased_name
|
43
|
+
field :upcasedParentName, types.String, property: :upcased_parent_name
|
44
|
+
field :edgeClassName, types.String do
|
45
|
+
resolve ->(obj, args, ctx) { obj.class.name }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
CustomEdgeBaseConnectionType = BaseType.define_connection(edge_class: CustomBaseEdge, edge_type: CustomBaseEdgeType, nodes_field: true) do
|
50
|
+
name "CustomEdgeBaseConnection"
|
51
|
+
|
52
|
+
field :totalCountTimes100 do
|
53
|
+
type types.Int
|
54
|
+
resolve ->(obj, args, ctx) { obj.nodes.count * 100 }
|
55
|
+
end
|
56
|
+
|
57
|
+
field :fieldName, types.String, resolve: ->(obj, args, ctx) { obj.field.name }
|
58
|
+
end
|
59
|
+
|
60
|
+
Faction = GraphQL::ObjectType.define do
|
61
|
+
name "Faction"
|
62
|
+
interfaces [GraphQL::Relay::Node.interface]
|
63
|
+
|
64
|
+
field :id, !types.ID, resolve: GraphQL::Relay::GlobalIdResolve.new(type: Faction)
|
65
|
+
field :name, types.String
|
66
|
+
connection :ships, Ship.connection_type do
|
67
|
+
resolve ->(obj, args, ctx) {
|
68
|
+
all_ships = obj.ships.map {|ship_id| StarWars::DATA["Ship"][ship_id] }
|
69
|
+
if args[:nameIncludes]
|
70
|
+
all_ships = all_ships.select { |ship| ship.name.include?(args[:nameIncludes])}
|
71
|
+
end
|
72
|
+
all_ships
|
73
|
+
}
|
74
|
+
# You can define arguments here and use them in the connection
|
75
|
+
argument :nameIncludes, types.String
|
76
|
+
end
|
77
|
+
connection :shipsWithMaxPageSize, Ship.connection_type, max_page_size: 2 do
|
78
|
+
resolve ->(obj, args, ctx) {
|
79
|
+
all_ships = obj.ships.map {|ship_id| StarWars::DATA["Ship"][ship_id] }
|
80
|
+
if args[:nameIncludes]
|
81
|
+
all_ships = all_ships.select { |ship| ship.name.include?(args[:nameIncludes])}
|
82
|
+
end
|
83
|
+
all_ships
|
84
|
+
}
|
85
|
+
# You can define arguments here and use them in the connection
|
86
|
+
argument :nameIncludes, types.String
|
87
|
+
end
|
88
|
+
|
89
|
+
connection :bases, BaseConnectionWithTotalCountType do
|
90
|
+
# Resolve field should return an Array, the Connection
|
91
|
+
# will do the rest!
|
92
|
+
resolve ->(obj, args, ctx) {
|
93
|
+
all_bases = Base.where(id: obj.bases)
|
94
|
+
if args[:nameIncludes]
|
95
|
+
all_bases = all_bases.where("name LIKE ?", "%#{args[:nameIncludes]}%")
|
96
|
+
end
|
97
|
+
all_bases
|
98
|
+
}
|
99
|
+
argument :nameIncludes, types.String
|
100
|
+
end
|
101
|
+
|
102
|
+
connection :basesClone, BaseType.connection_type
|
103
|
+
connection :basesByName, BaseType.connection_type, property: :bases do
|
104
|
+
argument :order, types.String, default_value: "name"
|
105
|
+
resolve ->(obj, args, ctx) {
|
106
|
+
if args[:order].present?
|
107
|
+
obj.bases.order(args[:order])
|
108
|
+
else
|
109
|
+
obj.bases
|
110
|
+
end
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
connection :basesWithMaxLimitRelation, BaseType.connection_type, max_page_size: 2 do
|
115
|
+
resolve ->(object, args, context) { Base.all }
|
116
|
+
end
|
117
|
+
|
118
|
+
connection :basesWithMaxLimitArray, BaseType.connection_type, max_page_size: 2 do
|
119
|
+
resolve ->(object, args, context) { Base.all.to_a }
|
120
|
+
end
|
121
|
+
|
122
|
+
connection :basesAsSequelDataset, BaseConnectionWithTotalCountType do
|
123
|
+
argument :nameIncludes, types.String
|
124
|
+
resolve ->(obj, args, ctx) {
|
125
|
+
all_bases = SequelBase.where(faction_id: obj.id)
|
126
|
+
if args[:nameIncludes]
|
127
|
+
all_bases = all_bases.where("name LIKE ?", "%#{args[:nameIncludes]}%")
|
128
|
+
end
|
129
|
+
all_bases
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
connection :basesWithCustomEdge, CustomEdgeBaseConnectionType, property: :bases
|
134
|
+
end
|
135
|
+
|
136
|
+
# Define a mutation. It will also:
|
137
|
+
# - define a derived InputObjectType
|
138
|
+
# - define a derived ObjectType (for return)
|
139
|
+
# - define a field, accessible from {Mutation#field}
|
140
|
+
#
|
141
|
+
# The resolve proc takes `inputs, ctx`, where:
|
142
|
+
# - `inputs` has the keys defined with `input_field`
|
143
|
+
# - `ctx` is the Query context (like normal fields)
|
144
|
+
#
|
145
|
+
# Notice that you leave out clientMutationId.
|
146
|
+
IntroduceShipMutation = GraphQL::Relay::Mutation.define do
|
147
|
+
# Used as the root for derived types:
|
148
|
+
name "IntroduceShip"
|
149
|
+
description "Add a ship to this faction"
|
150
|
+
|
151
|
+
# Nested under `input` in the query:
|
152
|
+
input_field :shipName, types.String
|
153
|
+
input_field :factionId, !types.ID
|
154
|
+
|
155
|
+
# Result may have access to these fields:
|
156
|
+
return_field :shipEdge, Ship.edge_type
|
157
|
+
return_field :faction, Faction
|
158
|
+
|
159
|
+
# Here's the mutation operation:
|
160
|
+
resolve ->(root_obj, inputs, ctx) {
|
161
|
+
faction_id = inputs["factionId"]
|
162
|
+
if inputs["shipName"] == 'Millennium Falcon'
|
163
|
+
GraphQL::ExecutionError.new("Sorry, Millennium Falcon ship is reserved")
|
164
|
+
|
165
|
+
else
|
166
|
+
ship = DATA.create_ship(inputs["shipName"], faction_id)
|
167
|
+
faction = DATA["Faction"][faction_id]
|
168
|
+
connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(faction.ships)
|
169
|
+
ships_connection = connection_class.new(faction.ships, inputs)
|
170
|
+
ship_edge = GraphQL::Relay::Edge.new(ship, ships_connection)
|
171
|
+
result = {
|
172
|
+
shipEdge: ship_edge,
|
173
|
+
faction: faction
|
174
|
+
}
|
175
|
+
if inputs["shipName"] == "Slave II"
|
176
|
+
LazyWrapper.new(result)
|
177
|
+
else
|
178
|
+
result
|
179
|
+
end
|
180
|
+
end
|
181
|
+
}
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
class LazyWrapper
|
186
|
+
attr_reader :value
|
187
|
+
def initialize(value)
|
188
|
+
@value = value
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
QueryType = GraphQL::ObjectType.define do
|
193
|
+
name "Query"
|
194
|
+
field :rebels, Faction do
|
195
|
+
resolve ->(obj, args, ctx) { StarWars::DATA["Faction"]["1"]}
|
196
|
+
end
|
197
|
+
|
198
|
+
field :empire, Faction do
|
199
|
+
resolve ->(obj, args, ctx) { StarWars::DATA["Faction"]["2"]}
|
200
|
+
end
|
201
|
+
|
202
|
+
field :largestBase, BaseType do
|
203
|
+
resolve ->(obj, args, ctx) { Base.find(3) }
|
204
|
+
end
|
205
|
+
|
206
|
+
connection :newestBasesGroupedByFaction, BaseType.connection_type do
|
207
|
+
resolve ->(obj, args, ctx) {
|
208
|
+
Base.order('sum(faction_id) desc').group(:faction_id)
|
209
|
+
}
|
210
|
+
end
|
211
|
+
|
212
|
+
field :node, GraphQL::Relay::Node.field
|
213
|
+
end
|
214
|
+
|
215
|
+
MutationType = GraphQL::ObjectType.define do
|
216
|
+
name "Mutation"
|
217
|
+
# The mutation object exposes a field:
|
218
|
+
field :introduceShip, field: IntroduceShipMutation.field
|
219
|
+
end
|
220
|
+
|
221
|
+
Schema = GraphQL::Schema.define do
|
222
|
+
query(QueryType)
|
223
|
+
mutation(MutationType)
|
224
|
+
|
225
|
+
resolve_type ->(object, ctx) {
|
226
|
+
if object == :test_error
|
227
|
+
:not_a_type
|
228
|
+
elsif object.is_a?(Base)
|
229
|
+
BaseType
|
230
|
+
elsif DATA["Faction"].values.include?(object)
|
231
|
+
Faction
|
232
|
+
elsif DATA["Ship"].values.include?(object)
|
233
|
+
Ship
|
234
|
+
else
|
235
|
+
nil
|
236
|
+
end
|
237
|
+
}
|
238
|
+
|
239
|
+
object_from_id ->(node_id, ctx) do
|
240
|
+
type_name, id = GraphQL::Schema::UniqueWithinType.decode(node_id)
|
241
|
+
StarWars::DATA[type_name][id]
|
242
|
+
end
|
243
|
+
|
244
|
+
id_from_object ->(object, type, ctx) do
|
245
|
+
GraphQL::Schema::UniqueWithinType.encode(type.name, object.id)
|
246
|
+
end
|
247
|
+
|
248
|
+
lazy_resolve(LazyWrapper, :value)
|
249
|
+
end
|
250
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
# This module assumes you have `let(:query_string)` in your spec.
|
3
3
|
# It provides `errors` which are the validation errors for that string,
|
4
|
-
# as validated against `
|
4
|
+
# as validated against `Dummy::Schema`.
|
5
5
|
# You can override `schema` to provide another schema
|
6
6
|
# @example testing static validation
|
7
7
|
# include StaticValidationHelpers
|
@@ -23,6 +23,6 @@ module StaticValidationHelpers
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def schema
|
26
|
-
|
26
|
+
Dummy::Schema
|
27
27
|
end
|
28
28
|
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.
|
4
|
+
version: 1.4.1
|
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-01-
|
11
|
+
date: 2017-01-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: codeclimate-test-reporter
|
@@ -547,11 +547,11 @@ files:
|
|
547
547
|
- spec/graphql/string_type_spec.rb
|
548
548
|
- spec/graphql/union_type_spec.rb
|
549
549
|
- spec/spec_helper.rb
|
550
|
-
- spec/support/
|
551
|
-
- spec/support/
|
550
|
+
- spec/support/dummy/data.rb
|
551
|
+
- spec/support/dummy/schema.rb
|
552
552
|
- spec/support/minimum_input_object.rb
|
553
|
-
- spec/support/
|
554
|
-
- spec/support/
|
553
|
+
- spec/support/star_wars/data.rb
|
554
|
+
- spec/support/star_wars/schema.rb
|
555
555
|
- spec/support/static_validation_helpers.rb
|
556
556
|
homepage: http://github.com/rmosolgo/graphql-ruby
|
557
557
|
licenses:
|
@@ -676,9 +676,9 @@ test_files:
|
|
676
676
|
- spec/graphql/string_type_spec.rb
|
677
677
|
- spec/graphql/union_type_spec.rb
|
678
678
|
- spec/spec_helper.rb
|
679
|
-
- spec/support/
|
680
|
-
- spec/support/
|
679
|
+
- spec/support/dummy/data.rb
|
680
|
+
- spec/support/dummy/schema.rb
|
681
681
|
- spec/support/minimum_input_object.rb
|
682
|
-
- spec/support/
|
683
|
-
- spec/support/
|
682
|
+
- spec/support/star_wars/data.rb
|
683
|
+
- spec/support/star_wars/schema.rb
|
684
684
|
- spec/support/static_validation_helpers.rb
|
data/spec/support/dairy_app.rb
DELETED
@@ -1,369 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require_relative "./dairy_data"
|
3
|
-
|
4
|
-
class NoSuchDairyError < StandardError; end
|
5
|
-
|
6
|
-
GraphQL::Field.accepts_definitions(joins: GraphQL::Define.assign_metadata_key(:joins))
|
7
|
-
GraphQL::BaseType.accepts_definitions(class_names: GraphQL::Define.assign_metadata_key(:class_names))
|
8
|
-
|
9
|
-
LocalProductInterface = GraphQL::InterfaceType.define do
|
10
|
-
name "LocalProduct"
|
11
|
-
description "Something that comes from somewhere"
|
12
|
-
field :origin, !types.String, "Place the thing comes from"
|
13
|
-
end
|
14
|
-
|
15
|
-
EdibleInterface = GraphQL::InterfaceType.define do
|
16
|
-
name "Edible"
|
17
|
-
description "Something you can eat, yum"
|
18
|
-
field :fatContent, !types.Float, "Percentage which is fat"
|
19
|
-
field :origin, !types.String, "Place the edible comes from"
|
20
|
-
field :selfAsEdible, EdibleInterface, resolve: ->(o, a, c) { o }
|
21
|
-
end
|
22
|
-
|
23
|
-
AnimalProductInterface = GraphQL::InterfaceType.define do
|
24
|
-
name "AnimalProduct"
|
25
|
-
description "Comes from an animal, no joke"
|
26
|
-
field :source, !types.String, "Animal which produced this product"
|
27
|
-
end
|
28
|
-
|
29
|
-
BeverageUnion = GraphQL::UnionType.define do
|
30
|
-
name "Beverage"
|
31
|
-
description "Something you can drink"
|
32
|
-
possible_types [MilkType]
|
33
|
-
end
|
34
|
-
|
35
|
-
DairyAnimalEnum = GraphQL::EnumType.define do
|
36
|
-
name "DairyAnimal"
|
37
|
-
description "An animal which can yield milk"
|
38
|
-
value("COW", "Animal with black and white spots", value: 1)
|
39
|
-
value("DONKEY", "Animal with fur", value: :donkey)
|
40
|
-
value("GOAT", "Animal with horns")
|
41
|
-
value("REINDEER", "Animal with horns", value: 'reindeer')
|
42
|
-
value("SHEEP", "Animal with wool")
|
43
|
-
value("YAK", "Animal with long hair", deprecation_reason: "Out of fashion")
|
44
|
-
end
|
45
|
-
|
46
|
-
CheeseType = GraphQL::ObjectType.define do
|
47
|
-
name "Cheese"
|
48
|
-
class_names ["Cheese"]
|
49
|
-
description "Cultured dairy product"
|
50
|
-
interfaces [EdibleInterface, AnimalProductInterface, LocalProductInterface]
|
51
|
-
|
52
|
-
# Can have (name, type, desc)
|
53
|
-
field :id, !types.Int, "Unique identifier"
|
54
|
-
field :flavor, !types.String, "Kind of Cheese"
|
55
|
-
field :origin, !types.String, "Place the cheese comes from"
|
56
|
-
|
57
|
-
field :source, !DairyAnimalEnum,
|
58
|
-
"Animal which produced the milk for this cheese"
|
59
|
-
|
60
|
-
# Or can define by block, `resolve ->` should override `property:`
|
61
|
-
field :similarCheese, CheeseType, "Cheeses like this one", property: :this_should_be_overriden do
|
62
|
-
# metadata test
|
63
|
-
joins [:cheeses, :milks]
|
64
|
-
argument :source, !types[!DairyAnimalEnum]
|
65
|
-
argument :nullableSource, types[!DairyAnimalEnum], default_value: [1]
|
66
|
-
resolve ->(t, a, c) {
|
67
|
-
# get the strings out:
|
68
|
-
sources = a["source"]
|
69
|
-
if sources.include?("YAK")
|
70
|
-
raise NoSuchDairyError.new("No cheeses are made from Yak milk!")
|
71
|
-
else
|
72
|
-
CHEESES.values.find { |c| sources.include?(c.source) }
|
73
|
-
end
|
74
|
-
}
|
75
|
-
end
|
76
|
-
|
77
|
-
field :nullableCheese, CheeseType, "Cheeses like this one" do
|
78
|
-
argument :source, types[!DairyAnimalEnum]
|
79
|
-
resolve ->(t, a, c) { raise("NotImplemented") }
|
80
|
-
end
|
81
|
-
|
82
|
-
field :deeplyNullableCheese, CheeseType, "Cheeses like this one" do
|
83
|
-
argument :source, types[types[DairyAnimalEnum]]
|
84
|
-
resolve ->(t, a, c) { raise("NotImplemented") }
|
85
|
-
end
|
86
|
-
|
87
|
-
# Keywords can be used for definition methods
|
88
|
-
field :fatContent,
|
89
|
-
property: :fat_content,
|
90
|
-
type: !GraphQL::FLOAT_TYPE,
|
91
|
-
description: "Percentage which is milkfat",
|
92
|
-
deprecation_reason: "Diet fashion has changed"
|
93
|
-
end
|
94
|
-
|
95
|
-
MilkType = GraphQL::ObjectType.define do
|
96
|
-
name "Milk"
|
97
|
-
description "Dairy beverage"
|
98
|
-
interfaces [EdibleInterface, AnimalProductInterface, LocalProductInterface]
|
99
|
-
field :id, !types.ID
|
100
|
-
field :source, DairyAnimalEnum, "Animal which produced this milk", hash_key: :source
|
101
|
-
field :origin, !types.String, "Place the milk comes from"
|
102
|
-
field :flavors, types[types.String], "Chocolate, Strawberry, etc" do
|
103
|
-
argument :limit, types.Int
|
104
|
-
resolve ->(milk, args, ctx) {
|
105
|
-
args[:limit] ? milk.flavors.first(args[:limit]) : milk.flavors
|
106
|
-
}
|
107
|
-
end
|
108
|
-
field :executionError do
|
109
|
-
type GraphQL::STRING_TYPE
|
110
|
-
resolve ->(t, a, c) { raise(GraphQL::ExecutionError, "There was an execution error") }
|
111
|
-
end
|
112
|
-
|
113
|
-
field :allDairy, -> { types[DairyProductUnion] } do
|
114
|
-
resolve ->(obj, args, ctx) { CHEESES.values + MILKS.values }
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
SweetenerInterface = GraphQL::InterfaceType.define do
|
119
|
-
name "Sweetener"
|
120
|
-
field :sweetness, types.Int
|
121
|
-
end
|
122
|
-
|
123
|
-
# No actual data; This type is an "orphan", only accessible through Interfaces
|
124
|
-
HoneyType = GraphQL::ObjectType.define do
|
125
|
-
name "Honey"
|
126
|
-
description "Sweet, dehydrated bee barf"
|
127
|
-
field :flowerType, types.String, "What flower this honey came from"
|
128
|
-
interfaces [EdibleInterface, AnimalProductInterface, SweetenerInterface]
|
129
|
-
end
|
130
|
-
|
131
|
-
DairyType = GraphQL::ObjectType.define do
|
132
|
-
name "Dairy"
|
133
|
-
description "A farm where milk is harvested and cheese is produced"
|
134
|
-
field :id, !types.ID
|
135
|
-
field :cheese, CheeseType
|
136
|
-
field :milks, types[MilkType]
|
137
|
-
end
|
138
|
-
|
139
|
-
MaybeNullType = GraphQL::ObjectType.define do
|
140
|
-
name "MaybeNull"
|
141
|
-
description "An object whose fields return nil"
|
142
|
-
field :cheese, CheeseType
|
143
|
-
end
|
144
|
-
|
145
|
-
DairyProductUnion = GraphQL::UnionType.define do
|
146
|
-
name "DairyProduct"
|
147
|
-
description "Kinds of food made from milk"
|
148
|
-
# Test that these forms of declaration still work:
|
149
|
-
possible_types ["MilkType", -> { CheeseType }]
|
150
|
-
end
|
151
|
-
|
152
|
-
CowType = GraphQL::ObjectType.define do
|
153
|
-
name "Cow"
|
154
|
-
description "A farm where milk is harvested and cheese is produced"
|
155
|
-
field :id, !types.ID
|
156
|
-
field :name, types.String
|
157
|
-
field :last_produced_dairy, DairyProductUnion
|
158
|
-
|
159
|
-
field :cantBeNullButIs do
|
160
|
-
type !GraphQL::STRING_TYPE
|
161
|
-
resolve ->(t, a, c) { nil }
|
162
|
-
end
|
163
|
-
|
164
|
-
field :cantBeNullButRaisesExecutionError do
|
165
|
-
type !GraphQL::STRING_TYPE
|
166
|
-
resolve ->(t, a, c) { raise GraphQL::ExecutionError, "BOOM" }
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
DairyProductInputType = GraphQL::InputObjectType.define {
|
171
|
-
name "DairyProductInput"
|
172
|
-
description "Properties for finding a dairy product"
|
173
|
-
input_field :source, !DairyAnimalEnum do
|
174
|
-
# ensure we can define description in block
|
175
|
-
description "Where it came from"
|
176
|
-
end
|
177
|
-
|
178
|
-
input_field :originDairy, types.String, "Dairy which produced it", default_value: "Sugar Hollow Dairy" do
|
179
|
-
description "Ignored because arg takes precedence"
|
180
|
-
default_value "Ignored because keyword arg takes precedence"
|
181
|
-
end
|
182
|
-
|
183
|
-
input_field :fatContent, types.Float, "How much fat it has" do
|
184
|
-
# ensure we can define default in block
|
185
|
-
default_value 0.3
|
186
|
-
end
|
187
|
-
|
188
|
-
# ensure default can be false
|
189
|
-
input_field :organic, types.Boolean, default_value: false
|
190
|
-
}
|
191
|
-
|
192
|
-
DeepNonNullType = GraphQL::ObjectType.define do
|
193
|
-
name "DeepNonNull"
|
194
|
-
field :nonNullInt, !types.Int do
|
195
|
-
argument :returning, types.Int
|
196
|
-
resolve ->(obj, args, ctx) { args[:returning] }
|
197
|
-
end
|
198
|
-
|
199
|
-
field :deepNonNull, DeepNonNullType.to_non_null_type do
|
200
|
-
resolve ->(obj, args, ctx) { :deepNonNull }
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
class FetchField
|
205
|
-
def self.create(type:, data:, id_type: !GraphQL::INT_TYPE)
|
206
|
-
desc = "Find a #{type.name} by id"
|
207
|
-
return_type = type
|
208
|
-
GraphQL::Field.define do
|
209
|
-
type(return_type)
|
210
|
-
description(desc)
|
211
|
-
argument :id, id_type
|
212
|
-
|
213
|
-
resolve ->(t, a, c) {
|
214
|
-
id_string = a["id"].to_s # Cheese has Int type, Milk has ID type :(
|
215
|
-
id, item = data.find { |id, item| id.to_s == id_string }
|
216
|
-
item
|
217
|
-
}
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
class SingletonField
|
223
|
-
def self.create(type:, data:)
|
224
|
-
desc = "Find the only #{type.name}"
|
225
|
-
return_type = type
|
226
|
-
GraphQL::Field.define do
|
227
|
-
type(return_type)
|
228
|
-
description(desc)
|
229
|
-
|
230
|
-
resolve ->(t, a, c) {data}
|
231
|
-
end
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
SourceFieldDefn = Proc.new {
|
236
|
-
type GraphQL::ListType.new(of_type: CheeseType)
|
237
|
-
description "Cheese from source"
|
238
|
-
argument :source, DairyAnimalEnum, default_value: 1
|
239
|
-
resolve ->(target, arguments, context) {
|
240
|
-
CHEESES.values.select{ |c| c.source == arguments["source"] }
|
241
|
-
}
|
242
|
-
}
|
243
|
-
|
244
|
-
FavoriteFieldDefn = GraphQL::Field.define do
|
245
|
-
name "favoriteEdible"
|
246
|
-
description "My favorite food"
|
247
|
-
type EdibleInterface
|
248
|
-
resolve ->(t, a, c) { MILKS[1] }
|
249
|
-
end
|
250
|
-
|
251
|
-
DairyAppQueryType = GraphQL::ObjectType.define do
|
252
|
-
name "Query"
|
253
|
-
description "Query root of the system"
|
254
|
-
field :root, types.String do
|
255
|
-
resolve ->(root_value, args, c) { root_value }
|
256
|
-
end
|
257
|
-
field :cheese, field: FetchField.create(type: CheeseType, data: CHEESES)
|
258
|
-
field :milk, field: FetchField.create(type: MilkType, data: MILKS, id_type: !types.ID)
|
259
|
-
field :dairy, field: SingletonField.create(type: DairyType, data: DAIRY)
|
260
|
-
field :fromSource, &SourceFieldDefn
|
261
|
-
field :favoriteEdible, FavoriteFieldDefn
|
262
|
-
field :cow, field: SingletonField.create(type: CowType, data: COW)
|
263
|
-
field :searchDairy do
|
264
|
-
description "Find dairy products matching a description"
|
265
|
-
type !DairyProductUnion
|
266
|
-
# This is a list just for testing 😬
|
267
|
-
argument :product, types[DairyProductInputType], default_value: [{"source" => "SHEEP"}]
|
268
|
-
resolve ->(t, args, c) {
|
269
|
-
source = args["product"][0][:source] # String or Sym is ok
|
270
|
-
products = CHEESES.values + MILKS.values
|
271
|
-
if !source.nil?
|
272
|
-
products = products.select { |pr| pr.source == source }
|
273
|
-
end
|
274
|
-
products.first
|
275
|
-
}
|
276
|
-
end
|
277
|
-
|
278
|
-
field :allDairy, types[DairyProductUnion] do
|
279
|
-
argument :executionErrorAtIndex, types.Int
|
280
|
-
resolve ->(obj, args, ctx) {
|
281
|
-
result = CHEESES.values + MILKS.values
|
282
|
-
result[args[:executionErrorAtIndex]] = GraphQL::ExecutionError.new("missing dairy") if args[:executionErrorAtIndex]
|
283
|
-
result
|
284
|
-
}
|
285
|
-
end
|
286
|
-
|
287
|
-
field :allEdible, types[EdibleInterface] do
|
288
|
-
resolve ->(obj, args, ctx) { CHEESES.values + MILKS.values }
|
289
|
-
end
|
290
|
-
|
291
|
-
field :error do
|
292
|
-
description "Raise an error"
|
293
|
-
type GraphQL::STRING_TYPE
|
294
|
-
resolve ->(t, a, c) { raise("This error was raised on purpose") }
|
295
|
-
end
|
296
|
-
|
297
|
-
field :executionError do
|
298
|
-
type GraphQL::STRING_TYPE
|
299
|
-
resolve ->(t, a, c) { raise(GraphQL::ExecutionError, "There was an execution error") }
|
300
|
-
end
|
301
|
-
|
302
|
-
field :valueWithExecutionError do
|
303
|
-
type !GraphQL::INT_TYPE
|
304
|
-
resolve ->(t, a, c) {
|
305
|
-
c.add_error(GraphQL::ExecutionError.new("Could not fetch latest value"))
|
306
|
-
return 0
|
307
|
-
}
|
308
|
-
end
|
309
|
-
|
310
|
-
# To test possibly-null fields
|
311
|
-
field :maybeNull, MaybeNullType do
|
312
|
-
resolve ->(t, a, c) { OpenStruct.new(cheese: nil) }
|
313
|
-
end
|
314
|
-
|
315
|
-
field :deepNonNull, !DeepNonNullType do
|
316
|
-
resolve ->(o, a, c) { :deepNonNull }
|
317
|
-
end
|
318
|
-
end
|
319
|
-
|
320
|
-
GLOBAL_VALUES = []
|
321
|
-
|
322
|
-
ReplaceValuesInputType = GraphQL::InputObjectType.define do
|
323
|
-
name "ReplaceValuesInput"
|
324
|
-
input_field :values, !types[!types.Int]
|
325
|
-
end
|
326
|
-
|
327
|
-
DairyAppMutationType = GraphQL::ObjectType.define do
|
328
|
-
name "Mutation"
|
329
|
-
description "The root for mutations in this schema"
|
330
|
-
field :pushValue, !types[!types.Int] do
|
331
|
-
description("Push a value onto a global array :D")
|
332
|
-
argument :value, !types.Int
|
333
|
-
resolve ->(o, args, ctx) {
|
334
|
-
GLOBAL_VALUES << args[:value]
|
335
|
-
GLOBAL_VALUES
|
336
|
-
}
|
337
|
-
end
|
338
|
-
|
339
|
-
field :replaceValues, !types[!types.Int] do
|
340
|
-
description("Replace the global array with new values")
|
341
|
-
argument :input, !ReplaceValuesInputType
|
342
|
-
resolve ->(o, args, ctx) {
|
343
|
-
GLOBAL_VALUES.clear
|
344
|
-
GLOBAL_VALUES.push(*args[:input][:values])
|
345
|
-
GLOBAL_VALUES
|
346
|
-
}
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
SubscriptionType = GraphQL::ObjectType.define do
|
351
|
-
name "Subscription"
|
352
|
-
field :test, types.String do
|
353
|
-
resolve ->(o, a, c) { "Test" }
|
354
|
-
end
|
355
|
-
end
|
356
|
-
|
357
|
-
DummySchema = GraphQL::Schema.define do
|
358
|
-
query DairyAppQueryType
|
359
|
-
mutation DairyAppMutationType
|
360
|
-
subscription SubscriptionType
|
361
|
-
max_depth 5
|
362
|
-
orphan_types [HoneyType, BeverageUnion]
|
363
|
-
|
364
|
-
rescue_from(NoSuchDairyError) { |err| err.message }
|
365
|
-
|
366
|
-
resolve_type ->(obj, ctx) {
|
367
|
-
DummySchema.types[obj.class.name]
|
368
|
-
}
|
369
|
-
end
|