graphql 1.9.0.pre1 → 1.9.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (235) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +5 -1
  3. data/lib/graphql/analysis/ast/analyzer.rb +1 -1
  4. data/lib/graphql/analysis/ast/visitor.rb +6 -1
  5. data/lib/graphql/backwards_compatibility.rb +1 -1
  6. data/lib/graphql/dig.rb +19 -0
  7. data/lib/graphql/directive.rb +13 -1
  8. data/lib/graphql/directive/include_directive.rb +1 -7
  9. data/lib/graphql/directive/skip_directive.rb +1 -8
  10. data/lib/graphql/execution/interpreter.rb +23 -13
  11. data/lib/graphql/execution/interpreter/resolve.rb +56 -0
  12. data/lib/graphql/execution/interpreter/runtime.rb +174 -74
  13. data/lib/graphql/execution/lazy.rb +7 -1
  14. data/lib/graphql/execution/lookahead.rb +71 -6
  15. data/lib/graphql/execution_error.rb +1 -1
  16. data/lib/graphql/introspection/entry_points.rb +5 -1
  17. data/lib/graphql/introspection/type_type.rb +4 -4
  18. data/lib/graphql/language.rb +0 -1
  19. data/lib/graphql/language/block_string.rb +37 -0
  20. data/lib/graphql/language/document_from_schema_definition.rb +1 -1
  21. data/lib/graphql/language/lexer.rb +55 -36
  22. data/lib/graphql/language/lexer.rl +8 -3
  23. data/lib/graphql/language/nodes.rb +27 -4
  24. data/lib/graphql/language/parser.rb +55 -55
  25. data/lib/graphql/language/parser.y +11 -11
  26. data/lib/graphql/language/printer.rb +1 -1
  27. data/lib/graphql/language/visitor.rb +22 -13
  28. data/lib/graphql/literal_validation_error.rb +6 -0
  29. data/lib/graphql/query.rb +13 -0
  30. data/lib/graphql/query/arguments.rb +2 -1
  31. data/lib/graphql/query/context.rb +3 -10
  32. data/lib/graphql/query/executor.rb +1 -1
  33. data/lib/graphql/query/validation_pipeline.rb +1 -1
  34. data/lib/graphql/relay/connection_resolve.rb +1 -1
  35. data/lib/graphql/relay/relation_connection.rb +1 -1
  36. data/lib/graphql/schema.rb +81 -11
  37. data/lib/graphql/schema/argument.rb +1 -1
  38. data/lib/graphql/schema/build_from_definition.rb +2 -4
  39. data/lib/graphql/schema/directive.rb +103 -0
  40. data/lib/graphql/schema/directive/feature.rb +66 -0
  41. data/lib/graphql/schema/directive/include.rb +25 -0
  42. data/lib/graphql/schema/directive/skip.rb +25 -0
  43. data/lib/graphql/schema/directive/transform.rb +48 -0
  44. data/lib/graphql/schema/enum_value.rb +2 -2
  45. data/lib/graphql/schema/field.rb +63 -17
  46. data/lib/graphql/schema/input_object.rb +1 -0
  47. data/lib/graphql/schema/member/base_dsl_methods.rb +4 -2
  48. data/lib/graphql/schema/member/build_type.rb +33 -1
  49. data/lib/graphql/schema/member/has_fields.rb +8 -73
  50. data/lib/graphql/schema/relay_classic_mutation.rb +6 -1
  51. data/lib/graphql/schema/resolver.rb +1 -1
  52. data/lib/graphql/static_validation.rb +2 -1
  53. data/lib/graphql/static_validation/all_rules.rb +1 -0
  54. data/lib/graphql/static_validation/base_visitor.rb +25 -10
  55. data/lib/graphql/static_validation/definition_dependencies.rb +3 -3
  56. data/lib/graphql/static_validation/{message.rb → error.rb} +11 -11
  57. data/lib/graphql/static_validation/interpreter_visitor.rb +14 -0
  58. data/lib/graphql/static_validation/literal_validator.rb +54 -11
  59. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +34 -5
  60. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +31 -0
  61. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +2 -2
  62. data/lib/graphql/static_validation/rules/argument_names_are_unique_error.rb +30 -0
  63. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +7 -1
  64. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +35 -0
  65. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -1
  66. data/lib/graphql/static_validation/rules/directives_are_defined_error.rb +29 -0
  67. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +11 -2
  68. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations_error.rb +31 -0
  69. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +11 -2
  70. data/lib/graphql/static_validation/rules/fields_are_defined_on_type_error.rb +32 -0
  71. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -2
  72. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections_error.rb +31 -0
  73. data/lib/graphql/static_validation/rules/fields_will_merge.rb +24 -6
  74. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +32 -0
  75. data/lib/graphql/static_validation/rules/fragment_names_are_unique.rb +5 -1
  76. data/lib/graphql/static_validation/rules/fragment_names_are_unique_error.rb +29 -0
  77. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +8 -1
  78. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible_error.rb +35 -0
  79. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +5 -1
  80. data/lib/graphql/static_validation/rules/fragment_types_exist_error.rb +29 -0
  81. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +6 -1
  82. data/lib/graphql/static_validation/rules/fragments_are_finite_error.rb +29 -0
  83. data/lib/graphql/static_validation/rules/fragments_are_named.rb +4 -1
  84. data/lib/graphql/static_validation/rules/fragments_are_named_error.rb +26 -0
  85. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +5 -1
  86. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types_error.rb +30 -0
  87. data/lib/graphql/static_validation/rules/fragments_are_used.rb +13 -3
  88. data/lib/graphql/static_validation/rules/fragments_are_used_error.rb +29 -0
  89. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +4 -1
  90. data/lib/graphql/static_validation/rules/mutation_root_exists_error.rb +26 -0
  91. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +2 -2
  92. data/lib/graphql/static_validation/rules/no_definitions_are_present_error.rb +25 -0
  93. data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +9 -2
  94. data/lib/graphql/static_validation/rules/operation_names_are_valid_error.rb +28 -0
  95. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +7 -1
  96. data/lib/graphql/static_validation/rules/required_arguments_are_present_error.rb +35 -0
  97. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +47 -0
  98. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present_error.rb +35 -0
  99. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +4 -1
  100. data/lib/graphql/static_validation/rules/subscription_root_exists_error.rb +26 -0
  101. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +4 -3
  102. data/lib/graphql/static_validation/rules/unique_directives_per_location_error.rb +29 -0
  103. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +20 -6
  104. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed_error.rb +39 -0
  105. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +5 -1
  106. data/lib/graphql/static_validation/rules/variable_names_are_unique_error.rb +29 -0
  107. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +8 -1
  108. data/lib/graphql/static_validation/rules/variable_usages_are_allowed_error.rb +38 -0
  109. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -2
  110. data/lib/graphql/static_validation/rules/variables_are_input_types_error.rb +32 -0
  111. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +18 -2
  112. data/lib/graphql/static_validation/rules/variables_are_used_and_defined_error.rb +37 -0
  113. data/lib/graphql/static_validation/validator.rb +24 -14
  114. data/lib/graphql/tracing/new_relic_tracing.rb +2 -2
  115. data/lib/graphql/tracing/skylight_tracing.rb +2 -2
  116. data/lib/graphql/unauthorized_field_error.rb +23 -0
  117. data/lib/graphql/version.rb +1 -1
  118. data/spec/graphql/analysis/ast_spec.rb +40 -0
  119. data/spec/graphql/authorization_spec.rb +93 -20
  120. data/spec/graphql/base_type_spec.rb +3 -1
  121. data/spec/graphql/execution/interpreter_spec.rb +127 -4
  122. data/spec/graphql/execution/lazy_spec.rb +49 -0
  123. data/spec/graphql/execution/lookahead_spec.rb +113 -21
  124. data/spec/graphql/execution/multiplex_spec.rb +2 -1
  125. data/spec/graphql/introspection/type_type_spec.rb +1 -1
  126. data/spec/graphql/language/lexer_spec.rb +72 -3
  127. data/spec/graphql/language/printer_spec.rb +18 -6
  128. data/spec/graphql/query/arguments_spec.rb +21 -0
  129. data/spec/graphql/query/context_spec.rb +10 -0
  130. data/spec/graphql/schema/build_from_definition_spec.rb +144 -29
  131. data/spec/graphql/schema/directive/feature_spec.rb +81 -0
  132. data/spec/graphql/schema/directive/transform_spec.rb +39 -0
  133. data/spec/graphql/schema/enum_spec.rb +5 -3
  134. data/spec/graphql/schema/field_extension_spec.rb +3 -3
  135. data/spec/graphql/schema/field_spec.rb +19 -0
  136. data/spec/graphql/schema/input_object_spec.rb +81 -0
  137. data/spec/graphql/schema/member/build_type_spec.rb +46 -0
  138. data/spec/graphql/schema/member/scoped_spec.rb +3 -3
  139. data/spec/graphql/schema/printer_spec.rb +244 -96
  140. data/spec/graphql/schema/relay_classic_mutation_spec.rb +26 -0
  141. data/spec/graphql/schema/resolver_spec.rb +1 -1
  142. data/spec/graphql/schema/warden_spec.rb +35 -11
  143. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +212 -72
  144. data/spec/graphql/static_validation/rules/argument_names_are_unique_spec.rb +2 -2
  145. data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +72 -29
  146. data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +4 -2
  147. data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +4 -2
  148. data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +10 -5
  149. data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +10 -5
  150. data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +2 -1
  151. data/spec/graphql/static_validation/rules/fragment_names_are_unique_spec.rb +2 -1
  152. data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +6 -3
  153. data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +4 -2
  154. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -2
  155. data/spec/graphql/static_validation/rules/fragments_are_named_spec.rb +2 -1
  156. data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +6 -3
  157. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +22 -2
  158. data/spec/graphql/static_validation/rules/mutation_root_exists_spec.rb +2 -1
  159. data/spec/graphql/static_validation/rules/operation_names_are_valid_spec.rb +6 -3
  160. data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +13 -4
  161. data/spec/graphql/static_validation/rules/required_input_object_attributes_are_present_spec.rb +58 -0
  162. data/spec/graphql/static_validation/rules/subscription_root_exists_spec.rb +2 -1
  163. data/spec/graphql/static_validation/rules/unique_directives_per_location_spec.rb +14 -7
  164. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +14 -7
  165. data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +8 -4
  166. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +8 -4
  167. data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +6 -3
  168. data/spec/graphql/static_validation/validator_spec.rb +6 -4
  169. data/spec/graphql/tracing/new_relic_tracing_spec.rb +10 -0
  170. data/spec/graphql/tracing/skylight_tracing_spec.rb +10 -0
  171. data/spec/graphql/types/iso_8601_date_time_spec.rb +1 -2
  172. data/spec/integration/mongoid/star_trek/schema.rb +5 -5
  173. data/spec/integration/rails/graphql/relay/relation_connection_spec.rb +37 -8
  174. data/spec/integration/rails/graphql/schema_spec.rb +2 -2
  175. data/spec/integration/rails/spec_helper.rb +10 -0
  176. data/spec/integration/tmp/app/graphql/types/bird_type.rb +7 -0
  177. data/spec/integration/tmp/dummy/Gemfile +45 -0
  178. data/spec/integration/tmp/dummy/README.rdoc +28 -0
  179. data/spec/integration/tmp/dummy/Rakefile +6 -0
  180. data/spec/integration/tmp/dummy/app/assets/javascripts/application.js +16 -0
  181. data/spec/integration/tmp/dummy/app/assets/stylesheets/application.css +15 -0
  182. data/spec/integration/tmp/dummy/app/controllers/application_controller.rb +5 -0
  183. data/spec/integration/tmp/dummy/app/controllers/graphql_controller.rb +43 -0
  184. data/spec/integration/tmp/dummy/app/graphql/dummy_schema.rb +34 -0
  185. data/spec/integration/tmp/dummy/app/graphql/types/base_enum.rb +4 -0
  186. data/spec/integration/tmp/dummy/app/graphql/types/base_input_object.rb +4 -0
  187. data/spec/integration/tmp/dummy/app/graphql/types/base_interface.rb +5 -0
  188. data/spec/integration/tmp/dummy/app/graphql/types/base_object.rb +4 -0
  189. data/spec/integration/tmp/dummy/app/graphql/types/base_scalar.rb +4 -0
  190. data/spec/integration/tmp/dummy/app/graphql/types/base_union.rb +4 -0
  191. data/spec/integration/tmp/dummy/app/graphql/types/mutation_type.rb +10 -0
  192. data/spec/integration/tmp/dummy/app/graphql/types/query_type.rb +15 -0
  193. data/spec/integration/tmp/dummy/app/helpers/application_helper.rb +2 -0
  194. data/spec/integration/tmp/dummy/app/views/layouts/application.html.erb +14 -0
  195. data/spec/integration/tmp/dummy/bin/bundle +3 -0
  196. data/spec/integration/tmp/dummy/bin/rails +4 -0
  197. data/spec/integration/tmp/dummy/bin/rake +4 -0
  198. data/spec/integration/tmp/dummy/bin/setup +29 -0
  199. data/spec/integration/tmp/dummy/config.ru +4 -0
  200. data/spec/integration/tmp/dummy/config/application.rb +32 -0
  201. data/spec/integration/tmp/dummy/config/boot.rb +3 -0
  202. data/spec/integration/tmp/dummy/config/environment.rb +5 -0
  203. data/spec/integration/tmp/dummy/config/environments/development.rb +38 -0
  204. data/spec/integration/tmp/dummy/config/environments/production.rb +76 -0
  205. data/spec/integration/tmp/dummy/config/environments/test.rb +42 -0
  206. data/spec/integration/tmp/dummy/config/initializers/assets.rb +11 -0
  207. data/spec/integration/tmp/dummy/config/initializers/backtrace_silencers.rb +7 -0
  208. data/spec/integration/tmp/dummy/config/initializers/cookies_serializer.rb +3 -0
  209. data/spec/integration/tmp/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  210. data/spec/integration/tmp/dummy/config/initializers/inflections.rb +16 -0
  211. data/spec/integration/tmp/dummy/config/initializers/mime_types.rb +4 -0
  212. data/spec/integration/tmp/dummy/config/initializers/session_store.rb +3 -0
  213. data/spec/integration/tmp/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
  214. data/spec/integration/tmp/dummy/config/initializers/wrap_parameters.rb +9 -0
  215. data/spec/integration/tmp/dummy/config/locales/en.yml +23 -0
  216. data/spec/integration/tmp/dummy/config/routes.rb +61 -0
  217. data/spec/integration/tmp/dummy/config/secrets.yml +22 -0
  218. data/spec/integration/tmp/dummy/db/seeds.rb +7 -0
  219. data/spec/integration/tmp/dummy/public/404.html +67 -0
  220. data/spec/integration/tmp/dummy/public/422.html +67 -0
  221. data/spec/integration/tmp/dummy/public/500.html +66 -0
  222. data/spec/integration/tmp/dummy/public/favicon.ico +0 -0
  223. data/spec/integration/tmp/dummy/public/robots.txt +5 -0
  224. data/spec/support/dummy/schema.rb +2 -2
  225. data/spec/support/error_bubbling_helpers.rb +23 -0
  226. data/spec/support/jazz.rb +53 -6
  227. data/spec/support/lazy_helpers.rb +26 -8
  228. data/spec/support/new_relic.rb +3 -0
  229. data/spec/support/skylight.rb +3 -0
  230. data/spec/support/star_wars/schema.rb +13 -9
  231. data/spec/support/static_validation_helpers.rb +3 -1
  232. metadata +145 -22
  233. data/lib/graphql/language/comments.rb +0 -45
  234. data/spec/graphql/schema/member/has_fields_spec.rb +0 -132
  235. data/spec/integration/tmp/app/graphql/types/family_type.rb +0 -9
@@ -70,6 +70,55 @@ describe GraphQL::Execution::Lazy do
70
70
  assert_equal expected_data, res["data"]
71
71
  end
72
72
 
73
+ # This only works with the interpreter
74
+ if TESTING_INTERPRETER
75
+ [
76
+ [1, 2, LazyHelpers::MAGIC_NUMBER_WITH_LAZY_AUTHORIZED_HOOK],
77
+ [2, LazyHelpers::MAGIC_NUMBER_WITH_LAZY_AUTHORIZED_HOOK, 1],
78
+ [LazyHelpers::MAGIC_NUMBER_WITH_LAZY_AUTHORIZED_HOOK, 1, 2],
79
+ ].each do |ordered_values|
80
+ it "resolves each field at one depth before proceeding to the next depth (using #{ordered_values})" do
81
+ res = run_query <<-GRAPHQL, variables: { values: ordered_values }
82
+ query($values: [Int!]!) {
83
+ listSum(values: $values) {
84
+ nestedSum(value: 3) {
85
+ value
86
+ }
87
+ }
88
+ }
89
+ GRAPHQL
90
+
91
+ # Even though magic number `44`'s `.authorized?` hook returns a lazy value,
92
+ # these fields should be resolved together and return the same value.
93
+ assert_equal 56, res["data"]["listSum"][0]["nestedSum"]["value"]
94
+ assert_equal 56, res["data"]["listSum"][1]["nestedSum"]["value"]
95
+ assert_equal 56, res["data"]["listSum"][2]["nestedSum"]["value"]
96
+ end
97
+ end
98
+
99
+ it "Handles fields that return nil" do
100
+ values = [
101
+ LazyHelpers::MAGIC_NUMBER_THAT_RETURNS_NIL,
102
+ LazyHelpers::MAGIC_NUMBER_WITH_LAZY_AUTHORIZED_HOOK,
103
+ 1,
104
+ 2,
105
+ ]
106
+
107
+ res = run_query <<-GRAPHQL, variables: { values: values }
108
+ query($values: [Int!]!) {
109
+ listSum(values: $values) {
110
+ nullableNestedSum(value: 3) {
111
+ value
112
+ }
113
+ }
114
+ }
115
+ GRAPHQL
116
+
117
+ values = res["data"]["listSum"].map { |s| s && s["nullableNestedSum"]["value"] }
118
+ assert_equal [nil, 56, 56, 56], values
119
+ end
120
+ end
121
+
73
122
  it "propagates nulls to the root" do
74
123
  res = run_query %|
75
124
  {
@@ -48,12 +48,22 @@ describe GraphQL::Execution::Lookahead do
48
48
  end
49
49
  end
50
50
 
51
+ class LookaheadInstrumenter
52
+ def self.before_query(query)
53
+ query.context[:root_lookahead_names] = query.lookahead.selections.map(&:name)
54
+ end
55
+
56
+ def self.after_query(q)
57
+ end
58
+ end
59
+
51
60
  class Schema < GraphQL::Schema
52
61
  query(Query)
62
+ instrument :query, LookaheadInstrumenter
63
+ if TESTING_INTERPRETER
64
+ use GraphQL::Execution::Interpreter
65
+ end
53
66
  end
54
- # Cause everything to be loaded
55
- # TODO remove this
56
- Schema.graphql_definition
57
67
  end
58
68
 
59
69
  describe "looking ahead" do
@@ -80,9 +90,7 @@ describe GraphQL::Execution::Lookahead do
80
90
  end
81
91
 
82
92
  it "can detect fields on objects with symbol or string" do
83
- ast_node = document.definitions.first.selections.first
84
- field = LookaheadTest::Query.fields["findBirdSpecies"]
85
- lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], field: field)
93
+ lookahead = query.lookahead.selection("findBirdSpecies")
86
94
  assert_equal true, lookahead.selects?("similarSpecies")
87
95
  assert_equal true, lookahead.selects?(:similar_species)
88
96
  assert_equal false, lookahead.selects?("isWaterfowl")
@@ -90,15 +98,12 @@ describe GraphQL::Execution::Lookahead do
90
98
  end
91
99
 
92
100
  it "detects by name, not by alias" do
93
- ast_node = document.definitions.first
94
- lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], root_type: LookaheadTest::Query)
95
- assert_equal true, lookahead.selects?("__typename")
101
+ assert_equal true, query.lookahead.selects?("__typename")
96
102
  end
97
103
 
98
104
  describe "constraints by arguments" do
99
105
  let(:lookahead) do
100
- ast_node = document.definitions.first
101
- GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], root_type: LookaheadTest::Query)
106
+ query.lookahead
102
107
  end
103
108
 
104
109
  it "is true without constraints" do
@@ -138,9 +143,7 @@ describe GraphQL::Execution::Lookahead do
138
143
  end
139
144
 
140
145
  it "can do a chained lookahead" do
141
- ast_node = document.definitions.first
142
- lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], root_type: LookaheadTest::Query)
143
- next_lookahead = lookahead.selection(:find_bird_species, arguments: { by_name: "Cardinal" })
146
+ next_lookahead = query.lookahead.selection(:find_bird_species, arguments: { by_name: "Cardinal" })
144
147
  assert_equal true, next_lookahead.selected?
145
148
  nested_selection = next_lookahead.selection(:similar_species).selection(:is_waterfowl, arguments: {})
146
149
  assert_equal true, nested_selection.selected?
@@ -148,10 +151,8 @@ describe GraphQL::Execution::Lookahead do
148
151
  end
149
152
 
150
153
  it "can detect fields on lists with symbol or string" do
151
- ast_node = document.definitions.first
152
- lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], root_type: LookaheadTest::Query)
153
- assert_equal true, lookahead.selection(:find_bird_species).selection(:similar_species).selection(:is_waterfowl).selected?
154
- assert_equal true, lookahead.selection("findBirdSpecies").selection("similarSpecies").selection("isWaterfowl").selected?
154
+ assert_equal true, query.lookahead.selection(:find_bird_species).selection(:similar_species).selection(:is_waterfowl).selected?
155
+ assert_equal true, query.lookahead.selection("findBirdSpecies").selection("similarSpecies").selection("isWaterfowl").selected?
155
156
  end
156
157
 
157
158
  describe "merging branches and fragments" do
@@ -184,9 +185,7 @@ describe GraphQL::Execution::Lookahead do
184
185
  }
185
186
 
186
187
  it "finds selections using merging" do
187
- ast_node = document.definitions.first
188
- lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], root_type: LookaheadTest::Query)
189
- merged_lookahead = lookahead.selection(:find_bird_species).selection(:similar_species)
188
+ merged_lookahead = query.lookahead.selection(:find_bird_species).selection(:similar_species)
190
189
  assert merged_lookahead.selects?(:__typename)
191
190
  assert merged_lookahead.selects?(:is_waterfowl)
192
191
  assert merged_lookahead.selects?(:name)
@@ -213,6 +212,99 @@ describe GraphQL::Execution::Lookahead do
213
212
  res = LookaheadTest::Schema.execute(query_str, context: context)
214
213
  refute res.key?("errors")
215
214
  assert_equal 2, context[:lookahead_latin_name]
215
+ assert_equal [:find_bird_species], context[:root_lookahead_names]
216
+ end
217
+
218
+ it "works for invalid queries" do
219
+ context = {lookahead_latin_name: 0}
220
+ res = LookaheadTest::Schema.execute("{ doesNotExist }", context: context)
221
+ assert res.key?("errors")
222
+ assert_equal 0, context[:lookahead_latin_name]
223
+ end
224
+ end
225
+
226
+ describe '#selections' do
227
+ let(:document) {
228
+ GraphQL.parse <<-GRAPHQL
229
+ query {
230
+ findBirdSpecies(byName: "Laughing Gull") {
231
+ name
232
+ similarSpecies {
233
+ likesWater: isWaterfowl
234
+ }
235
+ }
236
+ }
237
+ GRAPHQL
238
+ }
239
+
240
+ def query(doc = document)
241
+ GraphQL::Query.new(LookaheadTest::Schema, document: doc)
242
+ end
243
+
244
+ it "provides a list of all selections" do
245
+ ast_node = document.definitions.first.selections.first
246
+ field = LookaheadTest::Query.fields["findBirdSpecies"]
247
+ lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], field: field)
248
+ assert_equal lookahead.selections.map(&:name), [:name, :similar_species]
249
+ end
250
+
251
+ it "filters outs selections which do not match arguments" do
252
+ ast_node = document.definitions.first
253
+ lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], root_type: LookaheadTest::Query)
254
+ arguments = { by_name: "Cardinal" }
255
+
256
+ assert_equal lookahead.selections(arguments: arguments).map(&:name), []
257
+ end
258
+
259
+ it "includes selections which match arguments" do
260
+ ast_node = document.definitions.first
261
+ lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], root_type: LookaheadTest::Query)
262
+ arguments = { by_name: "Laughing Gull" }
263
+
264
+ assert_equal lookahead.selections(arguments: arguments).map(&:name), [:find_bird_species]
265
+ end
266
+
267
+ it 'handles duplicate selections across fragments' do
268
+ doc = GraphQL.parse <<-GRAPHQL
269
+ query {
270
+ ... on Query {
271
+ ...MoreFields
272
+ }
273
+ }
274
+
275
+ fragment MoreFields on Query {
276
+ findBirdSpecies(byName: "Laughing Gull") {
277
+ name
278
+ }
279
+ findBirdSpecies(byName: "Laughing Gull") {
280
+ ...EvenMoreFields
281
+ }
282
+ }
283
+
284
+ fragment EvenMoreFields on BirdSpecies {
285
+ similarSpecies {
286
+ likesWater: isWaterfowl
287
+ }
288
+ }
289
+ GRAPHQL
290
+
291
+ lookahead = query(doc).lookahead
292
+
293
+ root_selections = lookahead.selections
294
+ assert_equal [:find_bird_species], root_selections.map(&:name), "Selections are merged"
295
+ assert_equal 2, root_selections.first.ast_nodes.size, "It represents both nodes"
296
+
297
+ assert_equal [:name, :similar_species], root_selections.first.selections.map(&:name), "Subselections are merged"
298
+ end
299
+
300
+ it "works for missing selections" do
301
+ ast_node = document.definitions.first.selections.first
302
+ field = LookaheadTest::Query.fields["findBirdSpecies"]
303
+ lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], field: field)
304
+ null_lookahead = lookahead.selection(:genus)
305
+ # This is an implementation detail, but I want to make sure the test is set up right
306
+ assert_instance_of GraphQL::Execution::Lookahead::NullLookahead, null_lookahead
307
+ assert_equal [], null_lookahead.selections
216
308
  end
217
309
  end
218
310
  end
@@ -94,7 +94,8 @@ describe GraphQL::Execution::Multiplex do
94
94
  "errors" => [{
95
95
  "message"=>"Field must have selections (field 'nullableNestedSum' returns LazySum but has no selections. Did you mean 'nullableNestedSum { ... }'?)",
96
96
  "locations"=>[{"line"=>1, "column"=>4}],
97
- "fields"=>["query", "validationError"]
97
+ "path"=>["query", "validationError"],
98
+ "extensions"=>{"code"=>"selectionMismatch", "nodeName"=>"field 'nullableNestedSum'", "typeName"=>"LazySum"}
98
99
  }]
99
100
  },
100
101
  ]
@@ -146,7 +146,7 @@ describe GraphQL::Introspection::TypeType do
146
146
  GRAPHQL
147
147
  type_result = res["data"]["__schema"]["types"].find { |t| t["name"] == "Faction" }
148
148
  field_result = type_result["fields"].find { |f| f["name"] == "bases" }
149
- all_arg_names = ["after", "before", "first", "last", "nameIncludes"]
149
+ all_arg_names = ["after", "before", "first", "last", "nameIncludes", "complexOrder"]
150
150
  returned_arg_names = field_result["args"].map { |a| a["name"] }
151
151
  assert_equal all_arg_names, returned_arg_names
152
152
  end
@@ -26,12 +26,32 @@ describe GraphQL::Language::Lexer do
26
26
  assert_equal tokens[0], tokens[1].prev_token
27
27
  end
28
28
 
29
+ it "allows escaped quotes in strings" do
30
+ tokens = subject.tokenize('"a\\"b""c"')
31
+ assert_equal 'a"b', tokens[0].value
32
+ assert_equal 'c', tokens[1].value
33
+ end
34
+
29
35
  describe "block strings" do
30
- let(:query_string) { %|{ a(b: """\nc\n d\n""")}|}
36
+ let(:query_string) { %|{ a(b: """\nc\n \\""" d\n""" """""e""""")}|}
31
37
 
32
38
  it "tokenizes them" do
33
- str_token = tokens[5]
34
- assert_equal "c\n d", str_token.value
39
+ assert_equal "c\n \"\"\" d", tokens[5].value
40
+ assert_equal "\"\"e\"\"", tokens[6].value
41
+ end
42
+
43
+ it "tokenizes 10 quote edge case correctly" do
44
+ tokens = subject.tokenize('""""""""""')
45
+ assert_equal '""', tokens[0].value # first 8 quotes are a valid block string """"""""
46
+ assert_equal '', tokens[1].value # last 2 quotes are a valid string ""
47
+ end
48
+
49
+ it "tokenizes with nested single quote strings correctly" do
50
+ tokens = subject.tokenize('"""{"x"}"""')
51
+ assert_equal '{"x"}', tokens[0].value
52
+
53
+ tokens = subject.tokenize('"""{"foo":"bar"}"""')
54
+ assert_equal '{"foo":"bar"}', tokens[0].value
35
55
  end
36
56
  end
37
57
 
@@ -62,5 +82,54 @@ describe GraphQL::Language::Lexer do
62
82
  rparen_token = tokens[6]
63
83
  assert_equal '(RPAREN ")" [1:10])', rparen_token.inspect
64
84
  end
85
+
86
+ it "counts block string line properly" do
87
+ str = <<-GRAPHQL
88
+ """
89
+ Here is a
90
+ multiline description
91
+ """
92
+ type Query {
93
+ a: B
94
+ }
95
+
96
+ "Here's another description"
97
+
98
+ type B {
99
+ a: B
100
+ }
101
+
102
+ """
103
+ And another
104
+ multiline description
105
+ """
106
+
107
+
108
+ type C {
109
+ a: B
110
+ }
111
+ GRAPHQL
112
+
113
+ tokens = subject.tokenize(str)
114
+
115
+ string_tok, type_keyword_tok, query_name_tok,
116
+ _curly, _ident, _colon, _ident, _curly,
117
+ string_tok_2, type_keyword_tok_2, b_name_tok,
118
+ _curly, _ident, _colon, _ident, _curly,
119
+ string_tok_3, type_keyword_tok_3, c_name_tok = tokens
120
+
121
+ assert_equal 1, string_tok.line
122
+ assert_equal 5, type_keyword_tok.line
123
+ assert_equal 5, query_name_tok.line
124
+
125
+ # Make sure it handles the empty spaces, too
126
+ assert_equal 9, string_tok_2.line
127
+ assert_equal 11, type_keyword_tok_2.line
128
+ assert_equal 11, b_name_tok.line
129
+
130
+ assert_equal 15, string_tok_3.line
131
+ assert_equal 21, type_keyword_tok_3.line
132
+ assert_equal 21, c_name_tok.line
133
+ end
65
134
  end
66
135
  end
@@ -112,7 +112,9 @@ describe GraphQL::Language::Printer do
112
112
  mutation: MutationType
113
113
  }
114
114
 
115
- # Union description
115
+ """
116
+ Union description
117
+ """
116
118
  union AnnotatedUnion @onUnion = A | B
117
119
 
118
120
  type Foo implements Bar & AnnotatedInterface {
@@ -125,7 +127,9 @@ describe GraphQL::Language::Printer do
125
127
  seven(argument: String = null): Type
126
128
  }
127
129
 
128
- # Scalar description
130
+ """
131
+ Scalar description
132
+ """
129
133
  scalar CustomScalar
130
134
 
131
135
  type AnnotatedObject implements Bar @onObject(arg: "value") {
@@ -137,9 +141,13 @@ describe GraphQL::Language::Printer do
137
141
  four(argument: String = "string"): String
138
142
  }
139
143
 
140
- # Enum description
144
+ """
145
+ Enum description
146
+ """
141
147
  enum Site {
142
- # Enum value description
148
+ """
149
+ Enum value description
150
+ """
143
151
  DESKTOP
144
152
  MOBILE
145
153
  }
@@ -150,7 +158,9 @@ describe GraphQL::Language::Printer do
150
158
 
151
159
  union Feed = Story | Article | Advert
152
160
 
153
- # Input description
161
+ """
162
+ Input description
163
+ """
154
164
  input InputType {
155
165
  key: String!
156
166
  answer: Int = 42
@@ -160,7 +170,9 @@ describe GraphQL::Language::Printer do
160
170
 
161
171
  scalar CustomScalar
162
172
 
163
- # Directive description
173
+ """
174
+ Directive description
175
+ """
164
176
  directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
165
177
 
166
178
  scalar AnnotatedScalar @onScalar
@@ -121,6 +121,27 @@ describe GraphQL::Query::Arguments do
121
121
  end
122
122
  end
123
123
 
124
+ describe "#dig" do
125
+ it "returns the value at that key" do
126
+ assert_equal 1, arguments.dig("a")
127
+ assert_equal 1, arguments.dig(:a)
128
+ assert arguments.dig("inputObject").is_a?(GraphQL::Query::Arguments)
129
+ end
130
+
131
+ it "works with nested keys" do
132
+ assert_equal 3, arguments.dig("inputObject", "d")
133
+ assert_equal 3, arguments.dig(:inputObject, :d)
134
+ assert_equal 3, arguments.dig("inputObject", :d)
135
+ assert_equal 3, arguments.dig(:inputObject, "d")
136
+ end
137
+
138
+ it "returns nil for missing keys" do
139
+ assert_nil arguments.dig("z")
140
+ assert_nil arguments.dig(7)
141
+ end
142
+ end
143
+
144
+
124
145
  describe "#key?" do
125
146
  let(:arg_values) { [] }
126
147
  let(:schema) {
@@ -235,6 +235,16 @@ TABLE
235
235
  end
236
236
  end
237
237
 
238
+ describe "splatting" do
239
+ let(:context) { GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema), values: {a: {b: 1}}, object: nil) }
240
+
241
+ let(:splat) { ->(**context) { context } }
242
+
243
+ it "runs successfully" do
244
+ assert_equal({a: { b: 1 }}, splat.call(context))
245
+ end
246
+ end
247
+
238
248
  describe "accessing context after the fact" do
239
249
  let(:query_string) { %|
240
250
  { pushContext }