rails-graphql 0.2.1 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (315) 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 +631 -0
  6. data/ext/gql_parser.h +21 -0
  7. data/ext/shared.c +477 -0
  8. data/ext/shared.h +177 -0
  9. data/lib/generators/graphql/channel_generator.rb +27 -0
  10. data/lib/generators/graphql/controller_generator.rb +9 -4
  11. data/lib/generators/graphql/install_generator.rb +49 -0
  12. data/lib/generators/graphql/schema_generator.rb +9 -4
  13. data/lib/generators/graphql/templates/channel.erb +7 -0
  14. data/lib/generators/graphql/templates/config.rb +97 -0
  15. data/lib/generators/graphql/templates/controller.erb +2 -0
  16. data/lib/generators/graphql/templates/schema.erb +5 -3
  17. data/lib/gql_parser.so +0 -0
  18. data/lib/rails/graphql/adapters/mysql_adapter.rb +59 -0
  19. data/lib/rails/graphql/adapters/pg_adapter.rb +25 -22
  20. data/lib/rails/graphql/adapters/sqlite_adapter.rb +17 -14
  21. data/lib/rails/graphql/alternative/field_set.rb +48 -0
  22. data/lib/rails/graphql/alternative/mutation.rb +17 -0
  23. data/lib/rails/graphql/alternative/query.rb +98 -0
  24. data/lib/rails/graphql/alternative/subscription.rb +18 -0
  25. data/lib/rails/graphql/alternative.rb +20 -0
  26. data/lib/rails/graphql/argument.rb +25 -26
  27. data/lib/rails/graphql/callback.rb +30 -14
  28. data/lib/rails/graphql/collectors/hash_collector.rb +26 -7
  29. data/lib/rails/graphql/collectors/idented_collector.rb +10 -7
  30. data/lib/rails/graphql/collectors/json_collector.rb +43 -17
  31. data/lib/rails/graphql/collectors.rb +4 -4
  32. data/lib/rails/graphql/config.rb +154 -23
  33. data/lib/rails/graphql/directive/cached_directive.rb +33 -0
  34. data/lib/rails/graphql/directive/deprecated_directive.rb +10 -10
  35. data/lib/rails/graphql/directive/include_directive.rb +4 -4
  36. data/lib/rails/graphql/directive/skip_directive.rb +4 -4
  37. data/lib/rails/graphql/directive/specified_by_directive.rb +24 -0
  38. data/lib/rails/graphql/directive.rb +134 -73
  39. data/lib/rails/graphql/errors.rb +33 -4
  40. data/lib/rails/graphql/event.rb +21 -9
  41. data/lib/rails/graphql/field/authorized_field.rb +17 -6
  42. data/lib/rails/graphql/field/input_field.rb +8 -12
  43. data/lib/rails/graphql/field/mutation_field.rb +43 -9
  44. data/lib/rails/graphql/field/output_field.rb +112 -12
  45. data/lib/rails/graphql/field/proxied_field.rb +35 -26
  46. data/lib/rails/graphql/field/resolved_field.rb +27 -25
  47. data/lib/rails/graphql/field/scoped_config.rb +10 -4
  48. data/lib/rails/graphql/field/subscription_field.rb +123 -0
  49. data/lib/rails/graphql/field/typed_field.rb +69 -24
  50. data/lib/rails/graphql/field.rb +89 -74
  51. data/lib/rails/graphql/global_id.rb +89 -0
  52. data/lib/rails/graphql/helpers/attribute_delegator.rb +5 -5
  53. data/lib/rails/graphql/helpers/inherited_collection/array.rb +51 -0
  54. data/lib/rails/graphql/helpers/inherited_collection/base.rb +45 -0
  55. data/lib/rails/graphql/helpers/inherited_collection/hash.rb +88 -0
  56. data/lib/rails/graphql/helpers/inherited_collection.rb +25 -76
  57. data/lib/rails/graphql/helpers/instantiable.rb +15 -0
  58. data/lib/rails/graphql/helpers/leaf_from_ar.rb +7 -7
  59. data/lib/rails/graphql/helpers/registerable.rb +44 -62
  60. data/lib/rails/graphql/helpers/unregisterable.rb +16 -0
  61. data/lib/rails/graphql/helpers/with_arguments.rb +33 -28
  62. data/lib/rails/graphql/helpers/with_assignment.rb +6 -6
  63. data/lib/rails/graphql/helpers/with_callbacks.rb +28 -11
  64. data/lib/rails/graphql/helpers/with_description.rb +73 -0
  65. data/lib/rails/graphql/helpers/with_directives.rb +58 -30
  66. data/lib/rails/graphql/helpers/with_events.rb +22 -23
  67. data/lib/rails/graphql/helpers/with_fields.rb +86 -26
  68. data/lib/rails/graphql/helpers/with_global_id.rb +22 -0
  69. data/lib/rails/graphql/helpers/with_name.rb +44 -0
  70. data/lib/rails/graphql/helpers/with_namespace.rb +7 -4
  71. data/lib/rails/graphql/helpers/with_owner.rb +8 -7
  72. data/lib/rails/graphql/helpers/with_schema_fields.rb +162 -56
  73. data/lib/rails/graphql/helpers/with_validator.rb +9 -9
  74. data/lib/rails/graphql/helpers.rb +10 -3
  75. data/lib/rails/graphql/introspection.rb +43 -36
  76. data/lib/rails/graphql/railtie.rb +89 -33
  77. data/lib/rails/graphql/railties/app/base_channel.rb +10 -0
  78. data/lib/rails/graphql/railties/app/base_controller.rb +12 -0
  79. data/lib/rails/graphql/railties/app/views/_cable.js.erb +56 -0
  80. data/lib/rails/graphql/railties/app/views/_fetch.js.erb +20 -0
  81. data/lib/rails/graphql/railties/app/views/graphiql.html.erb +101 -0
  82. data/lib/rails/graphql/railties/base_generator.rb +5 -17
  83. data/lib/rails/graphql/railties/channel.rb +157 -0
  84. data/lib/rails/graphql/railties/controller.rb +91 -25
  85. data/lib/rails/graphql/railties/controller_runtime.rb +5 -5
  86. data/lib/rails/graphql/railties/log_subscriber.rb +81 -14
  87. data/lib/rails/graphql/request/arguments.rb +26 -50
  88. data/lib/rails/graphql/request/backtrace.rb +212 -0
  89. data/lib/rails/graphql/request/component/field.rb +98 -70
  90. data/lib/rails/graphql/request/component/fragment.rb +80 -26
  91. data/lib/rails/graphql/request/component/operation/subscription.rb +162 -4
  92. data/lib/rails/graphql/request/component/operation.rb +73 -34
  93. data/lib/rails/graphql/request/component/spread.rb +79 -27
  94. data/lib/rails/graphql/request/component/typename.rb +28 -13
  95. data/lib/rails/graphql/request/component.rb +77 -36
  96. data/lib/rails/graphql/request/context.rb +19 -9
  97. data/lib/rails/graphql/request/errors.rb +16 -6
  98. data/lib/rails/graphql/request/event.rb +23 -8
  99. data/lib/rails/graphql/request/helpers/directives.rb +69 -27
  100. data/lib/rails/graphql/request/helpers/selection_set.rb +57 -25
  101. data/lib/rails/graphql/request/helpers/value_writers.rb +24 -19
  102. data/lib/rails/graphql/request/prepared_data.rb +100 -0
  103. data/lib/rails/graphql/request/steps/authorizable.rb +24 -14
  104. data/lib/rails/graphql/request/steps/organizable.rb +111 -49
  105. data/lib/rails/graphql/request/steps/{prepareable.rb → preparable.rb} +21 -8
  106. data/lib/rails/graphql/request/steps/{resolveable.rb → resolvable.rb} +16 -7
  107. data/lib/rails/graphql/request/strategy/cached_strategy.rb +64 -0
  108. data/lib/rails/graphql/request/strategy/dynamic_instance.rb +6 -6
  109. data/lib/rails/graphql/request/strategy/multi_query_strategy.rb +6 -13
  110. data/lib/rails/graphql/request/strategy/sequenced_strategy.rb +9 -9
  111. data/lib/rails/graphql/request/strategy.rb +147 -77
  112. data/lib/rails/graphql/request/subscription.rb +82 -0
  113. data/lib/rails/graphql/request.rb +353 -104
  114. data/lib/rails/graphql/schema.rb +251 -106
  115. data/lib/rails/graphql/shortcuts.rb +33 -8
  116. data/lib/rails/graphql/source/active_record/builders.rb +64 -51
  117. data/lib/rails/graphql/source/active_record_source.rb +158 -82
  118. data/lib/rails/graphql/source/base.rb +83 -0
  119. data/lib/rails/graphql/source/builder.rb +115 -0
  120. data/lib/rails/graphql/source/scoped_arguments.rb +39 -21
  121. data/lib/rails/graphql/source.rb +90 -228
  122. data/lib/rails/graphql/subscription/provider/action_cable.rb +113 -0
  123. data/lib/rails/graphql/subscription/provider/base.rb +192 -0
  124. data/lib/rails/graphql/subscription/provider.rb +18 -0
  125. data/lib/rails/graphql/subscription/store/base.rb +141 -0
  126. data/lib/rails/graphql/subscription/store/memory.rb +136 -0
  127. data/lib/rails/graphql/subscription/store.rb +19 -0
  128. data/lib/rails/graphql/subscription.rb +17 -0
  129. data/lib/rails/graphql/to_gql.rb +29 -32
  130. data/lib/rails/graphql/type/creator.rb +196 -0
  131. data/lib/rails/graphql/type/enum/directive_location_enum.rb +11 -11
  132. data/lib/rails/graphql/type/enum/type_kind_enum.rb +3 -3
  133. data/lib/rails/graphql/type/enum.rb +44 -50
  134. data/lib/rails/graphql/type/input.rb +92 -25
  135. data/lib/rails/graphql/type/interface.rb +29 -28
  136. data/lib/rails/graphql/type/object/directive_object.rb +10 -9
  137. data/lib/rails/graphql/type/object/enum_value_object.rb +3 -3
  138. data/lib/rails/graphql/type/object/field_object.rb +24 -6
  139. data/lib/rails/graphql/type/object/input_value_object.rb +6 -7
  140. data/lib/rails/graphql/type/object/schema_object.rb +5 -8
  141. data/lib/rails/graphql/type/object/type_object.rb +62 -25
  142. data/lib/rails/graphql/type/object.rb +34 -26
  143. data/lib/rails/graphql/type/scalar/any_scalar.rb +30 -0
  144. data/lib/rails/graphql/type/scalar/bigint_scalar.rb +5 -5
  145. data/lib/rails/graphql/type/scalar/binary_scalar.rb +5 -3
  146. data/lib/rails/graphql/type/scalar/boolean_scalar.rb +8 -8
  147. data/lib/rails/graphql/type/scalar/date_scalar.rb +5 -3
  148. data/lib/rails/graphql/type/scalar/date_time_scalar.rb +5 -3
  149. data/lib/rails/graphql/type/scalar/decimal_scalar.rb +5 -3
  150. data/lib/rails/graphql/type/scalar/float_scalar.rb +5 -5
  151. data/lib/rails/graphql/type/scalar/id_scalar.rb +6 -5
  152. data/lib/rails/graphql/type/scalar/int_scalar.rb +6 -5
  153. data/lib/rails/graphql/type/scalar/json_scalar.rb +41 -0
  154. data/lib/rails/graphql/type/scalar/string_scalar.rb +18 -4
  155. data/lib/rails/graphql/type/scalar/time_scalar.rb +8 -6
  156. data/lib/rails/graphql/type/scalar.rb +26 -23
  157. data/lib/rails/graphql/type/union.rb +21 -18
  158. data/lib/rails/graphql/type.rb +43 -26
  159. data/lib/rails/graphql/type_map.rb +268 -165
  160. data/lib/rails/graphql/uri.rb +167 -0
  161. data/lib/rails/graphql/version.rb +19 -3
  162. data/lib/rails/graphql.rake +3 -0
  163. data/lib/rails/graphql.rb +91 -56
  164. data/lib/rails-graphql.rb +1 -1
  165. data/test/assets/en.yml +29 -0
  166. data/test/assets/introspection-mem.txt +1 -1
  167. data/test/assets/introspection.gql +2 -0
  168. data/test/assets/mem.gql +86 -99
  169. data/test/assets/mysql.gql +406 -0
  170. data/test/assets/sqlite.gql +96 -73
  171. data/test/assets/translate.gql +346 -0
  172. data/test/config.rb +19 -8
  173. data/test/graphql/schema_test.rb +14 -50
  174. data/test/graphql/source_test.rb +8 -85
  175. data/test/graphql/type/enum_test.rb +207 -203
  176. data/test/graphql/type/input_test.rb +14 -9
  177. data/test/graphql/type/interface_test.rb +12 -9
  178. data/test/graphql/type/object_test.rb +8 -2
  179. data/test/graphql/type/scalar/any_scalar_test.rb +38 -0
  180. data/test/graphql/type/scalar/boolean_scalar_test.rb +6 -3
  181. data/test/graphql/type/scalar/json_scalar_test.rb +23 -0
  182. data/test/graphql/type_map_test.rb +63 -81
  183. data/test/graphql/type_test.rb +0 -19
  184. data/test/graphql_test.rb +1 -1
  185. data/test/integration/{authorization/authorization_test.rb → authorization_test.rb} +40 -14
  186. data/test/integration/config.rb +36 -3
  187. data/test/integration/customization_test.rb +39 -0
  188. data/test/integration/global_id_test.rb +99 -0
  189. data/test/integration/memory/star_wars_introspection_test.rb +24 -16
  190. data/test/integration/memory/star_wars_query_test.rb +54 -3
  191. data/test/integration/memory/star_wars_validation_test.rb +3 -3
  192. data/test/integration/mysql/star_wars_introspection_test.rb +25 -0
  193. data/test/integration/persisted_query_test.rb +87 -0
  194. data/test/integration/resolver_precedence_test.rb +154 -0
  195. data/test/integration/schemas/memory.rb +24 -10
  196. data/test/integration/schemas/mysql.rb +62 -0
  197. data/test/integration/schemas/sqlite.rb +21 -12
  198. data/test/integration/sqlite/star_wars_global_id_test.rb +89 -0
  199. data/test/integration/sqlite/star_wars_introspection_test.rb +10 -0
  200. data/test/integration/sqlite/star_wars_query_test.rb +14 -1
  201. data/test/integration/translate_test.rb +73 -0
  202. data/test/test_ext.rb +16 -13
  203. metadata +125 -161
  204. data/ext/depend +0 -3
  205. data/ext/graphqlparser/Ast.cpp +0 -346
  206. data/ext/graphqlparser/Ast.h +0 -1214
  207. data/ext/graphqlparser/AstNode.h +0 -36
  208. data/ext/graphqlparser/AstVisitor.h +0 -137
  209. data/ext/graphqlparser/GraphQLParser.cpp +0 -76
  210. data/ext/graphqlparser/GraphQLParser.h +0 -55
  211. data/ext/graphqlparser/JsonVisitor.cpp +0 -161
  212. data/ext/graphqlparser/JsonVisitor.cpp.inc +0 -456
  213. data/ext/graphqlparser/JsonVisitor.h +0 -121
  214. data/ext/graphqlparser/JsonVisitor.h.inc +0 -110
  215. data/ext/graphqlparser/VERSION +0 -1
  216. data/ext/graphqlparser/c/GraphQLAst.cpp +0 -324
  217. data/ext/graphqlparser/c/GraphQLAst.h +0 -180
  218. data/ext/graphqlparser/c/GraphQLAstForEachConcreteType.h +0 -44
  219. data/ext/graphqlparser/c/GraphQLAstNode.cpp +0 -25
  220. data/ext/graphqlparser/c/GraphQLAstNode.h +0 -33
  221. data/ext/graphqlparser/c/GraphQLAstToJSON.cpp +0 -21
  222. data/ext/graphqlparser/c/GraphQLAstToJSON.h +0 -24
  223. data/ext/graphqlparser/c/GraphQLAstVisitor.cpp +0 -55
  224. data/ext/graphqlparser/c/GraphQLAstVisitor.h +0 -53
  225. data/ext/graphqlparser/c/GraphQLParser.cpp +0 -35
  226. data/ext/graphqlparser/c/GraphQLParser.h +0 -54
  227. data/ext/graphqlparser/dump_json_ast.cpp +0 -48
  228. data/ext/graphqlparser/lexer.lpp +0 -324
  229. data/ext/graphqlparser/parser.ypp +0 -693
  230. data/ext/graphqlparser/parsergen/lexer.cpp +0 -2633
  231. data/ext/graphqlparser/parsergen/lexer.h +0 -528
  232. data/ext/graphqlparser/parsergen/location.hh +0 -189
  233. data/ext/graphqlparser/parsergen/parser.tab.cpp +0 -3300
  234. data/ext/graphqlparser/parsergen/parser.tab.hpp +0 -646
  235. data/ext/graphqlparser/parsergen/position.hh +0 -179
  236. data/ext/graphqlparser/parsergen/stack.hh +0 -156
  237. data/ext/graphqlparser/syntaxdefs.h +0 -19
  238. data/ext/libgraphqlparser/AstNode.h +0 -36
  239. data/ext/libgraphqlparser/CMakeLists.txt +0 -148
  240. data/ext/libgraphqlparser/CONTRIBUTING.md +0 -23
  241. data/ext/libgraphqlparser/GraphQLParser.cpp +0 -76
  242. data/ext/libgraphqlparser/GraphQLParser.h +0 -55
  243. data/ext/libgraphqlparser/JsonVisitor.cpp +0 -161
  244. data/ext/libgraphqlparser/JsonVisitor.h +0 -121
  245. data/ext/libgraphqlparser/LICENSE +0 -22
  246. data/ext/libgraphqlparser/README.clang-tidy +0 -7
  247. data/ext/libgraphqlparser/README.md +0 -84
  248. data/ext/libgraphqlparser/ast/ast.ast +0 -203
  249. data/ext/libgraphqlparser/ast/ast.py +0 -61
  250. data/ext/libgraphqlparser/ast/c.py +0 -100
  251. data/ext/libgraphqlparser/ast/c.pyc +0 -0
  252. data/ext/libgraphqlparser/ast/c_impl.py +0 -61
  253. data/ext/libgraphqlparser/ast/c_impl.pyc +0 -0
  254. data/ext/libgraphqlparser/ast/c_visitor_impl.py +0 -39
  255. data/ext/libgraphqlparser/ast/c_visitor_impl.pyc +0 -0
  256. data/ext/libgraphqlparser/ast/casing.py +0 -26
  257. data/ext/libgraphqlparser/ast/casing.pyc +0 -0
  258. data/ext/libgraphqlparser/ast/cxx.py +0 -197
  259. data/ext/libgraphqlparser/ast/cxx.pyc +0 -0
  260. data/ext/libgraphqlparser/ast/cxx_impl.py +0 -61
  261. data/ext/libgraphqlparser/ast/cxx_impl.pyc +0 -0
  262. data/ext/libgraphqlparser/ast/cxx_json_visitor_header.py +0 -42
  263. data/ext/libgraphqlparser/ast/cxx_json_visitor_header.pyc +0 -0
  264. data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.py +0 -80
  265. data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.pyc +0 -0
  266. data/ext/libgraphqlparser/ast/cxx_visitor.py +0 -64
  267. data/ext/libgraphqlparser/ast/cxx_visitor.pyc +0 -0
  268. data/ext/libgraphqlparser/ast/js.py +0 -65
  269. data/ext/libgraphqlparser/ast/license.py +0 -10
  270. data/ext/libgraphqlparser/ast/license.pyc +0 -0
  271. data/ext/libgraphqlparser/c/GraphQLAstNode.cpp +0 -25
  272. data/ext/libgraphqlparser/c/GraphQLAstNode.h +0 -33
  273. data/ext/libgraphqlparser/c/GraphQLAstToJSON.cpp +0 -21
  274. data/ext/libgraphqlparser/c/GraphQLAstToJSON.h +0 -24
  275. data/ext/libgraphqlparser/c/GraphQLAstVisitor.cpp +0 -55
  276. data/ext/libgraphqlparser/c/GraphQLAstVisitor.h +0 -53
  277. data/ext/libgraphqlparser/c/GraphQLParser.cpp +0 -35
  278. data/ext/libgraphqlparser/c/GraphQLParser.h +0 -54
  279. data/ext/libgraphqlparser/clang-tidy-all.sh +0 -3
  280. data/ext/libgraphqlparser/cmake/version.cmake +0 -16
  281. data/ext/libgraphqlparser/dump_json_ast.cpp +0 -48
  282. data/ext/libgraphqlparser/go/README.md +0 -20
  283. data/ext/libgraphqlparser/go/callbacks.go +0 -18
  284. data/ext/libgraphqlparser/go/gotest.go +0 -64
  285. data/ext/libgraphqlparser/lexer.lpp +0 -324
  286. data/ext/libgraphqlparser/libgraphqlparser.pc.in +0 -11
  287. data/ext/libgraphqlparser/parser.ypp +0 -693
  288. data/ext/libgraphqlparser/parsergen/lexer.cpp +0 -2633
  289. data/ext/libgraphqlparser/parsergen/lexer.h +0 -528
  290. data/ext/libgraphqlparser/parsergen/location.hh +0 -189
  291. data/ext/libgraphqlparser/parsergen/parser.tab.cpp +0 -3300
  292. data/ext/libgraphqlparser/parsergen/parser.tab.hpp +0 -646
  293. data/ext/libgraphqlparser/parsergen/position.hh +0 -179
  294. data/ext/libgraphqlparser/parsergen/stack.hh +0 -156
  295. data/ext/libgraphqlparser/python/CMakeLists.txt +0 -14
  296. data/ext/libgraphqlparser/python/README.md +0 -5
  297. data/ext/libgraphqlparser/python/example.py +0 -31
  298. data/ext/libgraphqlparser/syntaxdefs.h +0 -19
  299. data/ext/libgraphqlparser/test/BuildCAPI.c +0 -5
  300. data/ext/libgraphqlparser/test/CMakeLists.txt +0 -25
  301. data/ext/libgraphqlparser/test/JsonVisitorTests.cpp +0 -28
  302. data/ext/libgraphqlparser/test/ParserTests.cpp +0 -352
  303. data/ext/libgraphqlparser/test/kitchen-sink.graphql +0 -59
  304. data/ext/libgraphqlparser/test/kitchen-sink.json +0 -1
  305. data/ext/libgraphqlparser/test/schema-kitchen-sink.graphql +0 -78
  306. data/ext/libgraphqlparser/test/schema-kitchen-sink.json +0 -1
  307. data/ext/libgraphqlparser/test/valgrind.supp +0 -33
  308. data/ext/version.cpp +0 -21
  309. data/lib/graphqlparser.so +0 -0
  310. data/lib/rails/graphql/native/functions.rb +0 -38
  311. data/lib/rails/graphql/native/location.rb +0 -41
  312. data/lib/rails/graphql/native/pointers.rb +0 -23
  313. data/lib/rails/graphql/native/visitor.rb +0 -349
  314. data/lib/rails/graphql/native.rb +0 -56
  315. data/test/integration/schemas/authorization.rb +0 -12
@@ -1,20 +1,30 @@
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 Controller
6
6
  #
7
7
  # The controller helper methods that allow GraphQL to be performed on a
8
- # Rails Controller class.
8
+ # Rails Controller class
9
9
  module Controller
10
10
  extend ActiveSupport::Concern
11
11
 
12
- REQUEST_XARGS = %i[operation_name variables context schema].freeze
12
+ REQUEST_XARGS = %i[operation_name variables context schema query_cache_key].freeze
13
+ DESCRIBE_HEADER = <<~TXT.freeze
14
+ """
15
+ Use the following HTTP params to modify this result:
16
+ without_descriptions => Hide all descriptions
17
+ without_spec => Hide all default spec types
18
+ """
19
+ TXT
13
20
 
14
21
  included do
15
22
  # Each controller is assigned to a GraphQL schema on which the requests
16
23
  # will be performed from. It can be a string or the class
17
24
  class_attribute :gql_schema, instance_accessor: false
25
+
26
+ # Add the internal views directory
27
+ prepend_view_path("#{__dir__}/app/views")
18
28
  end
19
29
 
20
30
  # POST /execute
@@ -24,49 +34,73 @@ module Rails # :nodoc:
24
34
 
25
35
  # GET /describe
26
36
  def describe
27
- render plain: gql_schema.to_gql(
28
- with_descriptions: !params.key?(:without_descriptions),
29
- with_spec: !params.key?(:without_spec),
30
- )
37
+ render plain: gql_schema_header + gql_describe_schema + gql_schema_footer
38
+ end
39
+
40
+ # GET /graphiql
41
+ def graphiql
42
+ render '/graphiql', layout: false, locals: { settings: graphiql_settings }
31
43
  end
32
44
 
33
45
  protected
34
46
 
47
+ # Identifies if the request should be threated as a compiled request
48
+ def gql_compiled_request?(*)
49
+ false
50
+ end
51
+
35
52
  # Render a response as a GraphQL request
36
53
  def gql_request_response(*args, **xargs)
37
54
  render json: gql_request(*args, **xargs)
38
55
  end
39
56
 
40
57
  # Execute a GraphQL request
41
- def gql_request(query, **xargs)
42
- request_xargs = REQUEST_XARGS.inject({}) do |result, setting|
43
- result.merge(setting => (xargs[setting] || send("gql_#{setting}")))
58
+ def gql_request(document, **xargs)
59
+ request_xargs = REQUEST_XARGS.each_with_object({}) do |setting, result|
60
+ result[setting] ||= (xargs[setting] || send(:"gql_#{setting}"))
44
61
  end
45
62
 
46
- ::Rails::GraphQL::Request.execute(query, **request_xargs)
63
+ request_xargs[:hash] ||= gql_query_cache_key
64
+ request_xargs[:origin] ||= self
65
+ request_xargs[:compiled] ||= gql_compiled_request?(document)
66
+
67
+ request_xargs = request_xargs.except(*%i[query_cache_key query_cache_version])
68
+ ::Rails::GraphQL::Request.execute(document, **request_xargs)
47
69
  end
48
70
 
49
71
  # The schema on which the requests will be performed from
50
72
  def gql_schema
73
+ return @gql_schema if defined?(@gql_schema)
74
+
51
75
  schema = self.class.gql_schema
52
- schema = schema.safe_constantize if schema.is_a?(String)
53
- schema ||= application_default_schema
54
- return schema if schema.is_a?(Module) && schema < ::Rails::GraphQL::Schema
76
+ schema = schema.constantize if schema.is_a?(String)
77
+ schema ||= gql_application_default_schema
78
+
79
+ return @gql_schema = schema if schema.is_a?(Module) &&
80
+ schema < ::Rails::GraphQL::Schema
55
81
 
56
- raise ExecutionError, <<~MSG.squish
82
+ raise ExecutionError, (+<<~MSG).squish
57
83
  Unable to find a valid schema for #{self.class.name},
58
84
  defined value: #{schema.inspect}.
59
85
  MSG
60
86
  end
61
87
 
62
88
  # Get the GraphQL query to execute
63
- def gql_query
89
+ def gql_document
64
90
  params[:query]
65
91
  end
66
92
 
93
+ alias gql_query gql_document
94
+
95
+ # Get the cache key of the query for persisted queries
96
+ def gql_query_cache_key(key = nil, version = nil)
97
+ return unless (key ||= params[:query_cache_key]).present?
98
+ CacheKey.new(key, version || params[:query_cache_version])
99
+ end
100
+
67
101
  # Get the GraphQL operation name
68
102
  def gql_operation_name
69
- params[:operationName]
103
+ params[:operationName] || params[:operation_name]
70
104
  end
71
105
 
72
106
  # Get the GraphQL context for a requests
@@ -84,16 +118,48 @@ module Rails # :nodoc:
84
118
  end
85
119
  end
86
120
 
121
+ # Return the settings for the GraphiQL view
122
+ def graphiql_settings(mode = nil)
123
+ if mode == :cable
124
+ { mode: :cable, url: '/cable', channel: 'GraphQL::BaseChannel' }
125
+ else
126
+ { mode: :fetch, url: '/graphql' }
127
+ end
128
+ end
129
+
130
+ # Shows a text representation of the schema
131
+ def gql_describe_schema(schema = gql_schema)
132
+ schema.to_gql(
133
+ with_descriptions: !params.key?(:without_descriptions),
134
+ with_spec: !params.key?(:without_spec),
135
+ )
136
+ end
137
+
138
+ # Print a header of the current schema for the description process
139
+ # TODO: Maybe add a way to detect from which file the schema is being loaded
140
+ def gql_schema_header
141
+ ns = +" [#{gql_schema.namespace}]" if gql_schema.namespace != :base
142
+ +"#{DESCRIBE_HEADER}# Schema #{gql_schema.name}#{ns}\n"
143
+ end
144
+
145
+ # Show the footer of the describe page
146
+ def gql_schema_footer
147
+ $/ + $/ + '# Version: ' + gql_version + $/ +
148
+ '# Rails GraphQL ' + ::Rails::GraphQL::VERSION::STRING +
149
+ ' (Spec ' + ::GQLParser::VERSION + ')'
150
+ end
151
+
152
+ # Get the version of the running instance of GraphQL
153
+ def gql_version
154
+ ::Rails::GraphQL.type_map.version
155
+ end
156
+
87
157
  private
88
158
 
89
159
  # Find the default application schema
90
- def application_default_schema
91
- app_class = Rails.application.class
92
- source_name = app_class.respond_to?(:module_parent_name) \
93
- ? :module_parent_name \
94
- : :parent_name
95
-
96
- klass = "::GraphQL::#{app_class.send(source_name)}Schema".constantize
160
+ def gql_application_default_schema
161
+ app_class = Rails.application.class.name.chomp('::Application')
162
+ klass = "::GraphQL::#{app_class}Schema".safe_constantize
97
163
  self.class.gql_schema = klass
98
164
  end
99
165
  end
@@ -2,20 +2,20 @@
2
2
 
3
3
  require 'active_support/core_ext/module/attr_internal'
4
4
 
5
- module Rails # :nodoc:
6
- module GraphQL # :nodoc:
5
+ module Rails
6
+ module GraphQL
7
7
  # = GraphQL Controller Runtime
8
8
  #
9
9
  # Tool that calculates the runtime of a GraphQL operation. This works
10
10
  # similar to how Rails ActiveRecord calculate its execution time while
11
- # performing a request.
11
+ # performing a request
12
12
  module ControllerRuntime
13
13
  extend ActiveSupport::Concern
14
14
 
15
- module ClassMethods # :nodoc: all
15
+ module ClassMethods
16
16
  def log_process_action(payload)
17
17
  messages, gql_runtime = super, payload[:gql_runtime]
18
- messages << format('GraphQL: %.1fms', gql_runtime.to_f) if gql_runtime
18
+ messages << format(+'GraphQL: %.1fms', gql_runtime.to_f) if gql_runtime
19
19
  messages
20
20
  end
21
21
  end
@@ -1,14 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Rails # :nodoc:
4
- module GraphQL # :nodoc:
3
+ require "active_support/parameter_filter"
4
+
5
+ module Rails
6
+ module GraphQL
5
7
  # = GraphQL Log Subscriber
6
8
  #
7
- # This is the log tracker that workds the same way as ActiveRecord when it
8
- # has to report on logs that a query was performed.
9
- class LogSubscriber < ::ActiveSupport::LogSubscriber # :nodoc: all
9
+ # This is the log tracker that works the same way as ActiveRecord when it
10
+ # has to report on logs that a query was performed
11
+ class LogSubscriber < ::ActiveSupport::LogSubscriber
10
12
  class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
11
13
 
14
+ REMOVE_COMMENTS = /#(?=(?:[^"]*"[^"]*")*[^"]*$).*/
15
+
12
16
  def self.runtime
13
17
  RuntimeRegistry.gql_runtime ||= 0
14
18
  end
@@ -22,23 +26,65 @@ module Rails # :nodoc:
22
26
  return unless logger.debug?
23
27
 
24
28
  payload = event.payload
29
+ cached = '[CACHE]' if payload[:cached]
30
+ doc = payload[:document]&.gsub(REMOVE_COMMENTS, '')&.squish
31
+
32
+ desc = +"#{header(event, cached)} #{doc}"
33
+ desc << debug_variables(payload[:variables]) unless payload[:variables].blank?
34
+
35
+ debug(desc)
36
+ end
37
+
38
+ def compile(event)
39
+ return unless logger.debug?
40
+
41
+ payload = event.payload
42
+ doc = payload[:document].gsub(REMOVE_COMMENTS, '').squish
43
+
44
+ helper = ActiveSupport::NumberHelper::NumberToHumanSizeConverter
45
+ total = helper.convert(payload[:total], EMPTY_HASH)
46
+
47
+ debug(+"#{header(event, 'Compile')} #{total} #{doc}")
48
+ end
49
+
50
+ def validate(event)
51
+ return unless logger.debug?
52
+
53
+ payload = event.payload
54
+ doc = payload[:document].gsub(REMOVE_COMMENTS, '').squish
55
+ valid = payload[:result] ? color('YES', GREEN) : color('NO', RED)
56
+
57
+ debug(+"#{header(event, 'Valid?')} #{valid} #{doc}")
58
+ end
59
+
60
+ def subscription(event)
61
+ return unless logger.info?
25
62
 
26
- name = ['GraphQL', payload[:name].presence]
27
- name.unshift('CACHE') if payload[:cached]
28
- name = "#{name.compact.join(' ')} (#{event.duration.round(1)}ms)"
63
+ item, type, provider = event.payload.values_at(:item, :type, :provider)
64
+ provider = provider.class.name.sub(/\ARails::GraphQL::Subscription::/, '')
65
+ duration = event.duration.round(1)
29
66
 
30
- document = payload[:document].squish
31
- variables = payload[:variables].blank? ? nil : begin
32
- " (#{JSON.pretty_generate(payload[:variables]).squish})"
67
+ desc = +"#{header(event, provider)} Subscription #{type}"
68
+
69
+ unless item.nil?
70
+ hex = { added: GREEN, removed: RED, updated: BLUE }[type]
71
+
72
+ if type == :updated
73
+ desc << +": #{color(item.sid, hex)}"
74
+ else
75
+ desc << +": [#{color(item.sid, hex)}] #{item.schema}.#{item.field.gql_name}"
76
+ desc << +" [#{(item.scope === null_subscription_scope ? nil : item.scope).inspect}"
77
+ desc << +", #{item.args.as_json.inspect}]"
78
+ end
33
79
  end
34
80
 
35
- debug " #{color(name, MAGENTA, true)} #{document}#{variables}"
81
+ info(desc)
36
82
  end
37
83
 
38
84
  private
39
85
 
40
86
  def logger
41
- GraphQL.config.logger
87
+ GraphQL.logger
42
88
  end
43
89
 
44
90
  def debug(*)
@@ -47,14 +93,35 @@ module Rails # :nodoc:
47
93
  log_query_source if GraphQL.config.verbose_logs
48
94
  end
49
95
 
96
+ def header(event, suffix = '')
97
+ duration = event.duration.round(1)
98
+ parts = [' GraphQL', suffix.presence, event.payload[:name]]
99
+ parts << "(#{duration}ms)" unless duration.zero?
100
+
101
+ color(parts.compact.join(' '), MAGENTA, true)
102
+ end
103
+
104
+ def debug_variables(vars)
105
+ vars = JSON.pretty_generate(parameter_filter.filter(vars))
106
+ +' ' << '(' << vars.squish << ')'
107
+ end
108
+
50
109
  def log_query_source
51
110
  source = extract_query_source_location(caller)
52
- logger.debug(" ↳ #{source}") if source
111
+ logger.debug(+" ↳ #{source}") if source
53
112
  end
54
113
 
55
114
  def extract_query_source_location(locations)
56
115
  backtrace_cleaner.clean(locations.lazy).first
57
116
  end
117
+
118
+ def parameter_filter
119
+ ActiveSupport::ParameterFilter.new(GraphQL.config.filter_parameters)
120
+ end
121
+
122
+ def null_subscription_scope
123
+ Request::Subscription::NULL_SCOPE
124
+ end
58
125
  end
59
126
  end
60
127
 
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Rails # :nodoc:
4
- module GraphQL # :nodoc:
5
- class Request # :nodoc:
3
+ module Rails
4
+ module GraphQL
5
+ class Request
6
6
  # = GraphQL Request Arguments
7
7
  #
8
8
  # This is an extension of an +OpenStruct+ since argument values can be
@@ -14,14 +14,34 @@ module Rails # :nodoc:
14
14
  # the spread and operation.
15
15
  class Arguments < OpenStruct
16
16
  THREAD_KEY = :_rails_graphql_operation
17
- LAZY_LOADER = ->(key, object) { object.variables[key] }.curry
18
17
 
19
- delegate :key?, to: :@table
18
+ class Lazy < Delegator
19
+ attr_reader :var_name
20
+
21
+ def self.[](key)
22
+ new(key)
23
+ end
24
+
25
+ def initialize(var_name)
26
+ @var_name = var_name
27
+ end
28
+
29
+ def __getobj__
30
+ Arguments.operation&.variables&.dig(var_name)
31
+ end
32
+
33
+ def __setobj__(*)
34
+ raise FrozenError
35
+ end
36
+ end
37
+
38
+ delegate :key?, :[], to: :@table
39
+ alias to_hash to_h
20
40
 
21
41
  class << self
22
42
  # Easy access to the easy loader method
23
43
  def lazy
24
- LAZY_LOADER
44
+ Lazy
25
45
  end
26
46
 
27
47
  # Get the current operation thread safely
@@ -42,50 +62,6 @@ module Rails # :nodoc:
42
62
  def scoped?
43
63
  operation.present?
44
64
  end
45
-
46
- # If it's running under a scope, transform proc based values
47
- def transform(value)
48
- return if value.nil?
49
- scoped? && value.is_a?(Proc) ? value.call(operation) : value
50
- end
51
- end
52
-
53
- # Transform any proc by its actual value before returning the hash
54
- def to_h(*)
55
- super.transform_values(&self.class.method(:transform))
56
- end
57
-
58
- # Before iterating, transform any needed value
59
- def each_pair
60
- enum = to_h.to_enum
61
- return enum unless block_given?
62
- enum.each { |v| yield v }
63
- self
64
- end
65
-
66
- # rubocop:disable Style/MissingRespondToMissing
67
- # Transform the value before returning
68
- def method_missing(*)
69
- self.class.transform(super)
70
- end
71
- # rubocop:enable Style/MissingRespondToMissing
72
-
73
- # Transform the value before returning
74
- def [](*)
75
- self.class.transform(super)
76
- end
77
-
78
- # Transform the value before returning
79
- def dig(name, *names)
80
- result = self.class.transform(super(name))
81
- names.empty? ? result : result&.dig(*names)
82
- end
83
-
84
- # Override the freeze method to just freeze the table and do not create
85
- # the getters and setter methods
86
- def freeze
87
- @table.freeze
88
- super
89
65
  end
90
66
  end
91
67
  end
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module GraphQL
5
+ class Request
6
+ # = GraphQL Request Backtrace
7
+ #
8
+ # Display any request errors in a nice way. By default, it won't display
9
+ # anything that is internal of this gem, but
10
+ module Backtrace
11
+ COL_MAX_WIDTH = 100
12
+
13
+ mattr_accessor :skip_base_class, instance_accessor: false,
14
+ default: Rails::GraphQL::StandardError
15
+
16
+ extend self
17
+
18
+ # Check if the given +error+ should be skipped
19
+ # TODO: Maybe check +cause+ to proper evaluate the skip
20
+ def skip?(error)
21
+ error.class <= skip_base_class
22
+ end
23
+
24
+ # Display the provided +error+ from the provided +request+
25
+ def print(error, component, request)
26
+ return if skip?(error)
27
+
28
+ table = print_table(error, component, request)
29
+ info = print_backtrace(error, request)
30
+
31
+ request.schema.logger.error(+"#{table}\n\n#{info}")
32
+ end
33
+
34
+ protected
35
+
36
+ # Organize and print a table of the place where the error occurred
37
+ def print_table(error, component, request)
38
+ table = begin_table
39
+ stack = [component] + request.stack
40
+ counter = stack.count { |item| !item.is_a?(Numeric) }
41
+ objects = request.strategy.context
42
+ oid = -1
43
+
44
+ last_object = suffix = nil
45
+ while (item = stack.shift)
46
+ next suffix = +"[#{item}]" if item.is_a?(Numeric)
47
+ location = request.location_of(item)
48
+ location = location[0].values.join(':') if location
49
+
50
+ data = [counter, location]
51
+ add = send("row_for_#{item.kind}", data, item, suffix)
52
+
53
+ if item.kind == :field
54
+ oid += 1
55
+ data[5] ||= oid == 0 ? '↓' : print_object(objects&.at(oid - 1))
56
+ data[3] ||= print_object(objects&.at(oid))
57
+ end
58
+
59
+ data[4] = clean_arguments(data[4], request) if data[4]
60
+
61
+ suffix = nil
62
+ counter -= 1
63
+ add_to_table(table, data) if add != false
64
+ end
65
+
66
+ stringify_table(table)
67
+ end
68
+
69
+ # Print the backtrace steps of the error
70
+ def print_backtrace(error, request)
71
+ steps = error.backtrace
72
+ # steps = cleaner.clean(steps) unless cleaner.nil?
73
+
74
+ klass = +"(\e[4m#{error.class}\e[24m)"
75
+ stage = +" [#{request.strategy.stage}]" if skip_base_class != StandardError
76
+
77
+ +"\e[1m#{error.message} #{klass}#{stage}\e[0m\n#{steps.join("\n")}"
78
+ end
79
+
80
+ # Add new information to the table and update headers sizes
81
+ def add_to_table(table, data)
82
+ data = data.map(&:to_s)
83
+ table[:header].each.with_index do |(header, size), idx|
84
+ length = data[idx].length
85
+ if length > COL_MAX_WIDTH
86
+ table[:header][header] = COL_MAX_WIDTH
87
+ data[idx] = data[idx][0..COL_MAX_WIDTH][0..-5] + '...'
88
+ elsif length > size
89
+ table[:header][header] = length
90
+ end
91
+ end
92
+
93
+ table[:body] << data
94
+ end
95
+
96
+ # Build the string of the given table
97
+ def stringify_table(table)
98
+ sizes = table[:header].values
99
+ headers = table[:header].keys
100
+
101
+ # Add a little banner
102
+ table[:body][-1][1] = "\e[1m\e[35m#{'GQL'.ljust(sizes[1])}\e[0m"
103
+
104
+ # Build all the lines
105
+ lines = table[:body].reverse.prepend(headers).map do |row|
106
+ +' ' << row.map.with_index do |col, idx|
107
+ col.ljust(sizes[idx])
108
+ end.join(' | ') << ' '
109
+ end
110
+
111
+ # Add a divider between headers and values
112
+ divider = sizes.map { |val| '-' * val }.join('-+-')
113
+ divider = +'-' << divider << '-'
114
+ lines.insert(1, divider)
115
+
116
+ # Bold the header and join the lines
117
+ lines[0] = +"\e[1m#{lines[0]}\e[0m"
118
+ lines.join("\n")
119
+ end
120
+
121
+ # Better display records and other objects that might be to big to
122
+ # show in here
123
+ def print_object(object)
124
+ object.respond_to?(:to_gql_backtrace) ? object.to_gql_backtrace : object.inspect
125
+ end
126
+
127
+ # Make sure to properly parse arguments and filter them
128
+ def clean_arguments(arguments, request)
129
+ value = arguments.as_json
130
+ return '{}' if value.blank?
131
+
132
+ request.cache(:backtrace_arguments_filter) do
133
+ ActiveSupport::ParameterFilter.new(GraphQL.config.filter_parameters)
134
+ end.filter(value)
135
+ end
136
+
137
+ # Visitors
138
+ def row_for_field(data, item, suffix)
139
+ field = item.field
140
+ parent =
141
+ if !field
142
+ '*'
143
+ elsif field.owner.is_a?(Helpers::WithSchemaFields)
144
+ item.request.schema.type_name_for(field.schema_type)
145
+ else
146
+ field.owner.gql_name
147
+ end
148
+
149
+ name = +"#{parent}.#{field.gql_name}#{suffix}" unless field.nil?
150
+
151
+ data.push(name || +"*.#{item.name}")
152
+ data.push(nil, item.arguments, nil)
153
+ end
154
+
155
+ def row_for_fragment(data, item, *)
156
+ type = item.instance_variable_get(:@node)[1]
157
+ object = item.current_object || item.type_klass
158
+ data.push(+"fragment #{item.name}", type, nil)
159
+ data.push(print_object(object))
160
+ end
161
+
162
+ def row_for_operation(data, item, *)
163
+ data.push(+"#{item.type} #{item.name}".squish)
164
+ data.push('nil')
165
+ data.push(item.variables)
166
+ data.push(item.typename)
167
+ end
168
+
169
+ def row_for_spread(data, item, *)
170
+ return false unless item.inline?
171
+
172
+ type = item.instance_variable_get(:@node)[1]
173
+ object = item.current_object || item.type_klass
174
+ data.push('...', type, nil)
175
+ data.push(print_object(object))
176
+ end
177
+
178
+ def row_for_schema(data, item, *)
179
+ data.push('schema', item.namespace.inspect, nil, item.name)
180
+ end
181
+
182
+ private
183
+
184
+ # The headers and sizes plus the rows for the table structure
185
+ def begin_table
186
+ {
187
+ header: {
188
+ ' ' => 1,
189
+ 'Loc' => 3,
190
+ 'Field' => 5,
191
+ 'Object' => 6,
192
+ 'Arguments' => 9,
193
+ 'Result' => 6,
194
+ },
195
+ body: [],
196
+ }
197
+ end
198
+
199
+ # Find the class that will be responsible for cleaning the backtrace
200
+ def cleaner
201
+ LogSubscriber.backtrace_cleaner
202
+ end
203
+
204
+ # Rewrite the cleaner method so that it returns nil and do not clean
205
+ # any of the items of the backtrace
206
+ def show_everything!
207
+ redefine_singleton_method(:cleaner) { nil }
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end