graphql 0.9.5 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql/base_type.rb +24 -0
- data/lib/graphql/definition_helpers/defined_by_config.rb +5 -4
- data/lib/graphql/definition_helpers/non_null_with_bang.rb +1 -1
- data/lib/graphql/definition_helpers/type_definer.rb +1 -1
- data/lib/graphql/enum_type.rb +16 -3
- data/lib/graphql/input_object_type.rb +10 -0
- data/lib/graphql/language.rb +0 -5
- data/lib/graphql/language/nodes.rb +79 -75
- data/lib/graphql/language/parser.rb +109 -106
- data/lib/graphql/language/transform.rb +100 -91
- data/lib/graphql/language/visitor.rb +78 -74
- data/lib/graphql/list_type.rb +5 -0
- data/lib/graphql/non_null_type.rb +6 -2
- data/lib/graphql/query.rb +58 -9
- data/lib/graphql/query/arguments.rb +29 -26
- data/lib/graphql/query/base_execution/value_resolution.rb +3 -3
- data/lib/graphql/query/directive_chain.rb +1 -1
- data/lib/graphql/query/executor.rb +6 -27
- data/lib/graphql/query/literal_input.rb +89 -0
- data/lib/graphql/query/ruby_input.rb +20 -0
- data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
- data/lib/graphql/query/variables.rb +39 -0
- data/lib/graphql/scalar_type.rb +27 -5
- data/lib/graphql/schema.rb +5 -0
- data/lib/graphql/schema/type_expression.rb +28 -0
- data/lib/graphql/static_validation/literal_validator.rb +5 -2
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +0 -2
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/readme.md +3 -2
- data/spec/graphql/enum_type_spec.rb +7 -2
- data/spec/graphql/input_object_type_spec.rb +45 -0
- data/spec/graphql/language/parser_spec.rb +2 -1
- data/spec/graphql/language/transform_spec.rb +5 -0
- data/spec/graphql/query/base_execution/value_resolution_spec.rb +46 -0
- data/spec/graphql/query/context_spec.rb +37 -0
- data/spec/graphql/query/executor_spec.rb +33 -0
- data/spec/graphql/query_spec.rb +110 -26
- data/spec/graphql/schema/type_expression_spec.rb +38 -0
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +2 -2
- data/spec/support/dairy_app.rb +17 -17
- data/spec/support/dairy_data.rb +2 -2
- metadata +12 -2
@@ -15,7 +15,7 @@ class GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTyped
|
|
15
15
|
if node.type.is_a?(GraphQL::Language::Nodes::NonNullType)
|
16
16
|
context.errors << message("Non-null variable $#{node.name} can't have a default value", node)
|
17
17
|
else
|
18
|
-
type = context.schema.
|
18
|
+
type = context.schema.type_from_ast(node.type)
|
19
19
|
if !literal_validator.validate(value, type)
|
20
20
|
context.errors << message("Default value for $#{node.name} doesn't match type #{node.type.name}", node)
|
21
21
|
end
|
data/lib/graphql/version.rb
CHANGED
data/readme.md
CHANGED
@@ -94,7 +94,7 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
|
|
94
94
|
- Code clean-up
|
95
95
|
- Raise if you try to configure an attribute which doesn't suit the type
|
96
96
|
- ie, if you try to define `resolve` on an ObjectType, it should somehow raise
|
97
|
-
-
|
97
|
+
- Clean up file structure in `lib/query` (don't need serial_execution namespace anymore)
|
98
98
|
- Overriding `!` on types breaks ActiveSupport `.blank?`
|
99
99
|
|
100
100
|
```ruby
|
@@ -105,9 +105,10 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
|
|
105
105
|
my_type.blank?
|
106
106
|
# => MyType!
|
107
107
|
```
|
108
|
-
|
108
|
+
- Statically validate type of variables (see early return in LiteralValidator)
|
109
109
|
- Big ideas:
|
110
110
|
- Use [graphql-parser](https://github.com/shopify/graphql-parser) (Ruby bindings for [libgraphqlparser](https://github.com/graphql/libgraphqlparser)) instead of Parslet
|
111
|
+
- Revamp the fixture Schema to be more useful (better names, more extensible)
|
111
112
|
|
112
113
|
## Goals
|
113
114
|
|
@@ -4,7 +4,12 @@ describe GraphQL::EnumType do
|
|
4
4
|
let(:enum) { DairyAnimalEnum }
|
5
5
|
|
6
6
|
it 'coerces names to underlying values' do
|
7
|
-
assert_equal("YAK", enum.
|
8
|
-
assert_equal(1, enum.
|
7
|
+
assert_equal("YAK", enum.coerce_input("YAK"))
|
8
|
+
assert_equal(1, enum.coerce_input("COW"))
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'coerces result values to value name' do
|
12
|
+
assert_equal("YAK", enum.coerce_result("YAK"))
|
13
|
+
assert_equal("COW", enum.coerce_result(1))
|
9
14
|
end
|
10
15
|
end
|
@@ -9,4 +9,49 @@ describe GraphQL::InputObjectType do
|
|
9
9
|
it 'has input fields' do
|
10
10
|
assert(DairyProductInputType.input_fields["fatContent"])
|
11
11
|
end
|
12
|
+
|
13
|
+
describe "when sent into a query" do
|
14
|
+
let(:variables) { {} }
|
15
|
+
let(:result) { DummySchema.execute(query_string, variables: variables) }
|
16
|
+
|
17
|
+
describe "list inputs" do
|
18
|
+
let(:variables) { {"search" => [{"source" => "COW"}]} }
|
19
|
+
let(:query_string) {%|
|
20
|
+
query getCheeses($search: [DairyProductInput]!){
|
21
|
+
sheep: searchDairy(product: [{source: SHEEP, fatContent: 0.1}]) {
|
22
|
+
... cheeseFields
|
23
|
+
}
|
24
|
+
cow: searchDairy(product: $search) {
|
25
|
+
... cheeseFields
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
fragment cheeseFields on Cheese {
|
30
|
+
flavor
|
31
|
+
}
|
32
|
+
|}
|
33
|
+
|
34
|
+
it "converts items to plain values" do
|
35
|
+
sheep_value = result["data"]["sheep"]["flavor"]
|
36
|
+
cow_value = result["data"]["cow"]["flavor"]
|
37
|
+
assert_equal("Manchego", sheep_value)
|
38
|
+
assert_equal("Brie", cow_value)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "scalar inputs" do
|
43
|
+
let(:query_string) {%|
|
44
|
+
{
|
45
|
+
cheese(id: 1.4) {
|
46
|
+
flavor
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|}
|
50
|
+
|
51
|
+
it "converts them to the correct type" do
|
52
|
+
cheese_name = result["data"]["cheese"]["flavor"]
|
53
|
+
assert_equal("Brie", cheese_name)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
12
57
|
end
|
@@ -42,7 +42,8 @@ describe GraphQL::Language::Parser do
|
|
42
42
|
assert(parser.operation_definition.parse_with_debug(%|{id, name, ...people}|), "just a selection")
|
43
43
|
assert(parser.operation_definition.parse_with_debug(%|query personStuff {id, name, ...people, ... stuff}|), "named fetch")
|
44
44
|
assert(parser.operation_definition.parse_with_debug(%|query personStuff @flagDirective {id, name, ...people}|), "with a directive")
|
45
|
-
assert(parser.operation_definition.parse_with_debug(%|mutation changeStuff($stuff: Int = 1
|
45
|
+
assert(parser.operation_definition.parse_with_debug(%|mutation changeStuff($stuff: Int = 1 $things: [SomeType]! = [{something: 1}, {something: 2}], $another: Sometype = {something: 3}) { id }|), "mutation with arguments")
|
46
|
+
assert(parser.operation_definition.parse_with_debug(%|mutation { id }|), "unnamed")
|
46
47
|
end
|
47
48
|
|
48
49
|
it 'parses fragment definitions' do
|
@@ -118,4 +118,9 @@ describe GraphQL::Language::Transform do
|
|
118
118
|
assert_equal("someFlag", res.name)
|
119
119
|
assert_equal([], res.arguments, 'gets [] if no args')
|
120
120
|
end
|
121
|
+
|
122
|
+
it 'transforms unnamed operations' do
|
123
|
+
assert_equal(1, get_result("query { me }").parts.length)
|
124
|
+
assert_equal(1, get_result("mutation { touch }").parts.length)
|
125
|
+
end
|
121
126
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::Query::BaseExecution::ValueResolution do
|
4
|
+
let(:debug) { false }
|
5
|
+
let(:query_root) {
|
6
|
+
day_of_week_enum = GraphQL::EnumType.define do
|
7
|
+
name "DayOfWeek"
|
8
|
+
value("MONDAY", value: 0)
|
9
|
+
value("TUESDAY", value: 1)
|
10
|
+
value("WEDNESDAY", value: 2)
|
11
|
+
value("THURSDAY", value: 3)
|
12
|
+
value("FRIDAY", value: 4)
|
13
|
+
value("SATURDAY", value: 5)
|
14
|
+
value("SUNDAY", value: 6)
|
15
|
+
end
|
16
|
+
GraphQL::ObjectType.define do
|
17
|
+
name "Query"
|
18
|
+
field :tomorrow, day_of_week_enum do
|
19
|
+
argument :today, day_of_week_enum
|
20
|
+
resolve ->(obj, args, ctx) { (args['today'] + 1) % 7 }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
}
|
24
|
+
let(:schema) { GraphQL::Schema.new(query: query_root) }
|
25
|
+
let(:result) { schema.execute(
|
26
|
+
query_string,
|
27
|
+
debug: debug,
|
28
|
+
)}
|
29
|
+
|
30
|
+
describe "enum resolution" do
|
31
|
+
let(:query_string) { %|
|
32
|
+
{
|
33
|
+
tomorrow(today: FRIDAY)
|
34
|
+
}
|
35
|
+
|}
|
36
|
+
|
37
|
+
it "coerces enum input to the value and result to the name" do
|
38
|
+
expected = {
|
39
|
+
"data" => {
|
40
|
+
"tomorrow" => "SATURDAY"
|
41
|
+
}
|
42
|
+
}
|
43
|
+
assert_equal(expected, result)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GraphQL::Query::Context do
|
4
|
+
let(:query_type) { GraphQL::ObjectType.define {
|
5
|
+
field :context, types.String do
|
6
|
+
argument :key, !types.String
|
7
|
+
resolve -> (target, args, ctx) { ctx[args[:key]] }
|
8
|
+
end
|
9
|
+
field :contextAstNodeName, types.String do
|
10
|
+
resolve -> (target, args, ctx) { ctx.ast_node.class.name }
|
11
|
+
end
|
12
|
+
}}
|
13
|
+
let(:schema) { GraphQL::Schema.new(query: query_type, mutation: nil)}
|
14
|
+
let(:result) { schema.execute(query_string, context: {"some_key" => "some value"})}
|
15
|
+
|
16
|
+
describe "access to passed-in values" do
|
17
|
+
let(:query_string) { %|
|
18
|
+
query getCtx { context(key: "some_key") }
|
19
|
+
|}
|
20
|
+
|
21
|
+
it 'passes context to fields' do
|
22
|
+
expected = {"data" => {"context" => "some value"}}
|
23
|
+
assert_equal(expected, result)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "access to the AST node" do
|
28
|
+
let(:query_string) { %|
|
29
|
+
query getCtx { contextAstNodeName }
|
30
|
+
|}
|
31
|
+
|
32
|
+
it 'provides access to the AST node' do
|
33
|
+
expected = {"data" => {"contextAstNodeName" => "GraphQL::Language::Nodes::Field"}}
|
34
|
+
assert_equal(expected, result)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -154,7 +154,40 @@ describe GraphQL::Query::Executor do
|
|
154
154
|
}
|
155
155
|
assert_equal(expected, result)
|
156
156
|
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe "variable coercion" do
|
161
|
+
describe "for unspecified with default" do
|
162
|
+
let(:query_string) {%| query Q($limit: Int = 2) { milk(id: 1) { flavors(limit: $limit) } } |}
|
157
163
|
|
164
|
+
it "uses the default value" do
|
165
|
+
expected = {
|
166
|
+
"data" => {
|
167
|
+
"milk" => {
|
168
|
+
"flavors" => ["Natural", "Chocolate"],
|
169
|
+
}
|
170
|
+
}
|
171
|
+
}
|
172
|
+
assert_equal(expected, result)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe "for input object type" do
|
177
|
+
let(:variables) { {"input" => [{ "source" => "SHEEP" }]} }
|
178
|
+
let(:query_string) {%| query Q($input: [DairyProductInput]) { searchDairy(product: $input) { __typename, ... on Cheese { id, source } } } |}
|
179
|
+
it "uses the default value" do
|
180
|
+
expected = {
|
181
|
+
"data" => {
|
182
|
+
"searchDairy" => {
|
183
|
+
"__typename" => "Cheese",
|
184
|
+
"id" => 3,
|
185
|
+
"source" => "SHEEP"
|
186
|
+
}
|
187
|
+
}
|
188
|
+
}
|
189
|
+
assert_equal(expected, result)
|
190
|
+
end
|
158
191
|
end
|
159
192
|
end
|
160
193
|
end
|
data/spec/graphql/query_spec.rb
CHANGED
@@ -13,7 +13,7 @@ describe GraphQL::Query do
|
|
13
13
|
}
|
14
14
|
fromSource(source: COW) { id }
|
15
15
|
fromSheep: fromSource(source: SHEEP) { id }
|
16
|
-
firstSheep: searchDairy(product: {source: SHEEP}) {
|
16
|
+
firstSheep: searchDairy(product: [{source: SHEEP}]) {
|
17
17
|
__typename,
|
18
18
|
... dairyFields,
|
19
19
|
... milkFields
|
@@ -30,10 +30,12 @@ describe GraphQL::Query do
|
|
30
30
|
|}
|
31
31
|
let(:debug) { false }
|
32
32
|
let(:operation_name) { nil }
|
33
|
+
let(:query_variables) { {"cheeseId" => 2} }
|
34
|
+
let(:schema) { DummySchema }
|
33
35
|
let(:query) { GraphQL::Query.new(
|
34
|
-
|
36
|
+
schema,
|
35
37
|
query_string,
|
36
|
-
variables:
|
38
|
+
variables: query_variables,
|
37
39
|
debug: debug,
|
38
40
|
operation_name: operation_name,
|
39
41
|
)}
|
@@ -159,38 +161,120 @@ describe GraphQL::Query do
|
|
159
161
|
end
|
160
162
|
end
|
161
163
|
|
162
|
-
describe
|
163
|
-
let(:
|
164
|
-
|
165
|
-
|
166
|
-
|
164
|
+
describe "field argument default values" do
|
165
|
+
let(:query_string) {%|
|
166
|
+
query getCheeses(
|
167
|
+
$search: [DairyProductInput]
|
168
|
+
$searchWithDefault: [DairyProductInput] = [{source: COW}]
|
169
|
+
){
|
170
|
+
noVariable: searchDairy(product: $search) {
|
171
|
+
... cheeseFields
|
172
|
+
}
|
173
|
+
noArgument: searchDairy {
|
174
|
+
... cheeseFields
|
175
|
+
}
|
176
|
+
variableDefault: searchDairy(product: $searchWithDefault) {
|
177
|
+
... cheeseFields
|
178
|
+
}
|
179
|
+
|
180
|
+
}
|
181
|
+
fragment cheeseFields on Cheese { flavor }
|
182
|
+
|}
|
183
|
+
|
184
|
+
it "has a default value" do
|
185
|
+
default_source = schema.query.fields["searchDairy"].arguments["product"].default_value[0]["source"]
|
186
|
+
assert_equal("SHEEP", default_source)
|
187
|
+
end
|
188
|
+
|
189
|
+
describe "when a variable is used, but not provided" do
|
190
|
+
it "uses the default_value" do
|
191
|
+
assert_equal("Manchego", result["data"]["noVariable"]["flavor"])
|
167
192
|
end
|
168
|
-
|
169
|
-
|
193
|
+
end
|
194
|
+
|
195
|
+
describe "when the argument isn't passed at all" do
|
196
|
+
it "uses the default value" do
|
197
|
+
assert_equal("Manchego", result["data"]["noArgument"]["flavor"])
|
170
198
|
end
|
171
|
-
|
172
|
-
let(:schema) { GraphQL::Schema.new(query: query_type, mutation: nil)}
|
173
|
-
let(:query) { GraphQL::Query.new(schema, query_string, context: {"some_key" => "some value"})}
|
199
|
+
end
|
174
200
|
|
175
|
-
describe "
|
176
|
-
|
177
|
-
|
178
|
-
|
201
|
+
describe "when the variable has a default" do
|
202
|
+
it "uses the variable default" do
|
203
|
+
assert_equal("Brie", result["data"]["variableDefault"]["flavor"])
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe "query variables" do
|
209
|
+
let(:query_string) {%|
|
210
|
+
query getCheese($cheeseId: Int!){
|
211
|
+
cheese(id: $cheeseId) { flavor }
|
212
|
+
}
|
213
|
+
|}
|
179
214
|
|
180
|
-
|
181
|
-
|
182
|
-
|
215
|
+
describe "when they can be coerced" do
|
216
|
+
let(:query_variables) { {"cheeseId" => 2.0} }
|
217
|
+
|
218
|
+
it "coerces them on the way in" do
|
219
|
+
assert("Gouda", result["data"]["cheese"]["flavor"])
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe "when they can't be coerced" do
|
224
|
+
let(:query_variables) { {"cheeseId" => "2"} }
|
225
|
+
|
226
|
+
it "raises an error" do
|
227
|
+
assert(result["errors"][0]["message"].include?(%{Couldn't coerce "2" to Int}))
|
183
228
|
end
|
184
229
|
end
|
185
230
|
|
186
|
-
describe "
|
187
|
-
let(:
|
188
|
-
|
231
|
+
describe "when they aren't provided" do
|
232
|
+
let(:query_variables) { {} }
|
233
|
+
|
234
|
+
it "raises an error" do
|
235
|
+
expected = "Variable cheeseId of type Int! can't be null"
|
236
|
+
assert_equal(result["errors"][0]["message"], expected)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
describe "default values" do
|
241
|
+
let(:query_string) {%|
|
242
|
+
query getCheese($cheeseId: Int = 3){
|
243
|
+
cheese(id: $cheeseId) { id, flavor }
|
244
|
+
}
|
189
245
|
|}
|
190
246
|
|
191
|
-
|
192
|
-
|
193
|
-
|
247
|
+
describe "when no value is provided" do
|
248
|
+
let(:query_variables) { {} }
|
249
|
+
|
250
|
+
it "uses the default" do
|
251
|
+
assert(3, result["data"]["cheese"]["id"])
|
252
|
+
assert("Manchego", result["data"]["cheese"]["flavor"])
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
describe "when a value is provided" do
|
257
|
+
it "uses the provided variable" do
|
258
|
+
assert(2, result["data"]["cheese"]["id"])
|
259
|
+
assert("Gouda", result["data"]["cheese"]["flavor"])
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
describe "when complex values" do
|
264
|
+
let(:query_variables) { {"search" => [{"source" => "COW"}]} }
|
265
|
+
let(:query_string) {%|
|
266
|
+
query getCheeses($search: [DairyProductInput]!){
|
267
|
+
cow: searchDairy(product: $search) {
|
268
|
+
... on Cheese {
|
269
|
+
flavor
|
270
|
+
}
|
271
|
+
}
|
272
|
+
}
|
273
|
+
|}
|
274
|
+
|
275
|
+
it "coerces recursively" do
|
276
|
+
assert_equal("Brie", result["data"]["cow"]["flavor"])
|
277
|
+
end
|
194
278
|
end
|
195
279
|
end
|
196
280
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GraphQL::Schema::TypeExpression do
|
4
|
+
let(:schema) { DummySchema }
|
5
|
+
let(:ast_node) {
|
6
|
+
ast = GraphQL::PARSER.type.parse(type_name)
|
7
|
+
GraphQL::TRANSFORM.apply(ast)
|
8
|
+
}
|
9
|
+
let(:type_expression) { GraphQL::Schema::TypeExpression.new(schema, ast_node) }
|
10
|
+
|
11
|
+
describe "#type" do
|
12
|
+
describe "simple types" do
|
13
|
+
let(:type_name) { "DairyProductInput" }
|
14
|
+
it "it gets types from the schema" do
|
15
|
+
assert_equal(DairyProductInputType, type_expression.type)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "non-null types" do
|
20
|
+
let(:type_name) { "String!"}
|
21
|
+
it "makes non-null types" do
|
22
|
+
assert_equal(GraphQL::STRING_TYPE.to_non_null_type, type_expression.type)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "list types" do
|
27
|
+
let(:type_name) { "[DairyAnimal!]!" }
|
28
|
+
|
29
|
+
it "makes list types" do
|
30
|
+
expected = DairyAnimalEnum
|
31
|
+
.to_non_null_type
|
32
|
+
.to_list_type
|
33
|
+
.to_non_null_type
|
34
|
+
assert_equal(expected, type_expression.type)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|