graphql 0.18.5 → 0.18.6
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/argument.rb +14 -0
- data/lib/graphql/enum_type.rb +5 -1
- data/lib/graphql/field.rb +1 -1
- data/lib/graphql/input_object_type.rb +1 -0
- data/lib/graphql/interface_type.rb +1 -1
- data/lib/graphql/language/parser_tests.rb +577 -0
- data/lib/graphql/object_type.rb +1 -1
- data/lib/graphql/schema/loader.rb +133 -0
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/argument_spec.rb +5 -0
- data/spec/graphql/enum_type_spec.rb +7 -0
- data/spec/graphql/field_spec.rb +6 -0
- data/spec/graphql/language/parser_spec.rb +3 -558
- data/spec/graphql/object_type_spec.rb +6 -0
- data/spec/graphql/schema/loader_spec.rb +147 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 097a1f2ab62a05d6617d2445c0771066c1d547a3
|
4
|
+
data.tar.gz: c65cc3378d3a1e525881abfde3c520b2f2eac71a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 913f57d14573f3fef716bd604e1dc62827d1fb62361bde854e42fe32f357a3e88ee382f1f1605a1bb11a16fcc80f2a9e7ff5e223a408c83bf5b224b8e9d76913
|
7
|
+
data.tar.gz: cfd8abc3faad374f484e97ef7b3505486c60067fefb6c1295f3eb55f0478f750dd344c3da9a876c87179a8f3772597245a05e561430df4a4c4f685f055e17a4b
|
data/lib/graphql.rb
CHANGED
data/lib/graphql/argument.rb
CHANGED
@@ -26,5 +26,19 @@ module GraphQL
|
|
26
26
|
end
|
27
27
|
|
28
28
|
attr_writer :name
|
29
|
+
|
30
|
+
def type=(new_return_type)
|
31
|
+
ensure_defined
|
32
|
+
@clean_type = nil
|
33
|
+
@dirty_type = new_return_type
|
34
|
+
end
|
35
|
+
|
36
|
+
# Get the return type for this field.
|
37
|
+
def type
|
38
|
+
@clean_type ||= begin
|
39
|
+
ensure_defined
|
40
|
+
GraphQL::BaseType.resolve_related_type(@dirty_type)
|
41
|
+
end
|
42
|
+
end
|
29
43
|
end
|
30
44
|
end
|
data/lib/graphql/enum_type.rb
CHANGED
@@ -56,7 +56,7 @@ module GraphQL
|
|
56
56
|
# }
|
57
57
|
#
|
58
58
|
class EnumType < GraphQL::BaseType
|
59
|
-
accepts_definitions value: GraphQL::Define::AssignEnumValue
|
59
|
+
accepts_definitions :values, value: GraphQL::Define::AssignEnumValue
|
60
60
|
|
61
61
|
def initialize
|
62
62
|
@values_by_name = {}
|
@@ -124,6 +124,10 @@ module GraphQL
|
|
124
124
|
#
|
125
125
|
# Created with the `value` helper
|
126
126
|
class EnumValue
|
127
|
+
def self.define(name:, description: nil, deprecation_reason: nil, value: nil)
|
128
|
+
new(name: name, description: description, deprecation_reason: deprecation_reason, value: value)
|
129
|
+
end
|
130
|
+
|
127
131
|
attr_accessor :name, :description, :deprecation_reason, :value
|
128
132
|
def initialize(name:, description:, deprecation_reason:, value:)
|
129
133
|
@name = name
|
data/lib/graphql/field.rb
CHANGED
@@ -120,7 +120,7 @@ module GraphQL
|
|
120
120
|
#
|
121
121
|
class Field
|
122
122
|
include GraphQL::Define::InstanceDefinable
|
123
|
-
accepts_definitions :name, :description, :resolve, :type, :property, :deprecation_reason, :complexity, :hash_key, argument: GraphQL::Define::AssignArgument
|
123
|
+
accepts_definitions :name, :description, :resolve, :type, :property, :deprecation_reason, :complexity, :hash_key, :arguments, argument: GraphQL::Define::AssignArgument
|
124
124
|
|
125
125
|
lazy_defined_attr_accessor :deprecation_reason, :description, :property, :hash_key
|
126
126
|
|
@@ -23,7 +23,7 @@ module GraphQL
|
|
23
23
|
#
|
24
24
|
class InterfaceType < GraphQL::BaseType
|
25
25
|
include GraphQL::BaseType::HasPossibleTypes
|
26
|
-
accepts_definitions :resolve_type, field: GraphQL::Define::AssignObjectField
|
26
|
+
accepts_definitions :resolve_type, :fields, field: GraphQL::Define::AssignObjectField
|
27
27
|
|
28
28
|
lazy_defined_attr_accessor :fields
|
29
29
|
|
@@ -0,0 +1,577 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Language
|
3
|
+
# If you create your own GraphQL parser, can verify it using these tests.
|
4
|
+
#
|
5
|
+
# @example Include these tests in a Minitest suite
|
6
|
+
# require 'graphql/language/parser_tests'
|
7
|
+
#
|
8
|
+
# describe MyParser do
|
9
|
+
# include GraphQL::Language::ParserTests
|
10
|
+
# subject { MyParser }
|
11
|
+
# end
|
12
|
+
module ParserTests
|
13
|
+
def self.included(test)
|
14
|
+
test.send(:describe, "Parser Tests") do
|
15
|
+
let(:document) { subject.parse(query_string) }
|
16
|
+
let(:query_string) {%|
|
17
|
+
query getStuff($someVar: Int = 1, $anotherVar: [String!] ) @skip(if: false) {
|
18
|
+
myField: someField(someArg: $someVar, ok: 1.4) @skip(if: $anotherVar) @thing(or: "Whatever")
|
19
|
+
|
20
|
+
anotherField(someArg: [1,2,3]) {
|
21
|
+
nestedField
|
22
|
+
... moreNestedFields @skip(if: true)
|
23
|
+
}
|
24
|
+
|
25
|
+
... on OtherType @include(unless: false){
|
26
|
+
field(arg: [{key: "value", anotherKey: 0.9, anotherAnotherKey: WHATEVER}])
|
27
|
+
anotherField
|
28
|
+
}
|
29
|
+
|
30
|
+
... {
|
31
|
+
id
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
fragment moreNestedFields on NestedType @or(something: "ok") {
|
36
|
+
anotherNestedField
|
37
|
+
}
|
38
|
+
|}
|
39
|
+
|
40
|
+
describe ".parse" do
|
41
|
+
it "parses queries" do
|
42
|
+
assert document
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "visited nodes" do
|
46
|
+
let(:query) { document.definitions.first }
|
47
|
+
let(:fragment_def) { document.definitions.last }
|
48
|
+
|
49
|
+
it "creates a valid document" do
|
50
|
+
assert document.is_a?(GraphQL::Language::Nodes::Document)
|
51
|
+
assert_equal 2, document.definitions.length
|
52
|
+
end
|
53
|
+
|
54
|
+
it "creates a valid operation" do
|
55
|
+
assert query.is_a?(GraphQL::Language::Nodes::OperationDefinition)
|
56
|
+
assert_equal "getStuff", query.name
|
57
|
+
assert_equal "query", query.operation_type
|
58
|
+
assert_equal 2, query.variables.length
|
59
|
+
assert_equal 4, query.selections.length
|
60
|
+
assert_equal 1, query.directives.length
|
61
|
+
assert_equal [2, 13], [query.line, query.col]
|
62
|
+
end
|
63
|
+
|
64
|
+
it "creates a valid fragment definition" do
|
65
|
+
assert fragment_def.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
|
66
|
+
assert_equal "moreNestedFields", fragment_def.name
|
67
|
+
assert_equal 1, fragment_def.selections.length
|
68
|
+
assert_equal "NestedType", fragment_def.type
|
69
|
+
assert_equal 1, fragment_def.directives.length
|
70
|
+
assert_equal [20, 13], fragment_def.position
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "variable definitions" do
|
74
|
+
let(:optional_var) { query.variables.first }
|
75
|
+
it "gets name and type" do
|
76
|
+
assert_equal "someVar", optional_var.name
|
77
|
+
assert_equal "Int", optional_var.type.name
|
78
|
+
end
|
79
|
+
|
80
|
+
it "gets default value" do
|
81
|
+
assert_equal 1, optional_var.default_value
|
82
|
+
end
|
83
|
+
|
84
|
+
it "gets position info" do
|
85
|
+
assert_equal [2, 28], optional_var.position
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "fields" do
|
90
|
+
let(:leaf_field) { query.selections.first }
|
91
|
+
let(:parent_field) { query.selections[1] }
|
92
|
+
|
93
|
+
it "gets name, alias, arguments and directives" do
|
94
|
+
assert_equal "someField", leaf_field.name
|
95
|
+
assert_equal "myField", leaf_field.alias
|
96
|
+
assert_equal 2, leaf_field.directives.length
|
97
|
+
assert_equal 2, leaf_field.arguments.length
|
98
|
+
end
|
99
|
+
|
100
|
+
it "gets nested fields" do
|
101
|
+
assert_equal 2, parent_field.selections.length
|
102
|
+
end
|
103
|
+
|
104
|
+
it "gets location info" do
|
105
|
+
assert_equal [3 ,15], leaf_field.position
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "when the arguments list is empty" do
|
109
|
+
let(:query_string) { "{ field() }"}
|
110
|
+
let(:field) { query.selections.first }
|
111
|
+
it "has zero arguments" do
|
112
|
+
assert_equal 0, field.arguments.length
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "when selections are empty" do
|
117
|
+
let(:query_string) { "{ field { } }"}
|
118
|
+
let(:field) { query.selections.first }
|
119
|
+
it "has zero selections" do
|
120
|
+
assert_equal 0, field.selections.length
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "arguments" do
|
126
|
+
let(:literal_argument) { query.selections.first.arguments.last }
|
127
|
+
let(:variable_argument) { query.selections.first.arguments.first }
|
128
|
+
|
129
|
+
it "gets name and literal value" do
|
130
|
+
assert_equal "ok", literal_argument.name
|
131
|
+
assert_equal 1.4, literal_argument.value
|
132
|
+
end
|
133
|
+
|
134
|
+
it "gets name and variable value" do
|
135
|
+
assert_equal "someArg", variable_argument.name
|
136
|
+
assert_equal "someVar", variable_argument.value.name
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
it "gets position info" do
|
141
|
+
assert_equal [3, 34], variable_argument.position
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "fragment spreads" do
|
146
|
+
let(:fragment_spread) { query.selections[1].selections.last }
|
147
|
+
it "gets the name and directives" do
|
148
|
+
assert_equal "moreNestedFields", fragment_spread.name
|
149
|
+
assert_equal 1, fragment_spread.directives.length
|
150
|
+
end
|
151
|
+
|
152
|
+
it "gets position info" do
|
153
|
+
assert_equal [7, 17], fragment_spread.position
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe "directives" do
|
158
|
+
let(:variable_directive) { query.selections.first.directives.first }
|
159
|
+
|
160
|
+
it "gets the name and arguments" do
|
161
|
+
assert_equal "skip", variable_directive.name
|
162
|
+
assert_equal "if", variable_directive.arguments.first.name
|
163
|
+
assert_equal 1, variable_directive.arguments.length
|
164
|
+
end
|
165
|
+
|
166
|
+
it "gets position info" do
|
167
|
+
assert_equal [3, 62], variable_directive.position
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe "inline fragments" do
|
172
|
+
let(:inline_fragment) { query.selections[2] }
|
173
|
+
let(:typeless_inline_fragment) { query.selections[3] }
|
174
|
+
|
175
|
+
it "gets the type and directives" do
|
176
|
+
assert_equal "OtherType", inline_fragment.type
|
177
|
+
assert_equal 2, inline_fragment.selections.length
|
178
|
+
assert_equal 1, inline_fragment.directives.length
|
179
|
+
end
|
180
|
+
|
181
|
+
it "gets inline fragments without type conditions" do
|
182
|
+
assert_equal nil, typeless_inline_fragment.type
|
183
|
+
assert_equal 1, typeless_inline_fragment.selections.length
|
184
|
+
assert_equal 0, typeless_inline_fragment.directives.length
|
185
|
+
end
|
186
|
+
|
187
|
+
it "gets position info" do
|
188
|
+
assert_equal [10, 15], inline_fragment.position
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe "inputs" do
|
193
|
+
let(:query_string) {%|
|
194
|
+
{
|
195
|
+
field(
|
196
|
+
int: 3,
|
197
|
+
float: 4.7e-24,
|
198
|
+
bool: false,
|
199
|
+
string: "☀︎🏆\\n escaped \\" unicode \\u00b6 /",
|
200
|
+
enum: ENUM_NAME,
|
201
|
+
array: [7, 8, 9]
|
202
|
+
object: {a: [1,2,3], b: {c: "4"}}
|
203
|
+
unicode_bom: "\xef\xbb\xbfquery"
|
204
|
+
keywordEnum: on
|
205
|
+
)
|
206
|
+
}
|
207
|
+
|}
|
208
|
+
|
209
|
+
let(:inputs) { document.definitions.first.selections.first.arguments }
|
210
|
+
|
211
|
+
it "parses ints" do
|
212
|
+
assert_equal 3, inputs[0].value
|
213
|
+
end
|
214
|
+
|
215
|
+
it "parses floats" do
|
216
|
+
assert_equal 0.47e-23, inputs[1].value
|
217
|
+
end
|
218
|
+
|
219
|
+
it "parses booleans" do
|
220
|
+
assert_equal false, inputs[2].value
|
221
|
+
end
|
222
|
+
|
223
|
+
it "parses UTF-8 strings" do
|
224
|
+
assert_equal %|☀︎🏆\n escaped " unicode ¶ /|, inputs[3].value
|
225
|
+
end
|
226
|
+
|
227
|
+
it "parses enums" do
|
228
|
+
assert_instance_of GraphQL::Language::Nodes::Enum, inputs[4].value
|
229
|
+
assert_equal "ENUM_NAME", inputs[4].value.name
|
230
|
+
end
|
231
|
+
|
232
|
+
it "parses arrays" do
|
233
|
+
assert_equal [7,8,9], inputs[5].value
|
234
|
+
end
|
235
|
+
|
236
|
+
it "parses objects" do
|
237
|
+
obj = inputs[6].value
|
238
|
+
assert_equal "a", obj.arguments[0].name
|
239
|
+
assert_equal [1,2,3], obj.arguments[0].value
|
240
|
+
assert_equal "b", obj.arguments[1].name
|
241
|
+
assert_equal "c", obj.arguments[1].value.arguments[0].name
|
242
|
+
assert_equal "4", obj.arguments[1].value.arguments[0].value
|
243
|
+
end
|
244
|
+
|
245
|
+
it "parses unicode bom" do
|
246
|
+
obj = inputs[7].value
|
247
|
+
assert_equal %|\xef\xbb\xbfquery|, inputs[7].value
|
248
|
+
end
|
249
|
+
|
250
|
+
it "parses enum 'on''" do
|
251
|
+
assert_equal "on", inputs[8].value.name
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
describe "unnamed queries" do
|
257
|
+
let(:query_string) {%|
|
258
|
+
{ name, age, height }
|
259
|
+
|}
|
260
|
+
let(:operation) { document.definitions.first }
|
261
|
+
|
262
|
+
it "parses unnamed queries" do
|
263
|
+
assert_equal 1, document.definitions.length
|
264
|
+
assert_equal "query", operation.operation_type
|
265
|
+
assert_equal nil, operation.name
|
266
|
+
assert_equal 3, operation.selections.length
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
describe "introspection query" do
|
271
|
+
let(:query_string) { GraphQL::Introspection::INTROSPECTION_QUERY }
|
272
|
+
|
273
|
+
it "parses a big ol' query" do
|
274
|
+
assert(document)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
describe "schema" do
|
279
|
+
it "parses the test schema" do
|
280
|
+
schema = DummySchema
|
281
|
+
schema_string = GraphQL::Schema::Printer.print_schema(schema)
|
282
|
+
document = subject.parse(schema_string)
|
283
|
+
|
284
|
+
assert_equal schema_string, document.to_query_string
|
285
|
+
end
|
286
|
+
|
287
|
+
it "parses mimal schema definition" do
|
288
|
+
document = subject.parse('schema { query: QueryRoot }')
|
289
|
+
|
290
|
+
schema = document.definitions.first
|
291
|
+
assert_equal 'QueryRoot', schema.query
|
292
|
+
assert_equal nil, schema.mutation
|
293
|
+
assert_equal nil, schema.subscription
|
294
|
+
end
|
295
|
+
|
296
|
+
it "parses full schema definitions" do
|
297
|
+
document = subject.parse('
|
298
|
+
schema {
|
299
|
+
query: QueryRoot
|
300
|
+
mutation: MutationRoot
|
301
|
+
subscription: SubscriptionRoot
|
302
|
+
}
|
303
|
+
')
|
304
|
+
|
305
|
+
schema = document.definitions.first
|
306
|
+
assert_equal 'QueryRoot', schema.query
|
307
|
+
assert_equal 'MutationRoot', schema.mutation
|
308
|
+
assert_equal 'SubscriptionRoot', schema.subscription
|
309
|
+
end
|
310
|
+
|
311
|
+
it "parses object types" do
|
312
|
+
document = subject.parse('
|
313
|
+
type Comment implements Node {
|
314
|
+
id: ID!
|
315
|
+
}
|
316
|
+
')
|
317
|
+
|
318
|
+
type = document.definitions.first
|
319
|
+
assert_equal GraphQL::Language::Nodes::ObjectTypeDefinition, type.class
|
320
|
+
assert_equal 'Comment', type.name
|
321
|
+
assert_equal ['Node'], type.interfaces
|
322
|
+
assert_equal ['id'], type.fields.map(&:name)
|
323
|
+
assert_equal [], type.fields[0].arguments
|
324
|
+
assert_equal 'ID', type.fields[0].type.of_type.name
|
325
|
+
end
|
326
|
+
|
327
|
+
it "parses field arguments" do
|
328
|
+
document = subject.parse('
|
329
|
+
type Mutation {
|
330
|
+
post(id: ID!, data: PostData = { message: "First!1!", type: BLOG, tags: ["Test", "Annoying"] }): Post
|
331
|
+
}
|
332
|
+
')
|
333
|
+
|
334
|
+
field = document.definitions.first.fields.first
|
335
|
+
assert_equal ['id', 'data'], field.arguments.map(&:name)
|
336
|
+
data_arg = field.arguments[1]
|
337
|
+
assert_equal 'PostData', data_arg.type.name
|
338
|
+
assert_equal ['message', 'type', 'tags'], data_arg.default_value.arguments.map(&:name)
|
339
|
+
tags_arg = data_arg.default_value.arguments[2]
|
340
|
+
assert_equal ['Test', 'Annoying'], tags_arg.value
|
341
|
+
end
|
342
|
+
|
343
|
+
it "parses scalar types" do
|
344
|
+
document = subject.parse('scalar DateTime')
|
345
|
+
|
346
|
+
type = document.definitions.first
|
347
|
+
assert_equal GraphQL::Language::Nodes::ScalarTypeDefinition, type.class
|
348
|
+
assert_equal 'DateTime', type.name
|
349
|
+
end
|
350
|
+
|
351
|
+
it "parses interface types" do
|
352
|
+
document = subject.parse('
|
353
|
+
interface Node {
|
354
|
+
id: ID!
|
355
|
+
}
|
356
|
+
')
|
357
|
+
|
358
|
+
type = document.definitions.first
|
359
|
+
assert_equal GraphQL::Language::Nodes::InterfaceTypeDefinition, type.class
|
360
|
+
assert_equal 'Node', type.name
|
361
|
+
assert_equal ['id'], type.fields.map(&:name)
|
362
|
+
assert_equal [], type.fields[0].arguments
|
363
|
+
assert_equal 'ID', type.fields[0].type.of_type.name
|
364
|
+
end
|
365
|
+
|
366
|
+
it "parses enum types" do
|
367
|
+
document = subject.parse('
|
368
|
+
enum DogCommand { SIT, DOWN, HEEL }
|
369
|
+
')
|
370
|
+
|
371
|
+
type = document.definitions.first
|
372
|
+
assert_equal GraphQL::Language::Nodes::EnumTypeDefinition, type.class
|
373
|
+
assert_equal 'DogCommand', type.name
|
374
|
+
assert_equal ['SIT', 'DOWN', 'HEEL'], type.values
|
375
|
+
end
|
376
|
+
|
377
|
+
it "parses input object types" do
|
378
|
+
document = subject.parse('
|
379
|
+
input EmptyMutationInput {
|
380
|
+
clientMutationId: String
|
381
|
+
}
|
382
|
+
')
|
383
|
+
|
384
|
+
type = document.definitions.first
|
385
|
+
assert_equal GraphQL::Language::Nodes::InputObjectTypeDefinition, type.class
|
386
|
+
assert_equal 'EmptyMutationInput', type.name
|
387
|
+
assert_equal ['clientMutationId'], type.fields.map(&:name)
|
388
|
+
assert_equal 'String', type.fields[0].type.name
|
389
|
+
assert_equal nil, type.fields[0].default_value
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
describe "errors" do
|
395
|
+
let(:query_string) {%| query doSomething { bogus { } |}
|
396
|
+
it "raises a parse error" do
|
397
|
+
err = assert_raises(GraphQL::ParseError) { document }
|
398
|
+
end
|
399
|
+
|
400
|
+
it "correctly identifies parse error location and content" do
|
401
|
+
e = assert_raises(GraphQL::ParseError) do
|
402
|
+
GraphQL.parse("
|
403
|
+
query getCoupons {
|
404
|
+
allCoupons: {data{id}}
|
405
|
+
}
|
406
|
+
")
|
407
|
+
end
|
408
|
+
assert_includes(e.message, '"{"')
|
409
|
+
assert_includes(e.message, "RCURLY")
|
410
|
+
assert_equal(3, e.line)
|
411
|
+
assert_equal(33, e.col)
|
412
|
+
end
|
413
|
+
|
414
|
+
it "handles unexpected ends" do
|
415
|
+
err = assert_raises(GraphQL::ParseError) { GraphQL.parse("{ ") }
|
416
|
+
assert_equal "Unexpected end of document", err.message
|
417
|
+
end
|
418
|
+
|
419
|
+
it "rejects unsupported characters" do
|
420
|
+
e = assert_raises(GraphQL::ParseError) do
|
421
|
+
GraphQL.parse("{ field; }")
|
422
|
+
end
|
423
|
+
|
424
|
+
assert_includes(e.message, "Parse error on \";\"")
|
425
|
+
end
|
426
|
+
|
427
|
+
it "rejects control characters" do
|
428
|
+
e = assert_raises(GraphQL::ParseError) do
|
429
|
+
GraphQL.parse("{ \afield }")
|
430
|
+
end
|
431
|
+
|
432
|
+
assert_includes(e.message, "Parse error on \"\\a\"")
|
433
|
+
end
|
434
|
+
|
435
|
+
it "rejects partial BOM" do
|
436
|
+
e = assert_raises(GraphQL::ParseError) do
|
437
|
+
GraphQL.parse("{ \xeffield }")
|
438
|
+
end
|
439
|
+
|
440
|
+
assert_includes(e.message, "Parse error on \"\\xEF\"")
|
441
|
+
end
|
442
|
+
|
443
|
+
it "rejects vertical tabs" do
|
444
|
+
e = assert_raises(GraphQL::ParseError) do
|
445
|
+
GraphQL.parse("{ \vfield }")
|
446
|
+
end
|
447
|
+
|
448
|
+
assert_includes(e.message, "Parse error on \"\\v\"")
|
449
|
+
end
|
450
|
+
|
451
|
+
it "rejects form feed" do
|
452
|
+
e = assert_raises(GraphQL::ParseError) do
|
453
|
+
GraphQL.parse("{ \ffield }")
|
454
|
+
end
|
455
|
+
|
456
|
+
assert_includes(e.message, "Parse error on \"\\f\"")
|
457
|
+
end
|
458
|
+
|
459
|
+
it "rejects no break space" do
|
460
|
+
e = assert_raises(GraphQL::ParseError) do
|
461
|
+
GraphQL.parse("{ \xa0field }")
|
462
|
+
end
|
463
|
+
|
464
|
+
assert_includes(e.message, "Parse error on \"\\xA0\"")
|
465
|
+
end
|
466
|
+
|
467
|
+
it "rejects unterminated strings" do
|
468
|
+
e = assert_raises(GraphQL::ParseError) do
|
469
|
+
GraphQL.parse("\"")
|
470
|
+
end
|
471
|
+
|
472
|
+
assert_includes(e.message, "Parse error on \"\\\"\"")
|
473
|
+
|
474
|
+
e = assert_raises(GraphQL::ParseError) do
|
475
|
+
GraphQL.parse("\"\n\"")
|
476
|
+
end
|
477
|
+
|
478
|
+
assert_includes(e.message, "Parse error on \"\\n\"")
|
479
|
+
end
|
480
|
+
|
481
|
+
it "rejects bad escape sequence in strings" do
|
482
|
+
e = assert_raises(GraphQL::ParseError) do
|
483
|
+
GraphQL.parse("{ field(arg:\"\\x\") }")
|
484
|
+
end
|
485
|
+
|
486
|
+
assert_includes(e.message, "Parse error on bad Unicode escape sequence")
|
487
|
+
end
|
488
|
+
|
489
|
+
it "rejects incomplete escape sequence in strings" do
|
490
|
+
e = assert_raises(GraphQL::ParseError) do
|
491
|
+
GraphQL.parse("{ field(arg:\"\\u1\") }")
|
492
|
+
end
|
493
|
+
|
494
|
+
assert_includes(e.message, "bad Unicode escape sequence")
|
495
|
+
end
|
496
|
+
|
497
|
+
it "rejects unicode escape with bad chars" do
|
498
|
+
e = assert_raises(GraphQL::ParseError) do
|
499
|
+
GraphQL.parse("{ field(arg:\"\\u0XX1\") }")
|
500
|
+
end
|
501
|
+
|
502
|
+
assert_includes(e.message, "bad Unicode escape sequence")
|
503
|
+
|
504
|
+
e = assert_raises(GraphQL::ParseError) do
|
505
|
+
GraphQL.parse("{ field(arg:\"\\uXXXX\") }")
|
506
|
+
end
|
507
|
+
|
508
|
+
assert_includes(e.message, "bad Unicode escape sequence")
|
509
|
+
|
510
|
+
|
511
|
+
e = assert_raises(GraphQL::ParseError) do
|
512
|
+
GraphQL.parse("{ field(arg:\"\\uFXXX\") }")
|
513
|
+
end
|
514
|
+
|
515
|
+
assert_includes(e.message, "bad Unicode escape sequence")
|
516
|
+
|
517
|
+
|
518
|
+
e = assert_raises(GraphQL::ParseError) do
|
519
|
+
GraphQL.parse("{ field(arg:\"\\uXXXF\") }")
|
520
|
+
end
|
521
|
+
|
522
|
+
assert_includes(e.message, "bad Unicode escape sequence")
|
523
|
+
end
|
524
|
+
|
525
|
+
it "rejects fragments named 'on'" do
|
526
|
+
e = assert_raises(GraphQL::ParseError) do
|
527
|
+
GraphQL.parse("fragment on on on { on }")
|
528
|
+
end
|
529
|
+
|
530
|
+
assert_includes(e.message, "Parse error on \"on\"")
|
531
|
+
end
|
532
|
+
|
533
|
+
it "rejects fragment spread of 'on'" do
|
534
|
+
e = assert_raises(GraphQL::ParseError) do
|
535
|
+
GraphQL.parse("{ ...on }")
|
536
|
+
end
|
537
|
+
|
538
|
+
assert_includes(e.message, "Parse error on \"}\"")
|
539
|
+
end
|
540
|
+
|
541
|
+
it "rejects null value" do
|
542
|
+
e = assert_raises(GraphQL::ParseError) do
|
543
|
+
GraphQL.parse("{ fieldWithNullableStringInput(input: null) }")
|
544
|
+
end
|
545
|
+
|
546
|
+
assert_includes(e.message, "Parse error on \"null\"")
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
|
551
|
+
describe "whitespace" do
|
552
|
+
describe "whitespace-only queries" do
|
553
|
+
let(:query_string) { " " }
|
554
|
+
it "doesn't blow up" do
|
555
|
+
assert_equal [], document.definitions
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
describe "empty string queries" do
|
560
|
+
let(:query_string) { "" }
|
561
|
+
it "doesn't blow up" do
|
562
|
+
assert_equal [], document.definitions
|
563
|
+
end
|
564
|
+
end
|
565
|
+
|
566
|
+
describe "using tabs as whitespace" do
|
567
|
+
let(:query_string) { "\t{\t\tid, \tname}"}
|
568
|
+
it "parses the query" do
|
569
|
+
assert_equal 1, document.definitions.length
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|
573
|
+
end
|
574
|
+
end
|
575
|
+
end
|
576
|
+
end
|
577
|
+
end
|