graphql 0.17.2 → 0.18.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 (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