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
@@ -0,0 +1,191 @@
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
+ oidx = -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
+ oidx += 1
55
+ data[5] ||= oidx == 0 ? '↓' : print_object(objects&.at(oidx - 1))
56
+ data[3] ||= print_object(objects&.at(oidx))
57
+ end
58
+
59
+ suffix = nil
60
+ counter -= 1
61
+ add_to_table(table, data) if add != false
62
+ end
63
+
64
+ stringify_table(table)
65
+ end
66
+
67
+ # Print the backtrace steps of the error
68
+ def print_backtrace(error, request)
69
+ steps = error.backtrace
70
+ steps = cleaner.clean(steps) unless cleaner.nil?
71
+
72
+ klass = +"(\e[4m#{error.class}\e[24m)"
73
+ stage = +" [#{request.strategy.stage}]" if skip_base_class != StandardError
74
+
75
+ +"\e[1m#{error.message} #{klass}#{stage}\e[0m\n#{steps.join("\n")}"
76
+ end
77
+
78
+ # Add new information to the table and update headers sizes
79
+ def add_to_table(table, data)
80
+ data = data.map(&:to_s)
81
+ table[:header].each.with_index do |(header, size), idx|
82
+ length = data[idx].length
83
+ if length > COL_MAX_WIDTH
84
+ table[:header][header] = COL_MAX_WIDTH
85
+ data[idx] = data[idx][0..COL_MAX_WIDTH][0..-5] + '...'
86
+ elsif length > size
87
+ table[:header][header] = length
88
+ end
89
+ end
90
+
91
+ table[:body] << data
92
+ end
93
+
94
+ # Build the string of the given table
95
+ def stringify_table(table)
96
+ sizes = table[:header].values
97
+ headers = table[:header].keys
98
+
99
+ # Add a little banner
100
+ table[:body][-1][1] = "\e[1m\e[35m#{'GQL'.ljust(sizes[1])}\e[0m"
101
+
102
+ # Build all the lines
103
+ lines = table[:body].reverse.prepend(headers).map do |row|
104
+ +' ' << row.map.with_index do |col, idx|
105
+ col.ljust(sizes[idx])
106
+ end.join(' | ') << ' '
107
+ end
108
+
109
+ # Add a divider between headers and values
110
+ divider = sizes.map { |val| '-' * val }.join('-+-')
111
+ divider = +'-' << divider << '-'
112
+ lines.insert(1, divider)
113
+
114
+ # Bold the header and join the lines
115
+ lines[0] = +"\e[1m#{lines[0]}\e[0m"
116
+ lines.join("\n")
117
+ end
118
+
119
+ # Better display records and other objects that might be to big to
120
+ # show in here
121
+ def print_object(object)
122
+ object.respond_to?(:to_gql_backtrace) ? object.to_gql_backtrace : object.inspect
123
+ end
124
+
125
+ # Visitors
126
+ def row_for_field(data, item, suffix)
127
+ field = item.field
128
+ name = +"#{field.owner.gql_name}.#{field.gql_name}#{suffix}" unless field.nil?
129
+
130
+ data.push(name || +"*.#{item.name}")
131
+ data.push(nil, (item.arguments ? item.arguments.to_json : 'nil'), nil)
132
+ end
133
+
134
+ def row_for_fragment(data, item, *)
135
+ type = item.instance_variable_get(:@node)[1]
136
+ object = item.current_object || item.type_klass
137
+ data.push(+"fragment #{item.name}", type, '')
138
+ data.push(print_object(object))
139
+ end
140
+
141
+ def row_for_operation(data, item, *)
142
+ data.push(+"#{item.type} #{item.name}".squish)
143
+ data.push('nil')
144
+ data.push(item.arguments.to_json)
145
+ data.push(item.typename)
146
+ end
147
+
148
+ def row_for_spread(data, item, *)
149
+ return false unless item.inline?
150
+
151
+ type = item.instance_variable_get(:@node)[1]
152
+ object = item.current_object || item.type_klass
153
+ data.push('...', type, '')
154
+ data.push(print_object(object))
155
+ end
156
+
157
+ def row_for_schema(data, item, *)
158
+ data.push('schema', item.namespace.inspect, nil, item.name)
159
+ end
160
+
161
+ private
162
+
163
+ # The headers and sizes plus the rows for the table structure
164
+ def begin_table
165
+ {
166
+ header: {
167
+ ' ' => 1,
168
+ 'Loc' => 3,
169
+ 'Field' => 5,
170
+ 'Object' => 6,
171
+ 'Arguments' => 9,
172
+ 'Result' => 6,
173
+ },
174
+ body: [],
175
+ }
176
+ end
177
+
178
+ # Find the class that will be responsible for cleaning the backtrace
179
+ def cleaner
180
+ LogSubscriber.backtrace_cleaner
181
+ end
182
+
183
+ # Rewrite the cleaner method so that it returns nil and do not clean
184
+ # any of the items of the backtrace
185
+ def show_everything!
186
+ redefine_singleton_method(:cleaner) { nil }
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
@@ -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 Component Field
7
7
  #
8
8
  # This class holds information about a given field that should be
@@ -14,61 +14,73 @@ module Rails # :nodoc:
14
14
  include Directives
15
15
 
16
16
  delegate :decorate, to: :type_klass
17
- delegate :operation, :variables, to: :parent
18
- delegate :method_name, :resolver, :performer, :type_klass, :leaf_type?,
19
- :dynamic_resolver?, to: :field
20
-
21
- parent_memoize :request
17
+ delegate :operation, :variables, :request, to: :parent
18
+ delegate :method_name, :resolver, :performer, :type_klass,:leaf_type?,
19
+ :dynamic_resolver?, :mutation?, to: :field
22
20
 
23
21
  attr_reader :name, :alias_name, :parent, :field, :arguments, :current_object
24
22
 
25
23
  alias args arguments
26
24
 
27
- def initialize(parent, node, data)
25
+ def initialize(parent, node)
28
26
  @parent = parent
29
27
 
30
- @name = data[:name]
31
- @alias_name = data[:alias]
32
-
33
- super(node, data)
34
- end
28
+ @name = node[0]
29
+ @alias_name = node[1]
35
30
 
36
- # Return both the field directives and the request directives
37
- def all_directives
38
- field.all_directives + super
31
+ super(node)
39
32
  end
40
33
 
41
34
  # Override that considers the requested field directives and also the
42
35
  # definition field events, both from itself and its directives events
43
36
  def all_listeners
44
- (request.cache(:listeners)[field] ||= field.all_listeners) + super
37
+ request.nested_cache(:listeners, field) do
38
+ if !field.listeners?
39
+ directive_listeners
40
+ elsif !directives?
41
+ field.all_listeners
42
+ else
43
+ local = directive_listeners
44
+ local.empty? ? field.all_listeners : field.all_listeners + local
45
+ end
46
+ end
45
47
  end
46
48
 
47
49
  # Override that considers the requested field directives and also the
48
50
  # definition field events, both from itself and its directives events
49
51
  def all_events
50
- @all_events ||= Helpers.merge_hash_array(
51
- (request.cache(:events)[field] ||= field.all_events),
52
- super,
53
- )
52
+ request.nested_cache(:events, field) do
53
+ if !field.events?
54
+ directive_events
55
+ elsif !directives?
56
+ field.all_events
57
+ else
58
+ Helpers.merge_hash_array(field.all_events, directive_events)
59
+ end
60
+ end
54
61
  end
55
62
 
56
63
  # Get and cache all the arguments for the field
57
64
  def all_arguments
58
- request.cache(:arguments)[field] ||= begin
59
- if (result = field.all_arguments).any?
60
- result.each_value.map(&:gql_name).zip(result.each_value).to_h
61
- else
62
- {}
65
+ return unless field.arguments?
66
+
67
+ request.nested_cache(:arguments, field) do
68
+ field.all_arguments.each_value.with_object({}) do |argument, hash|
69
+ hash[argument.gql_name] = argument
63
70
  end
64
71
  end
65
72
  end
66
73
 
74
+ # Check if the field is using a directive
75
+ def using?(item_or_symbol)
76
+ super || field.using?(item_or_symbol)
77
+ end
78
+
67
79
  # Assign a given +field+ to this class. The field must be an output
68
80
  # field, which means that +output_type?+ must be true. It also must be
69
81
  # called exactly once per field.
70
- def assing_to(field)
71
- raise ArgumentError, <<~MSG.squish if defined?(@assigned)
82
+ def assign_to(field)
83
+ raise ArgumentError, (+<<~MSG).squish if defined?(@assigned)
72
84
  The "#{gql_name}" field is already assigned to #{@field.inspect}.
73
85
  MSG
74
86
 
@@ -97,6 +109,14 @@ module Rails # :nodoc:
97
109
  true
98
110
  end
99
111
 
112
+ # Check if all the sub fields are broadcastable
113
+ # TODO: Maybe check for interfaces and if all types allow broadcast
114
+ def broadcastable?
115
+ value = field.broadcastable?
116
+ value = schema.config.default_subscription_broadcastable if value.nil?
117
+ value != false
118
+ end
119
+
100
120
  # A little extension of the +is_a?+ method that allows checking it using
101
121
  # the underlying +field+
102
122
  def of_type?(klass)
@@ -106,7 +126,7 @@ module Rails # :nodoc:
106
126
  # When the field is invalid, there's no much to do
107
127
  # TODO: Maybe add a invalid event trigger here
108
128
  def resolve_invalid(error = nil)
109
- request.exception_to_error(error, @node) if error.present?
129
+ request.exception_to_error(error, self) if error.present?
110
130
 
111
131
  validate_output!(nil)
112
132
  response.safe_add(gql_name, nil)
@@ -118,26 +138,44 @@ module Rails # :nodoc:
118
138
  # field needs to be redirected to the one from the actual resolved
119
139
  # +object+ type
120
140
  def resolve_with!(object)
141
+ return if skipped?
121
142
  return resolve! if invalid?
122
143
 
123
144
  old_field, @field = @field, object[@field.name]
145
+ request.nested_cache(:listeners, field) { strategy.add_listeners_from(self) }
124
146
  @current_object = object
125
147
  resolve!
126
148
  ensure
127
149
  @field, @current_object = old_field, nil
128
150
  end
129
151
 
152
+ # Build the cache object
153
+ def cache_dump
154
+ super.merge(field: (field && all_to_gid(field)))
155
+ end
156
+
157
+ # Organize from cache data
158
+ def cache_load(data)
159
+ @name = data[:node][0]
160
+ @alias_name = data[:node][1]
161
+ @field = all_from_gid(data[:field])
162
+ super
163
+
164
+ check_authorization! unless unresolvable?
165
+ end
166
+
130
167
  protected
131
168
 
132
169
  # Perform the organization step
133
170
  def organize_then(&block)
134
171
  super(block) do
135
172
  check_assignment!
173
+
174
+ parse_directives(@node[3])
136
175
  check_authorization!
137
176
 
138
- parse_arguments
139
- parse_directives
140
- parse_selection
177
+ parse_arguments(@node[2])
178
+ parse_selection(@node[4])
141
179
  end
142
180
  end
143
181
 
@@ -149,8 +187,7 @@ module Rails # :nodoc:
149
187
  # Perform the resolve step
150
188
  def resolve_then(&block)
151
189
  stacked do
152
- strategy.perform(self) if field.mutation?
153
- send(field.array? ? 'resolve_many' : 'resolve_one', &block)
190
+ send((field.array? ? :resolve_many : :resolve_one), &block)
154
191
  rescue StandardError => error
155
192
  resolve_invalid(error)
156
193
  end
@@ -181,46 +218,30 @@ module Rails # :nodoc:
181
218
  end
182
219
  end
183
220
 
184
- # This override allows reasigned fields to perform events. This
185
- # happens when fields are originally organized from interfaces. If
186
- # the event is stopped for the object, then it doesn't proceed to the
187
- # strategy implementation, ensuring compatibility
188
- def trigger_event(event_name, **xargs)
189
- return super if !defined?(@current_object) || @current_object.nil?
190
-
191
- listeners = request.cache(:listeners)[field] ||= field.all_listeners
192
- return super unless listeners.include?(event_name)
193
-
194
- callbacks = request.cache(:events)[field] ||= field.all_events
195
- old_events, @all_events = @all_events, callbacks
196
- super
197
- ensure
198
- @all_events = old_events
199
- end
200
-
201
221
  # Check if the field was assigned correctly to an output field
202
222
  def check_assignment!
203
- raise MissingFieldError, <<~MSG.squish if field.nil?
223
+ raise MissingFieldError, (+<<~MSG).squish if field.nil?
204
224
  Unable to find a field named "#{gql_name}" on
205
225
  #{entry_point? ? operation.kind : parent.type_klass.name}.
206
226
  MSG
207
227
 
208
- raise FieldError, <<~MSG.squish unless field.output_type?
228
+ raise FieldError, (+<<~MSG).squish unless field.output_type?
209
229
  The "#{gql_name}" was assigned to a non-output type of field: #{field.inspect}.
210
230
  MSG
211
231
 
212
- empty_selection = data[:selection].nil? || data[:selection].null?
213
- raise FieldError, <<~MSG.squish if field.leaf_type? && !empty_selection
214
- The "#{gql_name}" was assigned to the #{type_klass.gql_name} which
215
- is a leaf type and does not have nested fields.
216
- MSG
217
-
218
- raise FieldError, <<~MSG.squish if !field.leaf_type? && empty_selection
219
- The "#{gql_name}" was assigned to the #{type_klass.gql_name} which
220
- is not a leaf type and requires a selection of fields.
221
- MSG
232
+ if @node[4].nil?
233
+ raise FieldError, (+<<~MSG).squish if !field.leaf_type?
234
+ The "#{gql_name}" was assigned to the #{type_klass.gql_name} which
235
+ is not a leaf type and requires a selection of fields.
236
+ MSG
237
+ else
238
+ raise FieldError, (+<<~MSG).squish if field.leaf_type?
239
+ The "#{gql_name}" was assigned to the #{type_klass.gql_name} which
240
+ is a leaf type and does not have nested fields.
241
+ MSG
242
+ end
222
243
 
223
- raise DisabledFieldError, <<~MSG.squish if field.disabled?
244
+ raise DisabledFieldError, (+<<~MSG).squish if field.disabled?
224
245
  The "#{gql_name}" was found but it is marked as disabled.
225
246
  MSG
226
247
  end
@@ -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 Component Fragment
7
7
  #
8
8
  # This class holds information about a given fragment defined using the
@@ -12,19 +12,18 @@ module Rails # :nodoc:
12
12
  include SelectionSet
13
13
  include Directives
14
14
 
15
- attr_reader :name, :type_klass, :request
15
+ attr_reader :name, :type_klass, :request, :current_object
16
16
 
17
- def initialize(request, node, data)
18
- @name = data[:name]
17
+ def initialize(request, node)
18
+ @name = node[0]
19
19
  @request = request
20
20
 
21
- super(node, data)
21
+ super(node)
22
22
 
23
23
  check_duplicated_fragment!
24
24
  end
25
25
 
26
26
  # Return a lazy loaded variable proc
27
- # TODO: Mark all the dependent variables
28
27
  def variables
29
28
  Request::Arguments.lazy
30
29
  end
@@ -34,6 +33,16 @@ module Rails # :nodoc:
34
33
  Request::Arguments.operation
35
34
  end
36
35
 
36
+ # Stores all the used variables
37
+ def used_variables
38
+ return @used_variables if defined?(@used_variables)
39
+ end
40
+
41
+ # Stores all the used nested fragments
42
+ def used_fragments
43
+ return @used_fragments if defined?(@used_fragments)
44
+ end
45
+
37
46
  # Spread should always be performed with a current object, thus the
38
47
  # typename comes from it
39
48
  def typename
@@ -42,7 +51,7 @@ module Rails # :nodoc:
42
51
 
43
52
  # Only resolve if the +type_klass+ is equivalent to the given +object+
44
53
  def resolve_with!(object)
45
- return if invalid?
54
+ return if unresolvable?
46
55
 
47
56
  @current_object = object
48
57
  resolve!
@@ -50,6 +59,24 @@ module Rails # :nodoc:
50
59
  @current_object = nil
51
60
  end
52
61
 
62
+ # Check if all the sub fields are broadcastable
63
+ def broadcastable?
64
+ selection.each_value.all?(&:broadcastable?)
65
+ end
66
+
67
+ # Build the cache object
68
+ def cache_dump
69
+ super.merge(type_klass: all_to_gid(type_klass))
70
+ end
71
+
72
+ # Organize from cache data
73
+ def cache_load(data)
74
+ @name = data[:node][0]
75
+ @type_klass = all_from_gid(data[:type_klass])
76
+
77
+ super
78
+ end
79
+
53
80
  protected
54
81
 
55
82
  # Fragments always resolve selection unstacked on response, meaning
@@ -58,20 +85,25 @@ module Rails # :nodoc:
58
85
  false
59
86
  end
60
87
 
88
+ # Wrap the field organization with the collection of variables
89
+ def organize
90
+ organize_then { collect_usages { organize_fields } }
91
+ end
92
+
61
93
  # Perform the organization step
62
94
  def organize_then(&block)
63
95
  super(block) do
64
- @type_klass = find_type!(data[:type])
65
- parse_directives(:fragment_definition)
96
+ @type_klass = find_type!(@node[1])
97
+ parse_directives(@node[2], :fragment_definition)
66
98
 
67
99
  check_assignment!
68
- parse_selection
100
+ parse_selection(@node[3])
69
101
  end
70
102
  end
71
103
 
72
104
  # Resolve the spread operation
73
105
  def resolve
74
- return if invalid?
106
+ return if unresolvable?
75
107
 
76
108
  object = @current_object || type_klass
77
109
  resolve_then if type_klass =~ object
@@ -86,32 +118,48 @@ module Rails # :nodoc:
86
118
 
87
119
  # Check if the field was assigned correctly to an output field
88
120
  def check_assignment!
89
- raise ExecutionError, <<~MSG.squish unless type_klass.output_type?
90
- Unable to assing #{type_klass.gql_name} to "#{name}" fragment because
121
+ raise ExecutionError, (+<<~MSG).squish unless type_klass.output_type?
122
+ Unable to assign #{type_klass.gql_name} to "#{name}" fragment because
91
123
  it is not a output type.
92
124
  MSG
93
125
 
94
- raise ExecutionError, <<~MSG.squish if type_klass.leaf_type?
95
- Unable to assing #{type_klass.gql_name} to "#{name}" fragment because
96
- a "#{type_klass.kind}" type can not be the source of a fragmnet.
126
+ raise ExecutionError, (+<<~MSG).squish if type_klass.leaf_type?
127
+ Unable to assign #{type_klass.gql_name} to "#{name}" fragment because
128
+ a "#{type_klass.kind}" type can not be the source of a fragment.
97
129
  MSG
98
130
  end
99
131
 
100
132
  # If there is another fragment with the same name already defined,
101
133
  # raise an error
102
134
  def check_duplicated_fragment!
103
- return unless request.fragments.key?(name)
135
+ other = request.document[1].find do |other|
136
+ other != @node && name == other[0]
137
+ end
104
138
 
139
+ return if other.nil?
105
140
  invalidate!
106
141
 
107
- other_node = request.fragments[name].instance_variable_get(:@node)
108
- location = GraphQL::Native.get_location(other_node)
109
-
110
- request.report_node_error(<<~MSG.squish, @node)
142
+ request.report_node_error((+<<~MSG).squish, self)
111
143
  Duplicated fragment named "#{name}" defined on
112
- line #{location.begin_line}:#{location.begin_column}
144
+ line #{other.begin_line}:#{other.begin_column}
113
145
  MSG
114
146
  end
147
+
148
+ # Use the information on the operation to collect all the variables
149
+ # and nested fragments that were used inside a fragment
150
+ def collect_usages
151
+ vars_total = operation.used_variables.size
152
+ frag_total = operation.used_fragments.size
153
+ yield
154
+ ensure
155
+ if operation.used_variables.size > vars_total
156
+ @used_variables = Set.new(operation.used_variables.to_enum.drop(vars_total))
157
+ end
158
+
159
+ if operation.used_fragments.size > frag_total
160
+ @used_fragments = Set.new(operation.used_fragments.to_enum.drop(frag_total))
161
+ end
162
+ end
115
163
  end
116
164
  end
117
165
  end