graphql 1.9.0.pre1 → 1.9.0.pre2

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 (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 }