graphql 1.0.0 → 1.1.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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +10 -0
  3. data/lib/graphql/base_type.rb +8 -5
  4. data/lib/graphql/compatibility.rb +3 -0
  5. data/lib/graphql/compatibility/execution_specification.rb +414 -0
  6. data/lib/graphql/compatibility/query_parser_specification.rb +117 -0
  7. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +81 -0
  8. data/lib/graphql/compatibility/query_parser_specification/query_assertions.rb +78 -0
  9. data/lib/graphql/compatibility/schema_parser_specification.rb +239 -0
  10. data/lib/graphql/define/instance_definable.rb +53 -21
  11. data/lib/graphql/directive.rb +1 -1
  12. data/lib/graphql/enum_type.rb +31 -8
  13. data/lib/graphql/execution/directive_checks.rb +0 -6
  14. data/lib/graphql/input_object_type.rb +6 -4
  15. data/lib/graphql/introspection/arguments_field.rb +3 -1
  16. data/lib/graphql/introspection/enum_values_field.rb +10 -5
  17. data/lib/graphql/introspection/fields_field.rb +1 -1
  18. data/lib/graphql/introspection/input_fields_field.rb +2 -2
  19. data/lib/graphql/introspection/interfaces_field.rb +7 -1
  20. data/lib/graphql/introspection/possible_types_field.rb +1 -1
  21. data/lib/graphql/introspection/schema_type.rb +1 -1
  22. data/lib/graphql/introspection/type_by_name_field.rb +4 -2
  23. data/lib/graphql/introspection/type_type.rb +7 -6
  24. data/lib/graphql/language/lexer.rl +0 -4
  25. data/lib/graphql/language/parser.rb +1 -1
  26. data/lib/graphql/language/parser.y +1 -1
  27. data/lib/graphql/list_type.rb +3 -4
  28. data/lib/graphql/non_null_type.rb +4 -8
  29. data/lib/graphql/object_type.rb +5 -3
  30. data/lib/graphql/query.rb +48 -12
  31. data/lib/graphql/query/context.rb +7 -1
  32. data/lib/graphql/query/serial_execution/execution_context.rb +8 -3
  33. data/lib/graphql/query/serial_execution/field_resolution.rb +8 -5
  34. data/lib/graphql/query/serial_execution/operation_resolution.rb +2 -2
  35. data/lib/graphql/query/serial_execution/selection_resolution.rb +4 -21
  36. data/lib/graphql/query/serial_execution/value_resolution.rb +59 -99
  37. data/lib/graphql/query/variables.rb +7 -2
  38. data/lib/graphql/scalar_type.rb +1 -1
  39. data/lib/graphql/schema.rb +49 -18
  40. data/lib/graphql/schema/build_from_definition.rb +248 -0
  41. data/lib/graphql/schema/instrumented_field_map.rb +23 -0
  42. data/lib/graphql/schema/loader.rb +4 -11
  43. data/lib/graphql/schema/possible_types.rb +4 -2
  44. data/lib/graphql/schema/printer.rb +1 -1
  45. data/lib/graphql/schema/type_expression.rb +4 -4
  46. data/lib/graphql/schema/type_map.rb +1 -1
  47. data/lib/graphql/schema/validation.rb +4 -0
  48. data/lib/graphql/schema/warden.rb +114 -0
  49. data/lib/graphql/static_validation/literal_validator.rb +10 -7
  50. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -3
  51. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  52. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
  53. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -14
  54. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  55. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  56. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +3 -4
  57. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +2 -1
  58. data/lib/graphql/static_validation/validation_context.rb +7 -1
  59. data/lib/graphql/union_type.rb +6 -3
  60. data/lib/graphql/unresolved_type_error.rb +1 -2
  61. data/lib/graphql/version.rb +1 -1
  62. data/readme.md +1 -5
  63. data/spec/graphql/compatibility/execution_specification_spec.rb +3 -0
  64. data/spec/graphql/compatibility/query_parser_specification_spec.rb +5 -0
  65. data/spec/graphql/compatibility/schema_parser_specification_spec.rb +5 -0
  66. data/spec/graphql/define/instance_definable_spec.rb +20 -0
  67. data/spec/graphql/directive_spec.rb +11 -0
  68. data/spec/graphql/enum_type_spec.rb +20 -1
  69. data/spec/graphql/input_object_type_spec.rb +9 -9
  70. data/spec/graphql/introspection/directive_type_spec.rb +4 -4
  71. data/spec/graphql/introspection/input_value_type_spec.rb +6 -6
  72. data/spec/graphql/introspection/type_type_spec.rb +28 -26
  73. data/spec/graphql/language/parser_spec.rb +27 -17
  74. data/spec/graphql/list_type_spec.rb +2 -2
  75. data/spec/graphql/query/variables_spec.rb +1 -0
  76. data/spec/graphql/scalar_type_spec.rb +3 -3
  77. data/spec/graphql/schema/build_from_definition_spec.rb +693 -0
  78. data/spec/graphql/schema/type_expression_spec.rb +3 -3
  79. data/spec/graphql/schema/validation_spec.rb +7 -3
  80. data/spec/graphql/schema/warden_spec.rb +510 -0
  81. data/spec/graphql/schema_spec.rb +129 -0
  82. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +1 -1
  83. data/spec/graphql/static_validation/type_stack_spec.rb +3 -3
  84. data/spec/spec_helper.rb +27 -1
  85. data/spec/support/dairy_app.rb +8 -5
  86. metadata +21 -3
  87. data/lib/graphql/language/parser_tests.rb +0 -809
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 69c8b3d44df121369cc6c809f94584316ae60580
4
- data.tar.gz: cd20f49dd30f703c25fd3bb336b33de610d5885e
3
+ metadata.gz: 1d1d8b93486b17d67f6714c1ad867c5f5eb403a4
4
+ data.tar.gz: 68dc546c4124fd8fb3752c911b63be9f9753ffc2
5
5
  SHA512:
6
- metadata.gz: 52474d43d5e821f1d594214bb583fd8c9042e084afbf8222b389de22e4ed091c3a1d37ae5efdb4473d5fd4a3b6733d2b4b731a430d28e398632987ed68e964cf
7
- data.tar.gz: 63a46420973ab26b529b80e1867d9a809ddbab828d4462e43eb0aafb0ede0417d58a9f308d9c9c3c5f5dadab2f0f265b2fb6effade39510adb45ee53109080bd
6
+ metadata.gz: eb42f273418ba3eda88ca784f3af4a20b0a57223b02adbf64736953446312e87ece3392f2c7b3d696ebbd66cecc8a38809ff6ff79bb623942dd6bced33c4f5bc
7
+ data.tar.gz: 54669eb238917fedceba3df22ac927461a0a427b1d259a3277e6be1ffef420ada7ee1fc5abd63f605a4d5397e88a23d1d8ea73f95ebfd9cbaa97554b09b05089
@@ -27,6 +27,15 @@ module GraphQL
27
27
  def self.parse_with_racc(string)
28
28
  GraphQL::Language::Parser.parse(string)
29
29
  end
30
+
31
+ # @return [Array<GraphQL::Language::Token>]
32
+ def self.scan(query_string)
33
+ scan_with_ragel(query_string)
34
+ end
35
+
36
+ def self.scan_with_ragel(query_string)
37
+ GraphQL::Language::Lexer.tokenize(query_string)
38
+ end
30
39
  end
31
40
 
32
41
  # Order matters for these:
@@ -73,3 +82,4 @@ require "graphql/static_validation"
73
82
  require "graphql/version"
74
83
  require "graphql/relay"
75
84
  require "graphql/execution"
85
+ require "graphql/compatibility"
@@ -61,13 +61,16 @@ module GraphQL
61
61
 
62
62
  alias :inspect :to_s
63
63
 
64
- def valid_input?(value)
65
- validate_input(value).valid?
64
+ def valid_input?(value, warden)
65
+ validate_input(value, warden).valid?
66
66
  end
67
67
 
68
- def validate_input(value)
69
- return GraphQL::Query::InputValidationResult.new if value.nil?
70
- validate_non_null_input(value)
68
+ def validate_input(value, warden)
69
+ if value.nil?
70
+ GraphQL::Query::InputValidationResult.new
71
+ else
72
+ validate_non_null_input(value, warden)
73
+ end
71
74
  end
72
75
 
73
76
  def coerce_input(value)
@@ -0,0 +1,3 @@
1
+ require "graphql/compatibility/execution_specification"
2
+ require "graphql/compatibility/query_parser_specification"
3
+ require "graphql/compatibility/schema_parser_specification"
@@ -0,0 +1,414 @@
1
+ module GraphQL
2
+ module Compatibility
3
+ # Test an execution strategy. This spec is not meant as a development aid.
4
+ # Rather, when the strategy _works_, run it here to see if it has any differences
5
+ # from the built-in strategy.
6
+ #
7
+ # - Custom scalar input / output
8
+ # - Null propagation
9
+ # - Query-level masking
10
+ # - Directive support
11
+ # - Typecasting
12
+ # - Error handling (raise / return GraphQL::ExecutionError)
13
+ # - Provides Irep & AST node to resolve fn
14
+ #
15
+ # Some things are explicitly _not_ tested here, because they're handled
16
+ # by other parts of the system:
17
+ #
18
+ # - Schema definition (including types and fields)
19
+ # - Parsing & parse errors
20
+ # - AST -> IRep transformation (eg, fragment merging)
21
+ # - Query validation and analysis
22
+ # - Relay features
23
+ #
24
+ module ExecutionSpecification
25
+ DATA = {
26
+ "1001" => OpenStruct.new({
27
+ name: "Fannie Lou Hamer",
28
+ birthdate: Time.new(1917, 10, 6),
29
+ organization_ids: [],
30
+ }),
31
+ "1002" => OpenStruct.new({
32
+ name: "John Lewis",
33
+ birthdate: Time.new(1940, 2, 21),
34
+ organization_ids: ["2001"],
35
+ }),
36
+ "1003" => OpenStruct.new({
37
+ name: "Diane Nash",
38
+ birthdate: Time.new(1938, 5, 15),
39
+ organization_ids: ["2001", "2002"],
40
+ }),
41
+ "1004" => OpenStruct.new({
42
+ name: "Ralph Abernathy",
43
+ birthdate: Time.new(1926, 3, 11),
44
+ organization_ids: ["2002"],
45
+ }),
46
+ "2001" => OpenStruct.new({
47
+ name: "SNCC",
48
+ leader_id: nil, # fail on purpose
49
+ }),
50
+ "2002" => OpenStruct.new({
51
+ name: "SCLC",
52
+ leader_id: "1004",
53
+ }),
54
+ }
55
+
56
+ # Make a minitest suite for this execution strategy, making sure it
57
+ # fulfills all the requirements of this library.
58
+ # @param execution_strategy [<#new, #execute>] An execution strategy class
59
+ # @return [Class<Minitest::Test>] A test suite for this execution strategy
60
+ def self.build_suite(execution_strategy)
61
+ Class.new(Minitest::Test) do
62
+ def self.build_schema(execution_strategy)
63
+ organization_type = nil
64
+
65
+ timestamp_type = GraphQL::ScalarType.define do
66
+ name "Timestamp"
67
+ coerce_input ->(value) { Time.at(value.to_i) }
68
+ coerce_result ->(value) { value.to_i }
69
+ end
70
+
71
+ named_entity_interface_type = GraphQL::InterfaceType.define do
72
+ name "NamedEntity"
73
+ field :name, !types.String
74
+ end
75
+
76
+ person_type = GraphQL::ObjectType.define do
77
+ name "Person"
78
+ interfaces [named_entity_interface_type]
79
+ field :name, !types.String
80
+ field :birthdate, timestamp_type
81
+ field :age, types.Int do
82
+ argument :on, !timestamp_type
83
+ resolve ->(obj, args, ctx) {
84
+ if obj.birthdate.nil?
85
+ nil
86
+ else
87
+ age_on = args[:on]
88
+ age_years = age_on.year - obj.birthdate.year
89
+ this_year_birthday = Time.new(age_on.year, obj.birthdate.month, obj.birthdate.day)
90
+ if this_year_birthday > age_on
91
+ age_years -= 1
92
+ end
93
+ end
94
+ age_years
95
+ }
96
+ end
97
+ field :organizations, types[organization_type] do
98
+ resolve ->(obj, args, ctx) {
99
+ obj.organization_ids.map { |id| DATA[id] }
100
+ }
101
+ end
102
+ field :first_organization, !organization_type do
103
+ resolve ->(obj, args, ctx) {
104
+ DATA[obj.organization_ids.first]
105
+ }
106
+ end
107
+ end
108
+
109
+ organization_type = GraphQL::ObjectType.define do
110
+ name "Organization"
111
+ interfaces [named_entity_interface_type]
112
+ field :name, !types.String
113
+ field :leader, !person_type do
114
+ resolve ->(obj, args, ctx) {
115
+ DATA[obj.leader_id] || (ctx[:return_error] ? ExecutionError.new("Error on Nullable") : nil)
116
+ }
117
+ end
118
+ field :returnedError, types.String do
119
+ resolve ->(o, a, c) {
120
+ GraphQL::ExecutionError.new("This error was returned")
121
+ }
122
+ end
123
+ field :raisedError, types.String do
124
+ resolve ->(o, a, c) {
125
+ raise GraphQL::ExecutionError.new("This error was raised")
126
+ }
127
+ end
128
+
129
+ field :nodePresence, !types[!types.Boolean] do
130
+ resolve ->(o, a, ctx) {
131
+ [
132
+ ctx.irep_node.is_a?(GraphQL::InternalRepresentation::Node),
133
+ ctx.ast_node.is_a?(GraphQL::Language::Nodes::AbstractNode),
134
+ false, # just testing
135
+ ]
136
+ }
137
+ end
138
+ end
139
+
140
+ node_union_type = GraphQL::UnionType.define do
141
+ name "Node"
142
+ possible_types [person_type, organization_type]
143
+ end
144
+
145
+ query_type = GraphQL::ObjectType.define do
146
+ name "Query"
147
+ field :node, node_union_type do
148
+ argument :id, !types.ID
149
+ resolve ->(obj, args, ctx) {
150
+ obj[args[:id]]
151
+ }
152
+ end
153
+
154
+ field :organization, !organization_type do
155
+ argument :id, !types.ID
156
+ resolve ->(obj, args, ctx) {
157
+ args[:id].start_with?("2") && obj[args[:id]]
158
+ }
159
+ end
160
+
161
+ field :organizations, types[organization_type] do
162
+ resolve ->(obj, args, ctx) {
163
+ [obj["2001"], obj["2002"]]
164
+ }
165
+ end
166
+ end
167
+
168
+ GraphQL::Schema.define do
169
+ query_execution_strategy execution_strategy
170
+ query query_type
171
+
172
+ resolve_type ->(obj, ctx) {
173
+ obj.respond_to?(:birthdate) ? person_type : organization_type
174
+ }
175
+ end
176
+ end
177
+
178
+ @@schema = build_schema(execution_strategy)
179
+
180
+ def execute_query(query_string, **kwargs)
181
+ kwargs[:root_value] = DATA
182
+ @@schema.execute(query_string, **kwargs)
183
+ end
184
+
185
+ def test_it_fetches_data
186
+ query_string = %|
187
+ query getData($nodeId: ID = "1001") {
188
+ flh: node(id: $nodeId) {
189
+ __typename
190
+ ... on Person {
191
+ name @include(if: true)
192
+ skippedName: name @skip(if: true)
193
+ birthdate
194
+ age(on: 1477660133)
195
+ }
196
+
197
+ ... on NamedEntity {
198
+ ne_tn: __typename
199
+ ne_n: name
200
+ }
201
+
202
+ ... on Organization {
203
+ org_n: name
204
+ }
205
+ }
206
+ }
207
+ |
208
+ res = execute_query(query_string)
209
+
210
+ assert_equal nil, res["errors"], "It doesn't have an errors key"
211
+
212
+ flh = res["data"]["flh"]
213
+ assert_equal "Fannie Lou Hamer", flh["name"], "It returns values"
214
+ assert_equal Time.new(1917, 10, 6).to_i, flh["birthdate"], "It returns custom scalars"
215
+ assert_equal 99, flh["age"], "It runs resolve functions"
216
+ assert_equal "Person", flh["__typename"], "It serves __typename"
217
+ assert_equal "Person", flh["ne_tn"], "It serves __typename on interfaces"
218
+ assert_equal "Fannie Lou Hamer", flh["ne_n"], "It serves interface fields"
219
+ assert_equal false, flh.key?("skippedName"), "It obeys @skip"
220
+ assert_equal false, flh.key?("org_n"), "It doesn't apply other type fields"
221
+ end
222
+
223
+ def test_it_propagates_nulls_to_field
224
+ query_string = %|
225
+ query getOrg($id: ID = "2001"){
226
+ failure: node(id: $id) {
227
+ ... on Organization {
228
+ name
229
+ leader { name }
230
+ }
231
+ }
232
+ success: node(id: $id) {
233
+ ... on Organization {
234
+ name
235
+ }
236
+ }
237
+ }
238
+ |
239
+ res = execute_query(query_string)
240
+
241
+ failure = res["data"]["failure"]
242
+ success = res["data"]["success"]
243
+
244
+ assert_equal nil, failure, "It propagates nulls to the next nullable field"
245
+ assert_equal({"name" => "SNCC"}, success, "It serves the same object if no invalid null is encountered")
246
+ assert_equal 1, res["errors"].length , "It returns an error for the invalid null"
247
+ end
248
+
249
+ def test_it_propages_nulls_to_operation
250
+ query_string = %|
251
+ {
252
+ foundOrg: organization(id: "2001") {
253
+ name
254
+ }
255
+ organization(id: "2999") {
256
+ name
257
+ }
258
+ }
259
+ |
260
+
261
+ res = execute_query(query_string)
262
+ assert_equal nil, res["data"]
263
+ assert_equal 1, res["errors"].length
264
+ end
265
+
266
+ def test_it_exposes_raised_and_returned_user_execution_errors
267
+ query_string = %|
268
+ {
269
+ organization(id: "2001") {
270
+ name
271
+ returnedError
272
+ raisedError
273
+ }
274
+ organizations {
275
+ returnedError
276
+ raisedError
277
+ }
278
+ }
279
+ |
280
+
281
+ res = execute_query(query_string)
282
+
283
+ assert_equal "SNCC", res["data"]["organization"]["name"], "It runs the rest of the query"
284
+
285
+ expected_errors = [
286
+ {
287
+ "message"=>"This error was returned",
288
+ "locations"=>[{"line"=>5, "column"=>19}],
289
+ "path"=>["organization", "returnedError"]
290
+ },
291
+ {
292
+ "message"=>"This error was raised",
293
+ "locations"=>[{"line"=>6, "column"=>19}],
294
+ "path"=>["organization", "raisedError"]
295
+ },
296
+ {
297
+ "message"=>"This error was raised",
298
+ "locations"=>[{"line"=>10, "column"=>19}],
299
+ "path"=>["organizations", 0, "raisedError"]
300
+ },
301
+ {
302
+ "message"=>"This error was raised",
303
+ "locations"=>[{"line"=>10, "column"=>19}],
304
+ "path"=>["organizations", 1, "raisedError"]
305
+ },
306
+ {
307
+ "message"=>"This error was returned",
308
+ "locations"=>[{"line"=>9, "column"=>19}],
309
+ "path"=>["organizations", 0, "returnedError"]
310
+ },
311
+ {
312
+ "message"=>"This error was returned",
313
+ "locations"=>[{"line"=>9, "column"=>19}],
314
+ "path"=>["organizations", 1, "returnedError"]
315
+ },
316
+ ]
317
+
318
+ expected_errors.each do |expected_err|
319
+ assert_includes res["errors"], expected_err
320
+ end
321
+ end
322
+
323
+ def test_it_applies_masking
324
+ no_org = ->(member) { member.name == "Organization" }
325
+ query_string = %|
326
+ {
327
+ node(id: "2001") {
328
+ __typename
329
+ }
330
+ }|
331
+
332
+ assert_raises(GraphQL::UnresolvedTypeError) {
333
+ execute_query(query_string, except: no_org)
334
+ }
335
+
336
+ query_string = %|
337
+ {
338
+ organization(id: "2001") { name }
339
+ }|
340
+
341
+ res = execute_query(query_string, except: no_org)
342
+
343
+ assert_equal nil, res["data"]
344
+ assert_equal 1, res["errors"].length
345
+
346
+ query_string = %|
347
+ {
348
+ __type(name: "Organization") { name }
349
+ }|
350
+
351
+ res = execute_query(query_string, except: no_org)
352
+
353
+ assert_equal nil, res["data"]["__type"]
354
+ assert_equal nil, res["errors"]
355
+ end
356
+
357
+ def test_it_provides_nodes_to_resolve
358
+ query_string = %|
359
+ {
360
+ organization(id: "2001") {
361
+ name
362
+ nodePresence
363
+ }
364
+ }|
365
+
366
+ res = execute_query(query_string)
367
+ assert_equal "SNCC", res["data"]["organization"]["name"]
368
+ assert_equal [true, true, false], res["data"]["organization"]["nodePresence"]
369
+ end
370
+
371
+ def test_it_runs_the_introspection_query
372
+ execute_query(GraphQL::Introspection::INTROSPECTION_QUERY)
373
+ end
374
+
375
+ def test_it_propagates_deeply_nested_nulls
376
+ query_string = %|
377
+ {
378
+ node(id: "1001") {
379
+ ... on Person {
380
+ name
381
+ first_organization {
382
+ leader {
383
+ name
384
+ }
385
+ }
386
+ }
387
+ }
388
+ }
389
+ |
390
+ res = execute_query(query_string)
391
+ assert_equal nil, res["data"]["node"]
392
+ assert_equal 1, res["errors"].length
393
+ end
394
+
395
+ def test_it_doesnt_add_errors_for_invalid_nulls_from_execution_errors
396
+ query_string = %|
397
+ query getOrg($id: ID = "2001"){
398
+ failure: node(id: $id) {
399
+ ... on Organization {
400
+ name
401
+ leader { name }
402
+ }
403
+ }
404
+ }
405
+ |
406
+ res = execute_query(query_string, context: {return_error: true})
407
+ error_messages = res["errors"].map { |e| e["message"] }
408
+ assert_equal ["Error on Nullable"], error_messages
409
+ end
410
+ end
411
+ end
412
+ end
413
+ end
414
+ end