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