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.
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