graphql 0.9.5 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/base_type.rb +24 -0
  3. data/lib/graphql/definition_helpers/defined_by_config.rb +5 -4
  4. data/lib/graphql/definition_helpers/non_null_with_bang.rb +1 -1
  5. data/lib/graphql/definition_helpers/type_definer.rb +1 -1
  6. data/lib/graphql/enum_type.rb +16 -3
  7. data/lib/graphql/input_object_type.rb +10 -0
  8. data/lib/graphql/language.rb +0 -5
  9. data/lib/graphql/language/nodes.rb +79 -75
  10. data/lib/graphql/language/parser.rb +109 -106
  11. data/lib/graphql/language/transform.rb +100 -91
  12. data/lib/graphql/language/visitor.rb +78 -74
  13. data/lib/graphql/list_type.rb +5 -0
  14. data/lib/graphql/non_null_type.rb +6 -2
  15. data/lib/graphql/query.rb +58 -9
  16. data/lib/graphql/query/arguments.rb +29 -26
  17. data/lib/graphql/query/base_execution/value_resolution.rb +3 -3
  18. data/lib/graphql/query/directive_chain.rb +1 -1
  19. data/lib/graphql/query/executor.rb +6 -27
  20. data/lib/graphql/query/literal_input.rb +89 -0
  21. data/lib/graphql/query/ruby_input.rb +20 -0
  22. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  23. data/lib/graphql/query/variables.rb +39 -0
  24. data/lib/graphql/scalar_type.rb +27 -5
  25. data/lib/graphql/schema.rb +5 -0
  26. data/lib/graphql/schema/type_expression.rb +28 -0
  27. data/lib/graphql/static_validation/literal_validator.rb +5 -2
  28. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +0 -2
  29. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +1 -1
  30. data/lib/graphql/version.rb +1 -1
  31. data/readme.md +3 -2
  32. data/spec/graphql/enum_type_spec.rb +7 -2
  33. data/spec/graphql/input_object_type_spec.rb +45 -0
  34. data/spec/graphql/language/parser_spec.rb +2 -1
  35. data/spec/graphql/language/transform_spec.rb +5 -0
  36. data/spec/graphql/query/base_execution/value_resolution_spec.rb +46 -0
  37. data/spec/graphql/query/context_spec.rb +37 -0
  38. data/spec/graphql/query/executor_spec.rb +33 -0
  39. data/spec/graphql/query_spec.rb +110 -26
  40. data/spec/graphql/schema/type_expression_spec.rb +38 -0
  41. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +2 -2
  42. data/spec/support/dairy_app.rb +17 -17
  43. data/spec/support/dairy_data.rb +2 -2
  44. 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.types[node.type.name]
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
@@ -1,3 +1,3 @@
1
1
  module GraphQL
2
- VERSION = "0.9.5"
2
+ VERSION = "0.10.0"
3
3
  end
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
- - Incoming enums should be exposed as `EnumValue`s, not `Nodes::Enum`s
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.coerce("YAK"))
8
- assert_equal(1, enum.coerce("COW"))
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, $things: [SomeType]!) {id, name, ...people}|), "mutation with arguments")
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
@@ -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
- DummySchema,
36
+ schema,
35
37
  query_string,
36
- variables: {"cheeseId" => 2},
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 'context' do
163
- let(:query_type) { GraphQL::ObjectType.define {
164
- field :context, types.String do
165
- argument :key, !types.String
166
- resolve -> (target, args, ctx) { ctx[args[:key]] }
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
- field :contextAstNodeName, types.String do
169
- resolve -> (target, args, ctx) { ctx.ast_node.class.name }
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 "access to passed-in values" do
176
- let(:query_string) { %|
177
- query getCtx { context(key: "some_key") }
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
- it 'passes context to fields' do
181
- expected = {"data" => {"context" => "some value"}}
182
- assert_equal(expected, query.result)
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 "access to the AST node" do
187
- let(:query_string) { %|
188
- query getCtx { contextAstNodeName }
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
- it 'provides access to the AST node' do
192
- expected = {"data" => {"contextAstNodeName" => "GraphQL::Language::Nodes::Field"}}
193
- assert_equal(expected, query.result)
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