graphql 0.17.2 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql.rb +1 -0
- data/lib/graphql/analysis/query_depth.rb +1 -1
- data/lib/graphql/base_type.rb +25 -1
- data/lib/graphql/define.rb +2 -0
- data/lib/graphql/define/assign_connection.rb +11 -0
- data/lib/graphql/define/assign_global_id_field.rb +11 -0
- data/lib/graphql/define/assign_object_field.rb +21 -20
- data/lib/graphql/define/defined_object_proxy.rb +2 -2
- data/lib/graphql/define/instance_definable.rb +13 -3
- data/lib/graphql/field.rb +1 -1
- data/lib/graphql/language/generation.rb +57 -6
- data/lib/graphql/language/lexer.rb +434 -212
- data/lib/graphql/language/lexer.rl +18 -0
- data/lib/graphql/language/nodes.rb +75 -0
- data/lib/graphql/language/parser.rb +853 -341
- data/lib/graphql/language/parser.y +114 -17
- data/lib/graphql/query.rb +15 -1
- data/lib/graphql/relay.rb +13 -0
- data/lib/graphql/relay/array_connection.rb +80 -0
- data/lib/graphql/relay/base_connection.rb +138 -0
- data/lib/graphql/relay/connection_field.rb +54 -0
- data/lib/graphql/relay/connection_type.rb +25 -0
- data/lib/graphql/relay/edge.rb +22 -0
- data/lib/graphql/relay/edge_type.rb +14 -0
- data/lib/graphql/relay/global_id_resolve.rb +15 -0
- data/lib/graphql/relay/global_node_identification.rb +124 -0
- data/lib/graphql/relay/mutation.rb +146 -0
- data/lib/graphql/relay/page_info.rb +13 -0
- data/lib/graphql/relay/relation_connection.rb +98 -0
- data/lib/graphql/schema.rb +3 -0
- data/lib/graphql/schema/printer.rb +12 -2
- data/lib/graphql/static_validation/message.rb +9 -5
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +7 -7
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +6 -6
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +17 -9
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_used.rb +17 -6
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +2 -2
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +5 -5
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
- data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +12 -11
- data/lib/graphql/static_validation/type_stack.rb +33 -2
- data/lib/graphql/static_validation/validation_context.rb +5 -0
- data/lib/graphql/version.rb +1 -1
- data/readme.md +16 -4
- data/spec/graphql/analysis/analyze_query_spec.rb +31 -2
- data/spec/graphql/analysis/query_complexity_spec.rb +24 -0
- data/spec/graphql/argument_spec.rb +1 -1
- data/spec/graphql/define/instance_definable_spec.rb +9 -0
- data/spec/graphql/field_spec.rb +1 -1
- data/spec/graphql/internal_representation/rewrite_spec.rb +3 -3
- data/spec/graphql/language/generation_spec.rb +25 -4
- data/spec/graphql/language/parser_spec.rb +116 -1
- data/spec/graphql/query_spec.rb +10 -0
- data/spec/graphql/relay/array_connection_spec.rb +164 -0
- data/spec/graphql/relay/connection_type_spec.rb +37 -0
- data/spec/graphql/relay/global_node_identification_spec.rb +149 -0
- data/spec/graphql/relay/mutation_spec.rb +55 -0
- data/spec/graphql/relay/page_info_spec.rb +106 -0
- data/spec/graphql/relay/relation_connection_spec.rb +348 -0
- data/spec/graphql/schema/printer_spec.rb +8 -0
- data/spec/graphql/schema/reduce_types_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +12 -6
- data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +8 -4
- data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +7 -2
- data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +5 -3
- data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +5 -2
- data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +10 -2
- data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +8 -4
- data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +8 -4
- data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +6 -3
- data/spec/spec_helper.rb +7 -0
- data/spec/support/dairy_app.rb +11 -10
- data/spec/support/star_wars_data.rb +65 -58
- data/spec/support/star_wars_schema.rb +192 -54
- 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|
|
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
|
-
|
123
|
+
node_variables
|
119
124
|
.select { |name, usage| usage.declared? && !usage.used? }
|
120
|
-
.
|
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
|
-
|
128
|
+
node_variables
|
124
129
|
.select { |name, usage| usage.used? && !usage.declared? }
|
125
|
-
.
|
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
|
74
|
-
|
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
|
data/lib/graphql/version.rb
CHANGED
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
|
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
|
-
-
|
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
|
-
-
|
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(:
|
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 = [
|
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) }
|
@@ -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
|
data/spec/graphql/field_spec.rb
CHANGED
@@ -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
|
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 [
|
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 [
|
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:
|
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
|
|