graphql 0.9.5 → 0.10.0
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/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
|