rails-graphql 0.2.1 → 1.0.0.beta

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 (297) hide show
  1. checksums.yaml +4 -4
  2. data/ext/console.rb +18 -0
  3. data/ext/extconf.h +3 -0
  4. data/ext/extconf.rb +1 -54
  5. data/ext/gql_parser.c +646 -0
  6. data/ext/shared.c +482 -0
  7. data/ext/shared.h +177 -0
  8. data/lib/gql_parser.so +0 -0
  9. data/lib/rails/graphql/adapters/mysql_adapter.rb +59 -0
  10. data/lib/rails/graphql/adapters/pg_adapter.rb +25 -22
  11. data/lib/rails/graphql/adapters/sqlite_adapter.rb +17 -14
  12. data/lib/rails/graphql/alternative/field_set.rb +36 -0
  13. data/lib/rails/graphql/alternative/mutation.rb +17 -0
  14. data/lib/rails/graphql/alternative/query.rb +93 -0
  15. data/lib/rails/graphql/alternative/subscription.rb +17 -0
  16. data/lib/rails/graphql/alternative.rb +20 -0
  17. data/lib/rails/graphql/argument.rb +21 -24
  18. data/lib/rails/graphql/callback.rb +24 -9
  19. data/lib/rails/graphql/collectors/hash_collector.rb +14 -6
  20. data/lib/rails/graphql/collectors/idented_collector.rb +10 -7
  21. data/lib/rails/graphql/collectors/json_collector.rb +22 -17
  22. data/lib/rails/graphql/collectors.rb +4 -4
  23. data/lib/rails/graphql/config.rb +130 -15
  24. data/lib/rails/graphql/directive/cached_directive.rb +33 -0
  25. data/lib/rails/graphql/directive/deprecated_directive.rb +10 -10
  26. data/lib/rails/graphql/directive/include_directive.rb +5 -4
  27. data/lib/rails/graphql/directive/skip_directive.rb +5 -4
  28. data/lib/rails/graphql/directive.rb +118 -63
  29. data/lib/rails/graphql/errors.rb +33 -4
  30. data/lib/rails/graphql/event.rb +16 -5
  31. data/lib/rails/graphql/field/authorized_field.rb +19 -3
  32. data/lib/rails/graphql/field/input_field.rb +11 -10
  33. data/lib/rails/graphql/field/mutation_field.rb +42 -7
  34. data/lib/rails/graphql/field/output_field.rb +102 -13
  35. data/lib/rails/graphql/field/proxied_field.rb +31 -22
  36. data/lib/rails/graphql/field/resolved_field.rb +26 -24
  37. data/lib/rails/graphql/field/scoped_config.rb +10 -4
  38. data/lib/rails/graphql/field/subscription_field.rb +140 -0
  39. data/lib/rails/graphql/field/typed_field.rb +43 -22
  40. data/lib/rails/graphql/field.rb +70 -56
  41. data/lib/rails/graphql/global_id.rb +85 -0
  42. data/lib/rails/graphql/helpers/attribute_delegator.rb +5 -5
  43. data/lib/rails/graphql/helpers/inherited_collection/array.rb +50 -0
  44. data/lib/rails/graphql/helpers/inherited_collection/base.rb +43 -0
  45. data/lib/rails/graphql/helpers/inherited_collection/hash.rb +87 -0
  46. data/lib/rails/graphql/helpers/inherited_collection.rb +25 -76
  47. data/lib/rails/graphql/helpers/instantiable.rb +15 -0
  48. data/lib/rails/graphql/helpers/leaf_from_ar.rb +7 -7
  49. data/lib/rails/graphql/helpers/registerable.rb +44 -62
  50. data/lib/rails/graphql/helpers/unregisterable.rb +16 -0
  51. data/lib/rails/graphql/helpers/with_arguments.rb +31 -27
  52. data/lib/rails/graphql/helpers/with_assignment.rb +6 -6
  53. data/lib/rails/graphql/helpers/with_callbacks.rb +25 -8
  54. data/lib/rails/graphql/helpers/with_description.rb +71 -0
  55. data/lib/rails/graphql/helpers/with_directives.rb +54 -30
  56. data/lib/rails/graphql/helpers/with_events.rb +21 -23
  57. data/lib/rails/graphql/helpers/with_fields.rb +76 -22
  58. data/lib/rails/graphql/helpers/with_global_id.rb +22 -0
  59. data/lib/rails/graphql/helpers/with_name.rb +43 -0
  60. data/lib/rails/graphql/helpers/with_namespace.rb +7 -4
  61. data/lib/rails/graphql/helpers/with_owner.rb +8 -7
  62. data/lib/rails/graphql/helpers/with_schema_fields.rb +137 -55
  63. data/lib/rails/graphql/helpers/with_validator.rb +9 -9
  64. data/lib/rails/graphql/helpers.rb +10 -3
  65. data/lib/rails/graphql/introspection.rb +43 -36
  66. data/lib/rails/graphql/railtie.rb +88 -33
  67. data/lib/rails/graphql/railties/base_generator.rb +3 -9
  68. data/lib/rails/graphql/railties/channel.rb +157 -0
  69. data/lib/rails/graphql/railties/controller.rb +62 -17
  70. data/lib/rails/graphql/railties/controller_runtime.rb +5 -5
  71. data/lib/rails/graphql/railties/log_subscriber.rb +81 -14
  72. data/lib/rails/graphql/request/arguments.rb +24 -49
  73. data/lib/rails/graphql/request/backtrace.rb +191 -0
  74. data/lib/rails/graphql/request/component/field.rb +86 -65
  75. data/lib/rails/graphql/request/component/fragment.rb +72 -24
  76. data/lib/rails/graphql/request/component/operation/subscription.rb +164 -4
  77. data/lib/rails/graphql/request/component/operation.rb +63 -31
  78. data/lib/rails/graphql/request/component/spread.rb +68 -25
  79. data/lib/rails/graphql/request/component/typename.rb +27 -12
  80. data/lib/rails/graphql/request/component.rb +75 -36
  81. data/lib/rails/graphql/request/context.rb +18 -8
  82. data/lib/rails/graphql/request/errors.rb +16 -6
  83. data/lib/rails/graphql/request/event.rb +19 -8
  84. data/lib/rails/graphql/request/helpers/directives.rb +68 -27
  85. data/lib/rails/graphql/request/helpers/selection_set.rb +51 -25
  86. data/lib/rails/graphql/request/helpers/value_writers.rb +18 -16
  87. data/lib/rails/graphql/request/prepared_data.rb +98 -0
  88. data/lib/rails/graphql/request/steps/authorizable.rb +24 -14
  89. data/lib/rails/graphql/request/steps/organizable.rb +110 -48
  90. data/lib/rails/graphql/request/steps/{prepareable.rb → preparable.rb} +20 -7
  91. data/lib/rails/graphql/request/steps/{resolveable.rb → resolvable.rb} +15 -6
  92. data/lib/rails/graphql/request/strategy/cached_strategy.rb +64 -0
  93. data/lib/rails/graphql/request/strategy/dynamic_instance.rb +6 -6
  94. data/lib/rails/graphql/request/strategy/multi_query_strategy.rb +6 -13
  95. data/lib/rails/graphql/request/strategy/sequenced_strategy.rb +9 -9
  96. data/lib/rails/graphql/request/strategy.rb +131 -75
  97. data/lib/rails/graphql/request/subscription.rb +80 -0
  98. data/lib/rails/graphql/request.rb +305 -86
  99. data/lib/rails/graphql/schema.rb +240 -48
  100. data/lib/rails/graphql/shortcuts.rb +22 -3
  101. data/lib/rails/graphql/source/active_record/builders.rb +49 -35
  102. data/lib/rails/graphql/source/active_record_source.rb +70 -54
  103. data/lib/rails/graphql/source/base.rb +111 -0
  104. data/lib/rails/graphql/source/builder.rb +128 -0
  105. data/lib/rails/graphql/source/scoped_arguments.rb +31 -19
  106. data/lib/rails/graphql/source.rb +89 -213
  107. data/lib/rails/graphql/subscription/provider/action_cable.rb +112 -0
  108. data/lib/rails/graphql/subscription/provider/base.rb +191 -0
  109. data/lib/rails/graphql/subscription/provider.rb +18 -0
  110. data/lib/rails/graphql/subscription/store/base.rb +145 -0
  111. data/lib/rails/graphql/subscription/store/memory.rb +127 -0
  112. data/lib/rails/graphql/subscription/store.rb +19 -0
  113. data/lib/rails/graphql/subscription.rb +17 -0
  114. data/lib/rails/graphql/to_gql.rb +29 -32
  115. data/lib/rails/graphql/type/enum/directive_location_enum.rb +11 -11
  116. data/lib/rails/graphql/type/enum/type_kind_enum.rb +3 -3
  117. data/lib/rails/graphql/type/enum.rb +34 -48
  118. data/lib/rails/graphql/type/input.rb +74 -23
  119. data/lib/rails/graphql/type/interface.rb +16 -26
  120. data/lib/rails/graphql/type/object/directive_object.rb +4 -4
  121. data/lib/rails/graphql/type/object/enum_value_object.rb +3 -3
  122. data/lib/rails/graphql/type/object/field_object.rb +24 -6
  123. data/lib/rails/graphql/type/object/input_value_object.rb +3 -3
  124. data/lib/rails/graphql/type/object/schema_object.rb +5 -8
  125. data/lib/rails/graphql/type/object/type_object.rb +29 -19
  126. data/lib/rails/graphql/type/object.rb +26 -23
  127. data/lib/rails/graphql/type/scalar/any_scalar.rb +30 -0
  128. data/lib/rails/graphql/type/scalar/bigint_scalar.rb +5 -5
  129. data/lib/rails/graphql/type/scalar/binary_scalar.rb +3 -3
  130. data/lib/rails/graphql/type/scalar/boolean_scalar.rb +8 -8
  131. data/lib/rails/graphql/type/scalar/date_scalar.rb +3 -3
  132. data/lib/rails/graphql/type/scalar/date_time_scalar.rb +3 -3
  133. data/lib/rails/graphql/type/scalar/decimal_scalar.rb +3 -3
  134. data/lib/rails/graphql/type/scalar/float_scalar.rb +5 -5
  135. data/lib/rails/graphql/type/scalar/id_scalar.rb +6 -5
  136. data/lib/rails/graphql/type/scalar/int_scalar.rb +6 -5
  137. data/lib/rails/graphql/type/scalar/json_scalar.rb +39 -0
  138. data/lib/rails/graphql/type/scalar/string_scalar.rb +18 -4
  139. data/lib/rails/graphql/type/scalar/time_scalar.rb +5 -5
  140. data/lib/rails/graphql/type/scalar.rb +25 -22
  141. data/lib/rails/graphql/type/union.rb +14 -16
  142. data/lib/rails/graphql/type.rb +34 -25
  143. data/lib/rails/graphql/type_map.rb +256 -164
  144. data/lib/rails/graphql/uri.rb +166 -0
  145. data/lib/rails/graphql/version.rb +15 -3
  146. data/lib/rails/graphql.rake +3 -0
  147. data/lib/rails/graphql.rb +85 -52
  148. data/lib/rails-graphql.rb +1 -1
  149. data/test/assets/en.yml +29 -0
  150. data/test/assets/introspection-mem.txt +1 -1
  151. data/test/assets/mem.gql +18 -45
  152. data/test/assets/mysql.gql +392 -0
  153. data/test/assets/sqlite.gql +21 -12
  154. data/test/assets/translate.gql +335 -0
  155. data/test/config.rb +18 -8
  156. data/test/graphql/schema_test.rb +12 -19
  157. data/test/graphql/source_test.rb +8 -75
  158. data/test/graphql/type/enum_test.rb +207 -203
  159. data/test/graphql/type/input_test.rb +14 -9
  160. data/test/graphql/type/interface_test.rb +4 -4
  161. data/test/graphql/type/scalar/any_scalar_test.rb +38 -0
  162. data/test/graphql/type/scalar/boolean_scalar_test.rb +6 -3
  163. data/test/graphql/type/scalar/json_scalar_test.rb +23 -0
  164. data/test/graphql/type_map_test.rb +51 -66
  165. data/test/graphql/type_test.rb +0 -19
  166. data/test/graphql_test.rb +1 -1
  167. data/test/integration/{authorization/authorization_test.rb → authorization_test.rb} +40 -14
  168. data/test/integration/config.rb +36 -3
  169. data/test/integration/customization_test.rb +39 -0
  170. data/test/integration/global_id_test.rb +99 -0
  171. data/test/integration/memory/star_wars_introspection_test.rb +24 -16
  172. data/test/integration/memory/star_wars_query_test.rb +54 -3
  173. data/test/integration/memory/star_wars_validation_test.rb +1 -1
  174. data/test/integration/mysql/star_wars_introspection_test.rb +25 -0
  175. data/test/integration/persisted_query_test.rb +87 -0
  176. data/test/integration/resolver_precedence_test.rb +154 -0
  177. data/test/integration/schemas/memory.rb +22 -7
  178. data/test/integration/schemas/mysql.rb +62 -0
  179. data/test/integration/schemas/sqlite.rb +21 -12
  180. data/test/integration/sqlite/star_wars_global_id_test.rb +83 -0
  181. data/test/integration/sqlite/star_wars_introspection_test.rb +10 -0
  182. data/test/integration/sqlite/star_wars_query_test.rb +14 -1
  183. data/test/integration/translate_test.rb +61 -0
  184. data/test/test_ext.rb +16 -13
  185. metadata +108 -157
  186. data/ext/depend +0 -3
  187. data/ext/graphqlparser/Ast.cpp +0 -346
  188. data/ext/graphqlparser/Ast.h +0 -1214
  189. data/ext/graphqlparser/AstNode.h +0 -36
  190. data/ext/graphqlparser/AstVisitor.h +0 -137
  191. data/ext/graphqlparser/GraphQLParser.cpp +0 -76
  192. data/ext/graphqlparser/GraphQLParser.h +0 -55
  193. data/ext/graphqlparser/JsonVisitor.cpp +0 -161
  194. data/ext/graphqlparser/JsonVisitor.cpp.inc +0 -456
  195. data/ext/graphqlparser/JsonVisitor.h +0 -121
  196. data/ext/graphqlparser/JsonVisitor.h.inc +0 -110
  197. data/ext/graphqlparser/VERSION +0 -1
  198. data/ext/graphqlparser/c/GraphQLAst.cpp +0 -324
  199. data/ext/graphqlparser/c/GraphQLAst.h +0 -180
  200. data/ext/graphqlparser/c/GraphQLAstForEachConcreteType.h +0 -44
  201. data/ext/graphqlparser/c/GraphQLAstNode.cpp +0 -25
  202. data/ext/graphqlparser/c/GraphQLAstNode.h +0 -33
  203. data/ext/graphqlparser/c/GraphQLAstToJSON.cpp +0 -21
  204. data/ext/graphqlparser/c/GraphQLAstToJSON.h +0 -24
  205. data/ext/graphqlparser/c/GraphQLAstVisitor.cpp +0 -55
  206. data/ext/graphqlparser/c/GraphQLAstVisitor.h +0 -53
  207. data/ext/graphqlparser/c/GraphQLParser.cpp +0 -35
  208. data/ext/graphqlparser/c/GraphQLParser.h +0 -54
  209. data/ext/graphqlparser/dump_json_ast.cpp +0 -48
  210. data/ext/graphqlparser/lexer.lpp +0 -324
  211. data/ext/graphqlparser/parser.ypp +0 -693
  212. data/ext/graphqlparser/parsergen/lexer.cpp +0 -2633
  213. data/ext/graphqlparser/parsergen/lexer.h +0 -528
  214. data/ext/graphqlparser/parsergen/location.hh +0 -189
  215. data/ext/graphqlparser/parsergen/parser.tab.cpp +0 -3300
  216. data/ext/graphqlparser/parsergen/parser.tab.hpp +0 -646
  217. data/ext/graphqlparser/parsergen/position.hh +0 -179
  218. data/ext/graphqlparser/parsergen/stack.hh +0 -156
  219. data/ext/graphqlparser/syntaxdefs.h +0 -19
  220. data/ext/libgraphqlparser/AstNode.h +0 -36
  221. data/ext/libgraphqlparser/CMakeLists.txt +0 -148
  222. data/ext/libgraphqlparser/CONTRIBUTING.md +0 -23
  223. data/ext/libgraphqlparser/GraphQLParser.cpp +0 -76
  224. data/ext/libgraphqlparser/GraphQLParser.h +0 -55
  225. data/ext/libgraphqlparser/JsonVisitor.cpp +0 -161
  226. data/ext/libgraphqlparser/JsonVisitor.h +0 -121
  227. data/ext/libgraphqlparser/LICENSE +0 -22
  228. data/ext/libgraphqlparser/README.clang-tidy +0 -7
  229. data/ext/libgraphqlparser/README.md +0 -84
  230. data/ext/libgraphqlparser/ast/ast.ast +0 -203
  231. data/ext/libgraphqlparser/ast/ast.py +0 -61
  232. data/ext/libgraphqlparser/ast/c.py +0 -100
  233. data/ext/libgraphqlparser/ast/c.pyc +0 -0
  234. data/ext/libgraphqlparser/ast/c_impl.py +0 -61
  235. data/ext/libgraphqlparser/ast/c_impl.pyc +0 -0
  236. data/ext/libgraphqlparser/ast/c_visitor_impl.py +0 -39
  237. data/ext/libgraphqlparser/ast/c_visitor_impl.pyc +0 -0
  238. data/ext/libgraphqlparser/ast/casing.py +0 -26
  239. data/ext/libgraphqlparser/ast/casing.pyc +0 -0
  240. data/ext/libgraphqlparser/ast/cxx.py +0 -197
  241. data/ext/libgraphqlparser/ast/cxx.pyc +0 -0
  242. data/ext/libgraphqlparser/ast/cxx_impl.py +0 -61
  243. data/ext/libgraphqlparser/ast/cxx_impl.pyc +0 -0
  244. data/ext/libgraphqlparser/ast/cxx_json_visitor_header.py +0 -42
  245. data/ext/libgraphqlparser/ast/cxx_json_visitor_header.pyc +0 -0
  246. data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.py +0 -80
  247. data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.pyc +0 -0
  248. data/ext/libgraphqlparser/ast/cxx_visitor.py +0 -64
  249. data/ext/libgraphqlparser/ast/cxx_visitor.pyc +0 -0
  250. data/ext/libgraphqlparser/ast/js.py +0 -65
  251. data/ext/libgraphqlparser/ast/license.py +0 -10
  252. data/ext/libgraphqlparser/ast/license.pyc +0 -0
  253. data/ext/libgraphqlparser/c/GraphQLAstNode.cpp +0 -25
  254. data/ext/libgraphqlparser/c/GraphQLAstNode.h +0 -33
  255. data/ext/libgraphqlparser/c/GraphQLAstToJSON.cpp +0 -21
  256. data/ext/libgraphqlparser/c/GraphQLAstToJSON.h +0 -24
  257. data/ext/libgraphqlparser/c/GraphQLAstVisitor.cpp +0 -55
  258. data/ext/libgraphqlparser/c/GraphQLAstVisitor.h +0 -53
  259. data/ext/libgraphqlparser/c/GraphQLParser.cpp +0 -35
  260. data/ext/libgraphqlparser/c/GraphQLParser.h +0 -54
  261. data/ext/libgraphqlparser/clang-tidy-all.sh +0 -3
  262. data/ext/libgraphqlparser/cmake/version.cmake +0 -16
  263. data/ext/libgraphqlparser/dump_json_ast.cpp +0 -48
  264. data/ext/libgraphqlparser/go/README.md +0 -20
  265. data/ext/libgraphqlparser/go/callbacks.go +0 -18
  266. data/ext/libgraphqlparser/go/gotest.go +0 -64
  267. data/ext/libgraphqlparser/lexer.lpp +0 -324
  268. data/ext/libgraphqlparser/libgraphqlparser.pc.in +0 -11
  269. data/ext/libgraphqlparser/parser.ypp +0 -693
  270. data/ext/libgraphqlparser/parsergen/lexer.cpp +0 -2633
  271. data/ext/libgraphqlparser/parsergen/lexer.h +0 -528
  272. data/ext/libgraphqlparser/parsergen/location.hh +0 -189
  273. data/ext/libgraphqlparser/parsergen/parser.tab.cpp +0 -3300
  274. data/ext/libgraphqlparser/parsergen/parser.tab.hpp +0 -646
  275. data/ext/libgraphqlparser/parsergen/position.hh +0 -179
  276. data/ext/libgraphqlparser/parsergen/stack.hh +0 -156
  277. data/ext/libgraphqlparser/python/CMakeLists.txt +0 -14
  278. data/ext/libgraphqlparser/python/README.md +0 -5
  279. data/ext/libgraphqlparser/python/example.py +0 -31
  280. data/ext/libgraphqlparser/syntaxdefs.h +0 -19
  281. data/ext/libgraphqlparser/test/BuildCAPI.c +0 -5
  282. data/ext/libgraphqlparser/test/CMakeLists.txt +0 -25
  283. data/ext/libgraphqlparser/test/JsonVisitorTests.cpp +0 -28
  284. data/ext/libgraphqlparser/test/ParserTests.cpp +0 -352
  285. data/ext/libgraphqlparser/test/kitchen-sink.graphql +0 -59
  286. data/ext/libgraphqlparser/test/kitchen-sink.json +0 -1
  287. data/ext/libgraphqlparser/test/schema-kitchen-sink.graphql +0 -78
  288. data/ext/libgraphqlparser/test/schema-kitchen-sink.json +0 -1
  289. data/ext/libgraphqlparser/test/valgrind.supp +0 -33
  290. data/ext/version.cpp +0 -21
  291. data/lib/graphqlparser.so +0 -0
  292. data/lib/rails/graphql/native/functions.rb +0 -38
  293. data/lib/rails/graphql/native/location.rb +0 -41
  294. data/lib/rails/graphql/native/pointers.rb +0 -23
  295. data/lib/rails/graphql/native/visitor.rb +0 -349
  296. data/lib/rails/graphql/native.rb +0 -56
  297. data/test/integration/schemas/authorization.rb +0 -12
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Rails # :nodoc:
4
- module GraphQL # :nodoc:
3
+ module Rails
4
+ module GraphQL
5
5
  # = GraphQL Request
6
6
  #
7
7
  # This class is responsible for processing a GraphQL response. It will
@@ -11,19 +11,38 @@ module Rails # :nodoc:
11
11
  #
12
12
  # ==== Options
13
13
  #
14
+ # * <tt>:args</tt> - The arguments of the request, same as variables
15
+ # * <tt>:as</tt> - The format of the output of the request, supports both
16
+ # +:hash+ and +:string+ (defaults to :string)
17
+ # * <tt>:context</tt> - The context of the request, which can be accessed in
18
+ # fields, resolvers and so as a way to customize the result
19
+ # * <tt>:controller</tt> - From which controller this operation is running
20
+ # from, which provides view-like access to helpers and methods through the
21
+ # request
14
22
  # * <tt>:namespace</tt> - Set what is the namespace used for the request
15
- # (defaults to :base).
23
+ # (defaults to :base)
24
+ # * <tt>:operation_name</tt> - The name of the operation as a sort of label,
25
+ # it can also be collected by the name of the single operation in the
26
+ # request
27
+ # * <tt>:schema</tt> - The schema on which the request should run on. It
28
+ # has higher precedence than the namespace
29
+ # * <tt>:variables</tt> - The variables of the request
16
30
  class Request
17
31
  extend ActiveSupport::Autoload
18
32
 
19
- RESPONSE_FORMATS = { string: :to_s, object: :to_h, hash: :to_h }.freeze
33
+ RESPONSE_FORMATS = {
34
+ string: :to_json,
35
+ object: :as_json,
36
+ json: :as_json,
37
+ hash: :as_json,
38
+ }.freeze
20
39
 
21
40
  eager_autoload do
22
41
  autoload_under :steps do
23
42
  autoload :Authorizable
24
43
  autoload :Organizable
25
- autoload :Prepareable
26
- autoload :Resolveable
44
+ autoload :Preparable
45
+ autoload :Resolvable
27
46
  end
28
47
 
29
48
  autoload_under :helpers do
@@ -33,15 +52,25 @@ module Rails # :nodoc:
33
52
  end
34
53
 
35
54
  autoload :Arguments
55
+ autoload :Backtrace
36
56
  autoload :Component
37
57
  autoload :Context
38
58
  autoload :Errors
39
59
  autoload :Event
60
+ autoload :PreparedData
40
61
  autoload :Strategy
62
+ autoload :Subscription
41
63
  end
42
64
 
43
- attr_reader :schema, :visitor, :operations, :fragments, :errors,
44
- :args, :response, :strategy, :stack
65
+ attr_reader :args, :origin, :errors, :fragments, :operations, :response, :schema,
66
+ :stack, :strategy, :document, :operation_name, :subscriptions
67
+
68
+ alias arguments args
69
+ alias controller origin
70
+ alias channel origin
71
+
72
+ delegate :action_name, to: :controller, allow_nil: true
73
+ delegate :all_listeners, :all_events, to: :schema
45
74
 
46
75
  class << self
47
76
  # Shortcut for initialize, set context, and execute
@@ -51,6 +80,16 @@ module Rails # :nodoc:
51
80
  result.execute(*args, **xargs)
52
81
  end
53
82
 
83
+ # Shortcut for initialize and compile
84
+ def compile(*args, schema: nil, namespace: :base, **xargs)
85
+ new(schema, namespace: namespace).compile(*args, **xargs)
86
+ end
87
+
88
+ # Shortcut for initialize and validate
89
+ def valid?(*args, schema: nil, namespace: :base, **xargs)
90
+ new(schema, namespace: namespace).valid?(*args, **xargs)
91
+ end
92
+
54
93
  # Allow accessing component-based objects through the request
55
94
  def const_defined?(name, *)
56
95
  Component.const_defined?(name) || super
@@ -60,32 +99,21 @@ module Rails # :nodoc:
60
99
  def const_missing(name)
61
100
  Component.const_defined?(name) ? Component.const_get(name) : super
62
101
  end
63
-
64
- def eager_load! # :nodoc:
65
- super
66
-
67
- Request::Component.eager_load!
68
- Request::Strategy.eager_load!
69
- end
70
102
  end
71
103
 
72
104
  # Forces the schema to be registered on type map before moving forward
73
105
  def initialize(schema = nil, namespace: :base)
74
106
  @namespace = schema&.namespace || namespace
75
107
  @schema = GraphQL::Schema.find!(@namespace)
108
+ @prepared_data = {}
76
109
  @extensions = {}
77
110
 
78
111
  ensure_schema!
79
112
  end
80
113
 
81
- # Cache all the schema listeners for this current request
82
- def all_listeners
83
- @all_listeners ||= schema.all_listeners
84
- end
85
-
86
- # Cache all the schema events for this current request
87
- def all_events
88
- @all_events ||= schema.all_events
114
+ # Check if any new subscription was added
115
+ def subscriptions?
116
+ defined?(@subscriptions) && @subscriptions.any?
89
117
  end
90
118
 
91
119
  # Get the context of the request
@@ -99,18 +127,84 @@ module Rails # :nodoc:
99
127
  end
100
128
 
101
129
  # Execute a given document with the given arguments
102
- def execute(document, as: :string, **xargs)
103
- reset!(**xargs)
130
+ def execute(document, **xargs)
131
+ output = xargs.delete(:as) || schema.config.default_response_format
132
+ cache = xargs.delete(:hash)
133
+ formatter = RESPONSE_FORMATS[output]
134
+
135
+ document, cache = nil, document if xargs.delete(:compiled)
136
+ prepared_data = xargs.delete(:data_for)
104
137
 
105
- to = RESPONSE_FORMATS[as]
106
- @response = initialize_response(as, to)
138
+ reset!(**xargs)
139
+ prepared_data&.each { |key, value| prepare_data_for(key, value) }
140
+ @response = initialize_response(output, formatter)
141
+ execute!(document, cache)
107
142
 
108
- execute!(document)
109
- response.public_send(to)
143
+ response.public_send(formatter)
144
+ rescue StaticResponse
145
+ response.public_send(formatter)
110
146
  end
111
147
 
112
148
  alias perform execute
113
149
 
150
+ # Compile a given document
151
+ def compile(document, compress: true)
152
+ reset!
153
+
154
+ log_execution(document, event: 'compile.graphql') do
155
+ @document = initialize_document(document)
156
+ run_document(with: :compile)
157
+
158
+ result = Marshal.dump(cache_dump)
159
+ result = Zlib.deflate(result) if compress
160
+
161
+ @log_extra[:total] = result.bytesize
162
+ result
163
+ end
164
+ end
165
+
166
+ # Check if the given document is valid by piggybacking on the compile
167
+ # process
168
+ def valid?(document)
169
+ reset!
170
+
171
+ log_execution(document, event: 'validate.graphql') do
172
+ @document = initialize_document(document)
173
+ run_document(with: :compile)
174
+ @log_extra[:result] = @errors.empty?
175
+ end
176
+ end
177
+
178
+ # This is used by cache and static responses to jump from executing to
179
+ # delivery a response right away
180
+ def force_response(response, error = StaticResponse)
181
+ @response = response
182
+ raise error
183
+ end
184
+
185
+ # Add a new prepared data from +value+ to the given +field+
186
+ def prepare_data_for(field, value, **options)
187
+ field = PreparedData.lookup(self, field)
188
+
189
+ if @prepared_data.key?(field)
190
+ @prepared_data[field].push(value)
191
+ else
192
+ @prepared_data[field] = PreparedData.new(field, value, **options)
193
+ end
194
+ end
195
+
196
+ # Recover the next prepared data for the given field
197
+ def prepared_data_for(field)
198
+ field = field.field if field.is_a?(Component::Field)
199
+ @prepared_data[field]
200
+ end
201
+
202
+ # Check if the given field has prepared data
203
+ def prepared_data_for?(field)
204
+ field = field.field if field.is_a?(Component::Field)
205
+ defined?(@prepared_data) && @prepared_data.key?(field)
206
+ end
207
+
114
208
  # Build a easy-to-access object representing the current information of
115
209
  # the execution to be used on +rescue_with_handler+
116
210
  def build_rescue_object(**extra)
@@ -125,7 +219,8 @@ module Rails # :nodoc:
125
219
 
126
220
  # Use schema handlers for exceptions caught during the execution process
127
221
  def rescue_with_handler(exception, **extra)
128
- schema.rescue_with_handler(exception, object: build_rescue_object(**extra))
222
+ ExtendedError.extend(exception, build_rescue_object(**extra))
223
+ schema.rescue_with_handler(exception)
129
224
  end
130
225
 
131
226
  # Add the given +exception+ to the errors using the +node+ location
@@ -136,17 +231,28 @@ module Rails # :nodoc:
136
231
 
137
232
  # A little helper to report an error on a given node
138
233
  def report_node_error(message, node, **xargs)
234
+ xargs[:locations] ||= location_of(node) unless xargs.key?(:line)
235
+ report_error(message, **xargs)
236
+ end
237
+
238
+ # Get the location object of a given node
239
+ def location_of(node)
139
240
  node = node.instance_variable_get(:@node) if node.is_a?(Request::Component)
140
- location = GraphQL::Native.get_location(node)
241
+ return unless node.is_a?(GQLParser::Token)
141
242
 
142
- xargs[:locations] ||= location.to_errors unless xargs.key?(:line)
143
- report_error(message, **xargs)
243
+ [
244
+ { 'line' => node.begin_line, 'column' => node.begin_column },
245
+ { 'line' => node.end_line, 'column' => node.end_column },
246
+ ]
144
247
  end
145
248
 
146
249
  # The final helper that facilitates how errors are reported
147
250
  def report_error(message, **xargs)
148
251
  xargs[:path] ||= stack_to_path
149
252
  errors.add(message, **xargs)
253
+
254
+ # Return nil for easier usage
255
+ nil
150
256
  end
151
257
 
152
258
  # Add the given +object+ into the execution +stack+ and execute the given
@@ -154,8 +260,6 @@ module Rails # :nodoc:
154
260
  def stacked(object, &block)
155
261
  stack.unshift(object)
156
262
  block.call
157
- rescue => exception
158
- rescue_with_handler(exception) || raise
159
263
  ensure
160
264
  stack.shift
161
265
  end
@@ -183,12 +287,12 @@ module Rails # :nodoc:
183
287
  obj
184
288
  end
185
289
 
186
- # Get and cache a sanitized version of the arguments
187
- def sanitized_arguments
188
- return {} unless args.each_pair.any?
189
- cache(:sanitized_arguments) do
190
- args.to_h.transform_keys { |key| key.to_s.camelize(:lower) }
191
- end
290
+ # This allocates a new object which is aware of extensions
291
+ def build_from_cache(klass)
292
+ ext_module = extensions[klass]
293
+ obj = klass.allocate
294
+ obj.extend(ext_module) if ext_module
295
+ obj
192
296
  end
193
297
 
194
298
  # A shared way to cache information across the execution of an request
@@ -196,59 +300,136 @@ module Rails # :nodoc:
196
300
  @cache[key] ||= (init_value || block&.call || {})
197
301
  end
198
302
 
303
+ # A better way to ensure that nil values in a hash cache won't be
304
+ # reinitialized
305
+ def nested_cache(key, sub_key)
306
+ (source = cache(key)).key?(sub_key) ? source[sub_key] : source[sub_key] = yield
307
+ end
308
+
309
+ # Show if the current cached operation is still valid
310
+ def valid_cache?
311
+ defined?(@valid_cache) && @valid_cache
312
+ end
313
+
314
+ # Write the request into the cache so it can run again faster
315
+ def write_cache_request(hash, data = cache_dump)
316
+ schema.write_on_cache(hash, Marshal.dump(data))
317
+ end
318
+
319
+ # Read the request from the cache to run it faster
320
+ def read_cache_request(data = @document)
321
+ begin
322
+ data = Zlib.inflate(data) if data[0] == 'x'
323
+ data = Marshal.load(data)
324
+ rescue Zlib::BufError, ArgumentError
325
+ raise ::ArgumentError, +'Unable to recover the cached request.'
326
+ end
327
+
328
+ cache_load(data)
329
+ end
330
+
331
+ # Build the object that represent the request in the cache format
332
+ def cache_dump
333
+ {
334
+ strategy: @strategy.cache_dump,
335
+ operation_name: @operation_name,
336
+ type_map_version: schema.version,
337
+ document: @document,
338
+ errors: @errors.cache_dump,
339
+ operations: @operations.transform_values(&:cache_dump),
340
+ fragments: @fragments&.transform_values { |f| f.try(:cache_dump) }&.compact,
341
+ }
342
+ end
343
+
344
+ # Read the request from the cache to run it faster
345
+ def cache_load(data)
346
+ version = data[:type_map_version]
347
+ @document = data[:document]
348
+ @operation_name = data[:operation_name]
349
+ resolve_from_cache = (version == schema.version)
350
+
351
+ # Run the document from scratch if TypeMap has changed
352
+ return run_document unless resolve_from_cache
353
+ @valid_cache = true unless defined?(@valid_cache)
354
+
355
+ # Run the document as a cached operation
356
+ errors.cache_load(data[:errors])
357
+ @strategy = build(data[:strategy][:class], self)
358
+ @strategy.trigger_event(:request)
359
+ @strategy.cache_load(data)
360
+ @strategy.resolve!
361
+ end
362
+
199
363
  private
200
364
 
201
365
  attr_reader :extensions
202
366
 
203
367
  # Reset principal variables and set the given +args+
204
- def reset!(args: nil, variables: {}, operation_name: nil)
205
- @args = build_ostruct(args || variables).freeze
206
- @errors = Request::Errors.new(self)
207
- @visitor = GraphQL::Native::Visitor.new
368
+ def reset!(args: nil, variables: {}, operation_name: nil, origin: nil)
369
+ @arg_names = {}
370
+
371
+ @args = (args || variables || {}).transform_keys do |key|
372
+ key.to_s.camelize(:lower).tap do |sanitized_key|
373
+ @arg_names[sanitized_key] = key
374
+ end
375
+ end
376
+
377
+ @args = build_ostruct(@args).freeze
378
+ @errors = Request::Errors.new(self)
208
379
  @operation_name = operation_name
380
+ @origin = origin
209
381
 
210
382
  @stack = [schema]
211
383
  @cache = {}
384
+ @log_extra = {}
212
385
  @fragments = {}
213
386
  @operations = {}
387
+ @subscriptions = {}
388
+ @used_variables = Set.new
389
+
390
+ @strategy = nil
391
+ schema.validate
214
392
  end
215
393
 
216
394
  # This executes the whole process capturing any exceptions and handling
217
395
  # them as defined by the schema
218
- def execute!(document)
219
- log_execution(document) do
220
- @document = GraphQL::Native.parse(document)
221
- collect_definitions!
222
-
223
- @strategy = find_strategy!
224
- @strategy.trigger_event(:request)
225
- @strategy.resolve!
396
+ def execute!(document, cache = nil)
397
+ log_execution(document, cache) do
398
+ @document = initialize_document(document, cache)
399
+ @document.is_a?(String) ? read_cache_request : run_document
226
400
  end
227
- rescue ParseError => err
228
- parts = err.message.match(/\A(\d+)\.(\d+)(?:-\d+)?: (.*)\z/)
229
- errors.add(parts[3], line: parts[1], col: parts[2])
230
401
  ensure
402
+ report_unused_variables
403
+ write_cache_request(cache) if cache.present? && !valid_cache?
404
+
231
405
  @cache.clear
232
- @fragments.clear
406
+ @strategy&.clear
407
+ @fragments&.clear
233
408
  @operations.clear
409
+ @prepared_data.clear
234
410
 
235
- @visitor.terminate
236
- @visitor = nil
237
-
238
- GraphQL::Native.free_node(@document) if defined?(@document)
239
411
  @response.try(:append_errors, errors)
240
412
  end
241
413
 
242
- # Use the visitor to collect the operations and fragments
414
+ # Prepare the definitions, find the strategy and resolve
415
+ def run_document(with: :resolve!)
416
+ return if @document.nil?
417
+
418
+ collect_definitions!
419
+
420
+ @strategy ||= find_strategy!
421
+ @strategy.trigger_event(:request)
422
+ @strategy.public_send(with)
423
+ end
424
+
425
+ # Organize the list of definitions from the document
243
426
  def collect_definitions!
244
- visitor.collect_definitions(@document) do |kind, node, data|
245
- case kind
246
- when :operation
247
- operations[data[:name]] = Component::Operation.build(self, node, data)
248
- when :fragment
249
- fragments[data[:name]] = build(Component::Fragment, self, node, data)
250
- end
251
- end
427
+ @operations = @document[0]&.index_by { |node| node[1] }
428
+ @fragments = @document[1]&.index_by { |node| node[0] }
429
+
430
+ raise ::ArgumentError, (+<<~MSG).squish if operations.blank?
431
+ The document does not contains operations.
432
+ MSG
252
433
  end
253
434
 
254
435
  # Find the best strategy to resolve the request
@@ -284,38 +465,66 @@ module Rails # :nodoc:
284
465
  end
285
466
 
286
467
  # Log the execution of a GraphQL document
287
- def log_execution(document)
288
- ActiveSupport::Notifications.instrument('request.graphql') do |payload|
289
- yield.tap { log_payload(document, payload) }
468
+ def log_execution(document, hash = nil, event: 'request.graphql')
469
+ return yield if event.nil?
470
+
471
+ data = { document: document, hash: hash }
472
+ ActiveSupport::Notifications.instrument(event, **data) do |payload|
473
+ yield.tap { log_payload(payload) }
290
474
  end
291
475
  end
292
476
 
293
477
  # Build the payload to be sent to the log
294
- def log_payload(document, data)
478
+ def log_payload(data)
295
479
  name = @operation_name.presence
296
480
  name ||= operations.keys.first if operations.size.eql?(1)
297
- map_variables = args.to_h if args.each_pair.any?
481
+ map_variables = args.to_h.transform_keys do |key|
482
+ @arg_names[key.to_s]
483
+ end
298
484
 
485
+ data.merge!(@log_extra)
299
486
  data.merge!(
300
487
  name: name,
301
488
  cached: false,
302
- document: document,
303
- variables: map_variables,
489
+ variables: map_variables.presence,
304
490
  )
305
491
  end
306
492
 
307
- # Initialize the class that responsible for storaging the response
493
+ # When document is empty and the hash has been provided, then
494
+ def initialize_document(document, cache = nil)
495
+ if document.present?
496
+ ::GQLParser.parse_execution(document)
497
+ elsif cache.nil?
498
+ raise ::ArgumentError, +'Unable to execute an empty document.'
499
+ elsif schema.cached?(cache)
500
+ schema.read_from_cache(cache)
501
+ else
502
+ @valid_cache = true
503
+ cache
504
+ end
505
+ rescue ::GQLParser::ParserError => err
506
+ parts = err.message.match(/\A(Parser error: .*) at \[(\d+), (\d+)\]\z/m)
507
+ errors.add(parts[1], line: parts[2].to_i, col: parts[3].to_i)
508
+ nil
509
+ end
510
+
511
+ # Initialize the class that responsible for storing the response
308
512
  def initialize_response(as_format, to)
309
- raise ::ArgumentError, <<~MSG.squish if to.nil?
310
- The given format #{as_format.inspect} is not a valid reponse format.
513
+ raise ::ArgumentError, (+<<~MSG).squish if to.nil?
514
+ The given format #{as_format.inspect} is not a valid response format.
311
515
  MSG
312
516
 
313
- klass = schema.config.enable_string_collector \
314
- ? Collectors::JsonCollector \
315
- : Collectors::HashCollector
517
+ klass =
518
+ if schema.config.enable_string_collector && as_format == :string
519
+ Collectors::JsonCollector
520
+ elsif RESPONSE_FORMATS.key?(as_format)
521
+ Collectors::HashCollector
522
+ else
523
+ as_format
524
+ end
316
525
 
317
526
  obj = klass.new(self)
318
- raise ::ArgumentError, <<~MSG.squish unless obj.respond_to?(to)
527
+ raise ::ArgumentError, (+<<~MSG).squish unless obj.respond_to?(to)
319
528
  Unable to use "#{klass.name}" as response collector since it does
320
529
  not implement a #{to.inspect} method.
321
530
  MSG
@@ -326,7 +535,7 @@ module Rails # :nodoc:
326
535
  # Little helper to build an +OpenStruct+ ensure the given +value+ is a
327
536
  # +Hash+. It can also +transform_keys+ with the given block
328
537
  def build_ostruct(value, &block)
329
- raise ::ArgumentError, <<~MSG.squish unless value.is_a?(Hash)
538
+ raise ::ArgumentError, (+<<~MSG).squish unless value.is_a?(::Hash)
330
539
  The "#{value.class.name}" is not a valid hash.
331
540
  MSG
332
541
 
@@ -337,11 +546,21 @@ module Rails # :nodoc:
337
546
  # Make sure that a schema was assigned by find the corresponding one for
338
547
  # the namespace of the request
339
548
  def ensure_schema!
340
- raise ::ArgumentError, <<~MSG.squish if schema.nil?
549
+ raise ::ArgumentError, (+<<~MSG).squish if schema.nil?
341
550
  Unable to perform a request under the #{@namespace.inspect} namespace,
342
551
  because there are no schema assigned to it.
343
552
  MSG
344
553
  end
554
+
555
+ # Check all the operations and report any provided variable that was not
556
+ # used
557
+ def report_unused_variables
558
+ (@arg_names.keys - @used_variables.to_a).each do |key|
559
+ errors.add((+<<~MSG).squish)
560
+ Variable $#{@arg_names[key]} was provided but not used.
561
+ MSG
562
+ end
563
+ end
345
564
  end
346
565
  end
347
566
  end