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