graphql 0.17.2 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +1 -0
  3. data/lib/graphql/analysis/query_depth.rb +1 -1
  4. data/lib/graphql/base_type.rb +25 -1
  5. data/lib/graphql/define.rb +2 -0
  6. data/lib/graphql/define/assign_connection.rb +11 -0
  7. data/lib/graphql/define/assign_global_id_field.rb +11 -0
  8. data/lib/graphql/define/assign_object_field.rb +21 -20
  9. data/lib/graphql/define/defined_object_proxy.rb +2 -2
  10. data/lib/graphql/define/instance_definable.rb +13 -3
  11. data/lib/graphql/field.rb +1 -1
  12. data/lib/graphql/language/generation.rb +57 -6
  13. data/lib/graphql/language/lexer.rb +434 -212
  14. data/lib/graphql/language/lexer.rl +18 -0
  15. data/lib/graphql/language/nodes.rb +75 -0
  16. data/lib/graphql/language/parser.rb +853 -341
  17. data/lib/graphql/language/parser.y +114 -17
  18. data/lib/graphql/query.rb +15 -1
  19. data/lib/graphql/relay.rb +13 -0
  20. data/lib/graphql/relay/array_connection.rb +80 -0
  21. data/lib/graphql/relay/base_connection.rb +138 -0
  22. data/lib/graphql/relay/connection_field.rb +54 -0
  23. data/lib/graphql/relay/connection_type.rb +25 -0
  24. data/lib/graphql/relay/edge.rb +22 -0
  25. data/lib/graphql/relay/edge_type.rb +14 -0
  26. data/lib/graphql/relay/global_id_resolve.rb +15 -0
  27. data/lib/graphql/relay/global_node_identification.rb +124 -0
  28. data/lib/graphql/relay/mutation.rb +146 -0
  29. data/lib/graphql/relay/page_info.rb +13 -0
  30. data/lib/graphql/relay/relation_connection.rb +98 -0
  31. data/lib/graphql/schema.rb +3 -0
  32. data/lib/graphql/schema/printer.rb +12 -2
  33. data/lib/graphql/static_validation/message.rb +9 -5
  34. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  35. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  36. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  37. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +7 -7
  38. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
  39. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
  40. data/lib/graphql/static_validation/rules/fields_will_merge.rb +6 -6
  41. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +17 -9
  42. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  43. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +1 -1
  44. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  45. data/lib/graphql/static_validation/rules/fragments_are_used.rb +17 -6
  46. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  47. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +2 -2
  48. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +5 -5
  49. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  50. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +12 -11
  51. data/lib/graphql/static_validation/type_stack.rb +33 -2
  52. data/lib/graphql/static_validation/validation_context.rb +5 -0
  53. data/lib/graphql/version.rb +1 -1
  54. data/readme.md +16 -4
  55. data/spec/graphql/analysis/analyze_query_spec.rb +31 -2
  56. data/spec/graphql/analysis/query_complexity_spec.rb +24 -0
  57. data/spec/graphql/argument_spec.rb +1 -1
  58. data/spec/graphql/define/instance_definable_spec.rb +9 -0
  59. data/spec/graphql/field_spec.rb +1 -1
  60. data/spec/graphql/internal_representation/rewrite_spec.rb +3 -3
  61. data/spec/graphql/language/generation_spec.rb +25 -4
  62. data/spec/graphql/language/parser_spec.rb +116 -1
  63. data/spec/graphql/query_spec.rb +10 -0
  64. data/spec/graphql/relay/array_connection_spec.rb +164 -0
  65. data/spec/graphql/relay/connection_type_spec.rb +37 -0
  66. data/spec/graphql/relay/global_node_identification_spec.rb +149 -0
  67. data/spec/graphql/relay/mutation_spec.rb +55 -0
  68. data/spec/graphql/relay/page_info_spec.rb +106 -0
  69. data/spec/graphql/relay/relation_connection_spec.rb +348 -0
  70. data/spec/graphql/schema/printer_spec.rb +8 -0
  71. data/spec/graphql/schema/reduce_types_spec.rb +1 -1
  72. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +12 -6
  73. data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +8 -4
  74. data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +4 -2
  75. data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +4 -2
  76. data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +7 -2
  77. data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +4 -2
  78. data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +6 -3
  79. data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +5 -3
  80. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -2
  81. data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +5 -2
  82. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +10 -2
  83. data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +6 -3
  84. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +8 -4
  85. data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +8 -4
  86. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +6 -3
  87. data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +6 -3
  88. data/spec/spec_helper.rb +7 -0
  89. data/spec/support/dairy_app.rb +11 -10
  90. data/spec/support/star_wars_data.rb +65 -58
  91. data/spec/support/star_wars_schema.rb +192 -54
  92. metadata +84 -2
@@ -14,7 +14,7 @@ module GraphQL
14
14
  include GraphQL::StaticValidation::Message::MessageHelper
15
15
 
16
16
  class VariableUsage
17
- attr_accessor :ast_node, :used_by, :declared_by
17
+ attr_accessor :ast_node, :used_by, :declared_by, :path
18
18
  def used?
19
19
  !!@used_by
20
20
  end
@@ -50,7 +50,11 @@ module GraphQL
50
50
  context.visitor[GraphQL::Language::Nodes::OperationDefinition] << -> (node, parent) {
51
51
  # mark variables as defined:
52
52
  var_hash = variable_usages_for_context[node]
53
- node.variables.each { |var| var_hash[var.name].declared_by = node }
53
+ node.variables.each { |var|
54
+ var_usage = var_hash[var.name]
55
+ var_usage.declared_by = node
56
+ var_usage.path = context.path
57
+ }
54
58
  }
55
59
  context.visitor[GraphQL::Language::Nodes::OperationDefinition].leave << pop_variable_context_stack
56
60
 
@@ -74,6 +78,7 @@ module GraphQL
74
78
  usage = declared_variables[node.name]
75
79
  usage.used_by = usage_context
76
80
  usage.ast_node = node
81
+ usage.path = context.path
77
82
  }
78
83
 
79
84
 
@@ -104,6 +109,7 @@ module GraphQL
104
109
  if child_usage.used?
105
110
  parent_usage.ast_node = child_usage.ast_node
106
111
  parent_usage.used_by = child_usage.used_by
112
+ parent_usage.path = child_usage.path
107
113
  end
108
114
  end
109
115
  follow_spreads(def_node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments)
@@ -113,20 +119,15 @@ module GraphQL
113
119
  # Determine all the error messages,
114
120
  # Then push messages into the validation context
115
121
  def create_errors(node_variables, context)
116
- errors = []
117
122
  # Declared but not used:
118
- errors += node_variables
123
+ node_variables
119
124
  .select { |name, usage| usage.declared? && !usage.used? }
120
- .map { |var_name, usage| ["Variable $#{var_name} is declared by #{usage.declared_by.name} but not used", usage.declared_by] }
125
+ .each { |var_name, usage| context.errors << message("Variable $#{var_name} is declared by #{usage.declared_by.name} but not used", usage.declared_by, path: usage.path) }
121
126
 
122
127
  # Used but not declared:
123
- errors += node_variables
128
+ node_variables
124
129
  .select { |name, usage| usage.used? && !usage.declared? }
125
- .map { |var_name, usage| ["Variable $#{var_name} is used by #{usage.used_by.name} but not declared", usage.ast_node] }
126
-
127
- errors.each do |error_args|
128
- context.errors << message(*error_args)
129
- end
130
+ .each { |var_name, usage| context.errors << message("Variable $#{var_name} is used by #{usage.used_by.name} but not declared", usage.ast_node, path: usage.path) }
130
131
  end
131
132
  end
132
133
  end
@@ -29,6 +29,9 @@ module GraphQL
29
29
  # @return [Array<GraphQL::Node::Argument>] arguments which have been entered
30
30
  attr_reader :argument_definitions
31
31
 
32
+ # @return [Array<String>] fields which have been entered (by their AST name)
33
+ attr_reader :path
34
+
32
35
  # @param schema [GraphQL::Schema] the schema whose types to use when climbing this document
33
36
  # @param visitor [GraphQL::Language::Visitor] a visitor to follow & watch the types
34
37
  def initialize(schema, visitor)
@@ -37,6 +40,7 @@ module GraphQL
37
40
  @field_definitions = []
38
41
  @directive_definitions = []
39
42
  @argument_definitions = []
43
+ @path = []
40
44
  visitor.enter << -> (node, parent) { PUSH_STRATEGIES[node.class].push(self, node) }
41
45
  visitor.leave << -> (node, parent) { PUSH_STRATEGIES[node.class].pop(self, node) }
42
46
  end
@@ -63,25 +67,38 @@ module GraphQL
63
67
  object_type = object_type.unwrap
64
68
  end
65
69
  stack.object_types.push(object_type)
70
+ push_path_member(stack, node)
66
71
  end
67
72
 
68
73
  def pop(stack, node)
69
74
  stack.object_types.pop
75
+ stack.path.pop
70
76
  end
71
77
  end
72
78
 
73
- class FragmentDefinitionStrategy < FragmentWithTypeStrategy; end
74
- class InlineFragmentStrategy < FragmentWithTypeStrategy; end
79
+ class FragmentDefinitionStrategy < FragmentWithTypeStrategy
80
+ def push_path_member(stack, node)
81
+ stack.path.push("fragment #{node.name}")
82
+ end
83
+ end
84
+
85
+ class InlineFragmentStrategy < FragmentWithTypeStrategy
86
+ def push_path_member(stack, node)
87
+ stack.path.push("...#{node.type ? " on #{node.type}" : ""}")
88
+ end
89
+ end
75
90
 
76
91
  class OperationDefinitionStrategy
77
92
  def push(stack, node)
78
93
  # eg, QueryType, MutationType
79
94
  object_type = stack.schema.root_type_for_operation(node.operation_type)
80
95
  stack.object_types.push(object_type)
96
+ stack.path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
81
97
  end
82
98
 
83
99
  def pop(stack, node)
84
100
  stack.object_types.pop
101
+ stack.path.pop
85
102
  end
86
103
  end
87
104
 
@@ -98,11 +115,13 @@ module GraphQL
98
115
  else
99
116
  stack.object_types.push(nil)
100
117
  end
118
+ stack.path.push(node.alias || node.name)
101
119
  end
102
120
 
103
121
  def pop(stack, node)
104
122
  stack.field_definitions.pop
105
123
  stack.object_types.pop
124
+ stack.path.pop
106
125
  end
107
126
  end
108
127
 
@@ -137,10 +156,22 @@ module GraphQL
137
156
  argument_defn = nil
138
157
  end
139
158
  stack.argument_definitions.push(argument_defn)
159
+ stack.path.push(node.name)
140
160
  end
141
161
 
142
162
  def pop(stack, node)
143
163
  stack.argument_definitions.pop
164
+ stack.path.pop
165
+ end
166
+ end
167
+
168
+ class FragmentSpreadStrategy
169
+ def push(stack, node)
170
+ stack.path.push("... #{node.name}")
171
+ end
172
+
173
+ def pop(stack, node)
174
+ stack.path.pop
144
175
  end
145
176
  end
146
177
 
@@ -52,6 +52,11 @@ module GraphQL
52
52
  @type_stack.field_definitions.last
53
53
  end
54
54
 
55
+ # @return [Array<String>] Field names to get to the current field
56
+ def path
57
+ @type_stack.path.dup
58
+ end
59
+
55
60
  # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
56
61
  def directive_definition
57
62
  @type_stack.directive_definitions.last
@@ -1,3 +1,3 @@
1
1
  module GraphQL
2
- VERSION = "0.17.2"
2
+ VERSION = "0.18.0"
3
3
  end
data/readme.md CHANGED
@@ -15,6 +15,7 @@ A Ruby implementation of [GraphQL](http://graphql.org/).
15
15
  - [Testing](http://www.rubydoc.info/github/rmosolgo/graphql-ruby/file/guides/testing.md)
16
16
  - [Code Reuse](http://www.rubydoc.info/github/rmosolgo/graphql-ruby/file/guides/code_reuse.md)
17
17
  - [Security](http://www.rubydoc.info/github/rmosolgo/graphql-ruby/file/guides/security.md)
18
+ - [Relay](http://www.rubydoc.info/github/rmosolgo/graphql-ruby/file/guides/relay.md)
18
19
 
19
20
 
20
21
  - [API Documentation](http://www.rubydoc.info/github/rmosolgo/graphql-ruby)
@@ -88,7 +89,7 @@ result_hash = Schema.execute(query_string)
88
89
  If you're building a backend for [Relay](http://facebook.github.io/relay/), you'll need:
89
90
 
90
91
  - A JSON dump of the schema, which you can get by sending [`GraphQL::Introspection::INTROSPECTION_QUERY`](https://github.com/rmosolgo/graphql-ruby/blob/master/lib/graphql/introspection/introspection_query.rb)
91
- - Relay-specific helpers for GraphQL like Connections, node fields, and global ids. Here's one example of those: [`graphql-relay`](https://github.com/rmosolgo/graphql-relay-ruby)
92
+ - Relay-specific helpers for GraphQL, see [`GraphQL::Relay`](http://www.rubydoc.info/github/rmosolgo/graphql-ruby/file/guides/relay.md)
92
93
 
93
94
  ## Goals
94
95
 
@@ -109,7 +110,6 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
109
110
 
110
111
  - `graphql-ruby` + Rails demo ([src](https://github.com/rmosolgo/graphql-ruby-demo) / [heroku](http://graphql-ruby-demo.herokuapp.com))
111
112
  - [`graphql-batch`](https://github.com/shopify/graphql-batch), a batched query execution strategy
112
- - [Example Relay support](https://github.com/rmosolgo/graphql-relay-ruby) in Ruby
113
113
  - [`graphql-libgraphqlparser`](https://github.com/rmosolgo/graphql-libgraphqlparser), bindings to [libgraphqlparser](https://github.com/graphql/libgraphqlparser), a C-level parser.
114
114
 
115
115
  ### Blog Posts
@@ -127,7 +127,19 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
127
127
  - Problem: how does a field know which schema to look up the name from?
128
128
  - Problem: how can we load types in Rails without accessing the constant?
129
129
  - Maybe support by third-party library? `type("Post!")` could implement "type_missing", keeps `graphql-ruby` very simple
130
- - Type check improvements:
130
+ - StaticValidation improvements
131
+ - Include `path: [...]` in validation errors
131
132
  - Use catch-all type/field/argument definitions instead of terminating traversal
132
133
  - Reduce ad-hoc traversals?
133
- - Merge `graphql-relay-ruby` into this Repo so that they can stay in sync?
134
+ - Validators are order-dependent, is this a smell?
135
+ - Tests for interference between validators are poor
136
+ - Maybe this is a candidate for a rewrite? Can it somehow work more closely with query analysis? Somehow support the `Query#perform_validation` refactor?
137
+ - Add Rails-y argument validations, eg `less_than: 100`, `max_length: 255`, `one_of: [...]`
138
+ - Must be customizable
139
+ - Refactor `Query#perform_validation`, how can that be organized better?
140
+ - Relay:
141
+ - `GlobalNodeIdentification.to_global_id` should receive the type name and _object_, not `id`. (Or, maintain the "`type_name, id` in, `type_name, id` out" pattern?)
142
+ - Reduce duplication in ArrayConnection / RelationConnection
143
+ - Improve API for creating edges (better RANGE_ADD support)
144
+ - If the new edge isn't a member of the connection's objects, raise a nice error
145
+ - Rename `Connection#object` => `Connection#collection` with deprecation
@@ -26,7 +26,8 @@ describe GraphQL::Analysis do
26
26
  let(:type_collector) { TypeCollector.new }
27
27
  let(:analyzers) { [type_collector, node_counter] }
28
28
  let(:reduce_result) { GraphQL::Analysis.analyze_query(query, analyzers) }
29
- let(:query) { GraphQL::Query.new(DummySchema, query_string) }
29
+ let(:variables) { {} }
30
+ let(:query) { GraphQL::Query.new(DummySchema, query_string, variables: variables) }
30
31
  let(:query_string) {%|
31
32
  {
32
33
  cheese(id: 1) {
@@ -38,7 +39,7 @@ describe GraphQL::Analysis do
38
39
 
39
40
  it "calls the defined analyzers" do
40
41
  collected_types, node_counts = reduce_result
41
- expected_visited_types = [QueryType, CheeseType, GraphQL::INT_TYPE, GraphQL::STRING_TYPE]
42
+ expected_visited_types = [DairyAppQueryType, CheeseType, GraphQL::INT_TYPE, GraphQL::STRING_TYPE]
42
43
  assert_equal expected_visited_types, collected_types
43
44
  expected_node_counts = {
44
45
  GraphQL::Language::Nodes::OperationDefinition => 1,
@@ -46,5 +47,33 @@ describe GraphQL::Analysis do
46
47
  }
47
48
  assert_equal expected_node_counts, node_counts
48
49
  end
50
+
51
+ describe "when a variable is missing" do
52
+ let(:query_string) {%|
53
+ query something($cheeseId: Int!){
54
+ cheese(id: $cheeseId) {
55
+ id
56
+ flavor
57
+ }
58
+ }
59
+ |}
60
+ let(:variable_accessor) { -> (memo, visit_type, irep_node) { query.variables["cheeseId"] } }
61
+
62
+ before do
63
+ @previous_query_analyzers = DummySchema.query_analyzers.dup
64
+ DummySchema.query_analyzers.clear
65
+ DummySchema.query_analyzers << variable_accessor
66
+ end
67
+
68
+ after do
69
+ DummySchema.query_analyzers.clear
70
+ DummySchema.query_analyzers.push(*@previous_query_analyzers)
71
+ end
72
+
73
+ it "returns an error" do
74
+ error = query.result["errors"].first
75
+ assert_equal "Variable cheeseId of type Int! was provided invalid value", error["message"]
76
+ end
77
+ end
49
78
  end
50
79
  end
@@ -187,6 +187,30 @@ describe GraphQL::Analysis::QueryComplexity do
187
187
  end
188
188
  end
189
189
 
190
+ describe "relay types" do
191
+ let(:query) { GraphQL::Query.new(StarWarsSchema, query_string) }
192
+ let(:query_string) {%|
193
+ {
194
+ rebels {
195
+ ships {
196
+ edges {
197
+ node {
198
+ id
199
+ }
200
+ }
201
+ pageInfo {
202
+ hasNextPage
203
+ }
204
+ }
205
+ }
206
+ }
207
+ |}
208
+
209
+ it "gets the complexity" do
210
+ reduce_result
211
+ assert_equal 7, complexities.last
212
+ end
213
+ end
190
214
 
191
215
  describe "custom complexities" do
192
216
  let(:query) { GraphQL::Query.new(complexity_schema, query_string) }
@@ -9,7 +9,7 @@ describe GraphQL::Argument do
9
9
  end
10
10
  end
11
11
 
12
- err = assert_raises {
12
+ err = assert_raises(GraphQL::Schema::InvalidTypeError) {
13
13
  schema = GraphQL::Schema.new(query: query_type)
14
14
  schema.types
15
15
  }
@@ -52,4 +52,13 @@ describe GraphQL::Define::InstanceDefinable do
52
52
  assert_equal Date.new(2000, 6, 1), tomato.end_planting_on
53
53
  end
54
54
  end
55
+
56
+ describe ".define with keywords" do
57
+ it "applies definitions from keywords" do
58
+ okra = Garden::Vegetable.define(name: "Okra", plant_between: Date.new(2000, 5, 1)..Date.new(2000, 7, 1))
59
+ assert_equal "Okra", okra.name
60
+ assert_equal Date.new(2000, 5, 1), okra.start_planting_on
61
+ assert_equal Date.new(2000, 7, 1), okra.end_planting_on
62
+ end
63
+ end
55
64
  end
@@ -69,7 +69,7 @@ describe GraphQL::Field do
69
69
  name("something")
70
70
  end
71
71
  assert_equal "something", field.name
72
- assert_raises { field.name = "somethingelse" }
72
+ assert_raises(RuntimeError) { field.name = "somethingelse" }
73
73
  assert_equal "something", field.name
74
74
  end
75
75
 
@@ -23,15 +23,15 @@ describe GraphQL::InternalRepresentation::Rewrite do
23
23
  op_node = rewrite_result["getCheeses"]
24
24
 
25
25
  assert_equal 2, op_node.children.length
26
- assert_equal QueryType, op_node.return_type
26
+ assert_equal DairyAppQueryType, op_node.return_type
27
27
  first_field = op_node.children.values.first
28
28
  assert_equal 3, first_field.children.length
29
- assert_equal [QueryType], first_field.definitions.keys
29
+ assert_equal [DairyAppQueryType], first_field.definitions.keys
30
30
  assert_equal CheeseType, first_field.return_type
31
31
 
32
32
  second_field = op_node.children.values.last
33
33
  assert_equal 1, second_field.children.length
34
- assert_equal [QueryType], second_field.definitions.keys
34
+ assert_equal [DairyAppQueryType], second_field.definitions.keys
35
35
  assert_equal CheeseType, second_field.return_type
36
36
  assert second_field.inspect.is_a?(String)
37
37
  end
@@ -3,20 +3,21 @@ require "spec_helper"
3
3
  describe GraphQL::Language::Generation do
4
4
  let(:document) { GraphQL::Language::Parser.parse(query_string) }
5
5
  let(:query_string) {%|
6
- query getStuff($someVar: Int = 1, $anotherVar: [String!]) @skip(if: false) {
6
+ query getStuff($someVar: Int = 1, $anotherVar: [String!], $skipNested: Boolean! = false) @skip(if: false) {
7
7
  myField: someField(someArg: $someVar, ok: 1.4) @skip(if: $anotherVar) @thing(or: "Whatever")
8
8
  anotherField(someArg: [1, 2, 3]) {
9
9
  nestedField
10
- ... moreNestedFields @skip(if: true)
10
+ ... moreNestedFields @skip(if: $skipNested)
11
11
  }
12
12
  ... on OtherType @include(unless: false) {
13
- field(arg: [{key: "value", anotherKey: 0.9, anotherAnotherKey: WHATEVER}])
13
+ field(arg: [{ key: "value", anotherKey: 0.9, anotherAnotherKey: WHATEVER }])
14
14
  anotherField
15
15
  }
16
16
  ... {
17
17
  id
18
18
  }
19
19
  }
20
+
20
21
  fragment moreNestedFields on NestedType @or(something: "ok") {
21
22
  anotherNestedField
22
23
  }
@@ -30,13 +31,33 @@ describe GraphQL::Language::Generation do
30
31
  describe "inputs" do
31
32
  let(:query_string) {%|
32
33
  query {
33
- field(int: 3, float: 4.7e-24, bool: false, string: "☀︎🏆\\n escaped \\" unicode ¶ /", enum: ENUM_NAME, array: [7, 8, 9], object: {a: [1, 2, 3], b: {c: "4"}}, unicode_bom: "\xef\xbb\xbfquery")
34
+ field(int: 3, float: 4.7e-24, bool: false, string: "☀︎🏆\\n escaped \\" unicode ¶ /", enum: ENUM_NAME, array: [7, 8, 9], object: { a: [1, 2, 3], b: { c: "4" } }, unicode_bom: "\xef\xbb\xbfquery")
35
+ }
36
+ |}
37
+
38
+ it "generate" do
39
+ assert_equal query_string.gsub(/^ /, "").strip, document.to_query_string
40
+ end
41
+ end
42
+
43
+ describe "schema" do
44
+ let(:query_string) {%|
45
+ schema {
46
+ query: Query
47
+ }
48
+
49
+ type Query {
50
+ field: String!
34
51
  }
35
52
  |}
36
53
 
37
54
  it "generate" do
38
55
  assert_equal query_string.gsub(/^ /, "").strip, document.to_query_string
39
56
  end
57
+
58
+ it "doesn't mutate the document" do
59
+ assert_equal document.to_query_string, document.to_query_string
60
+ end
40
61
  end
41
62
  end
42
63
  end
@@ -263,6 +263,121 @@ describe GraphQL::Language::Parser do
263
263
  assert(document)
264
264
  end
265
265
  end
266
+
267
+ describe "schema" do
268
+ it "parses the test schema" do
269
+ schema = DummySchema
270
+ schema_string = GraphQL::Schema::Printer.print_schema(schema)
271
+ document = GraphQL::Language::Parser.parse(schema_string)
272
+
273
+ assert_equal schema_string, document.to_query_string
274
+ end
275
+
276
+ it "parses mimal schema definition" do
277
+ document = GraphQL::Language::Parser.parse('schema { query: QueryRoot }')
278
+
279
+ schema = document.definitions.first
280
+ assert_equal 'QueryRoot', schema.query
281
+ assert_equal nil, schema.mutation
282
+ assert_equal nil, schema.subscription
283
+ end
284
+
285
+ it "parses full schema definitions" do
286
+ document = GraphQL::Language::Parser.parse('
287
+ schema {
288
+ query: QueryRoot
289
+ mutation: MutationRoot
290
+ subscription: SubscriptionRoot
291
+ }
292
+ ')
293
+
294
+ schema = document.definitions.first
295
+ assert_equal 'QueryRoot', schema.query
296
+ assert_equal 'MutationRoot', schema.mutation
297
+ assert_equal 'SubscriptionRoot', schema.subscription
298
+ end
299
+
300
+ it "parses object types" do
301
+ document = GraphQL::Language::Parser.parse('
302
+ type Comment implements Node {
303
+ id: ID!
304
+ }
305
+ ')
306
+
307
+ type = document.definitions.first
308
+ assert_equal GraphQL::Language::Nodes::ObjectTypeDefinition, type.class
309
+ assert_equal 'Comment', type.name
310
+ assert_equal ['Node'], type.interfaces
311
+ assert_equal ['id'], type.fields.map(&:name)
312
+ assert_equal [], type.fields[0].arguments
313
+ assert_equal 'ID', type.fields[0].type.of_type.name
314
+ end
315
+
316
+ it "parses field arguments" do
317
+ document = GraphQL::Language::Parser.parse('
318
+ type Mutation {
319
+ post(id: ID!, data: PostData = { message: "First!1!", type: BLOG, tags: ["Test", "Annoying"] }): Post
320
+ }
321
+ ')
322
+
323
+ field = document.definitions.first.fields.first
324
+ assert_equal ['id', 'data'], field.arguments.map(&:name)
325
+ data_arg = field.arguments[1]
326
+ assert_equal 'PostData', data_arg.type.name
327
+ assert_equal ['message', 'type', 'tags'], data_arg.default_value.arguments.map(&:name)
328
+ tags_arg = data_arg.default_value.arguments[2]
329
+ assert_equal ['Test', 'Annoying'], tags_arg.value
330
+ end
331
+
332
+ it "parses scalar types" do
333
+ document = GraphQL::Language::Parser.parse('scalar DateTime')
334
+
335
+ type = document.definitions.first
336
+ assert_equal GraphQL::Language::Nodes::ScalarTypeDefinition, type.class
337
+ assert_equal 'DateTime', type.name
338
+ end
339
+
340
+ it "parses interface types" do
341
+ document = GraphQL::Language::Parser.parse('
342
+ interface Node {
343
+ id: ID!
344
+ }
345
+ ')
346
+
347
+ type = document.definitions.first
348
+ assert_equal GraphQL::Language::Nodes::InterfaceTypeDefinition, type.class
349
+ assert_equal 'Node', type.name
350
+ assert_equal ['id'], type.fields.map(&:name)
351
+ assert_equal [], type.fields[0].arguments
352
+ assert_equal 'ID', type.fields[0].type.of_type.name
353
+ end
354
+
355
+ it "parses enum types" do
356
+ document = GraphQL::Language::Parser.parse('
357
+ enum DogCommand { SIT, DOWN, HEEL }
358
+ ')
359
+
360
+ type = document.definitions.first
361
+ assert_equal GraphQL::Language::Nodes::EnumTypeDefinition, type.class
362
+ assert_equal 'DogCommand', type.name
363
+ assert_equal ['SIT', 'DOWN', 'HEEL'], type.values
364
+ end
365
+
366
+ it "parses input object types" do
367
+ document = GraphQL::Language::Parser.parse('
368
+ input EmptyMutationInput {
369
+ clientMutationId: String
370
+ }
371
+ ')
372
+
373
+ type = document.definitions.first
374
+ assert_equal GraphQL::Language::Nodes::InputObjectTypeDefinition, type.class
375
+ assert_equal 'EmptyMutationInput', type.name
376
+ assert_equal ['clientMutationId'], type.fields.map(&:name)
377
+ assert_equal 'String', type.fields[0].type.name
378
+ assert_equal nil, type.fields[0].default_value
379
+ end
380
+ end
266
381
  end
267
382
 
268
383
  describe "errors" do
@@ -286,7 +401,7 @@ describe GraphQL::Language::Parser do
286
401
  end
287
402
 
288
403
  it "handles unexpected ends" do
289
- err = assert_raises { GraphQL.parse("{ ") }
404
+ err = assert_raises(GraphQL::ParseError) { GraphQL.parse("{ ") }
290
405
  assert_equal "Unexpected end of document", err.message
291
406
  end
292
407