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,21 +1,21 @@
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
  # A set of helper methods to write a value to the response
7
7
  module ValueWriters
8
8
  # TODO: Maybe move this to a setting so it allow extensions
9
9
  KIND_WRITERS = {
10
- union: 'write_union',
11
- interface: 'write_interface',
12
- object: 'write_object',
10
+ union: :write_union,
11
+ interface: :write_interface,
12
+ object: :write_object,
13
13
  }.freeze
14
14
 
15
15
  # Write a value to the response
16
16
  def write_value(value)
17
17
  return write_leaf(value) if value.nil?
18
- send(KIND_WRITERS[field.kind] || 'write_leaf', value)
18
+ send(KIND_WRITERS[field.kind] || :write_leaf, value)
19
19
  end
20
20
 
21
21
  # Resolve a given value when it is an array
@@ -31,7 +31,7 @@ module Rails # :nodoc:
31
31
  response.next
32
32
 
33
33
  format_array_execption(error, idx)
34
- request.exception_to_error(error, @node)
34
+ request.exception_to_error(error, self)
35
35
  end
36
36
  rescue StandardError => error
37
37
  format_array_execption(error, idx)
@@ -41,13 +41,13 @@ module Rails # :nodoc:
41
41
 
42
42
  # Helper to start writing as array
43
43
  def write_array!(value, &block)
44
- raise InvalidValueError, <<~MSG.squish unless value.respond_to?(:each)
44
+ raise InvalidValueError, (+<<~MSG).squish unless value.respond_to?(:each)
45
45
  The #{gql_name} field is excepting an array
46
46
  but got an "#{value.class.name}" instead.
47
47
  MSG
48
48
 
49
49
  @writing_array = true
50
- response.with_stack(field.gql_name, array: true, plain: leaf_type?) do
50
+ response.with_stack(gql_name, array: true, plain: leaf_type?) do
51
51
  value.each(&block)
52
52
  end
53
53
  ensure
@@ -56,9 +56,11 @@ module Rails # :nodoc:
56
56
 
57
57
  # Add the item index to the exception message
58
58
  def format_array_execption(error, idx)
59
- real_error = 'The ' + ActiveSupport::Inflector.ordinalize(idx + 1)
60
- real_error += " value of the #{gql_name} field"
61
- source_error = "The #{gql_name} field value"
59
+ real_error = (+<<~MSG).squish
60
+ The #{ActiveSupport::Inflector.ordinalize(idx + 1)} value of the #{gql_name} field
61
+ MSG
62
+
63
+ source_error = +"The #{gql_name} field value"
62
64
 
63
65
  message = error.message.gsub(source_error, real_error)
64
66
  error.define_singleton_method(:message) { message }
@@ -68,13 +70,13 @@ module Rails # :nodoc:
68
70
 
69
71
  # Write a value based on a Union type
70
72
  def write_union(value)
71
- object = type_klass.all_members.reverse_each.find { |t| t.valid_member?(value) }
73
+ object = type_klass.all_members&.reverse_each&.find { |t| t.valid_member?(value) }
72
74
  object.nil? ? raise_invalid_member! : resolve_fields(object)
73
75
  end
74
76
 
75
77
  # Write a value based on a Interface type
76
78
  def write_interface(value)
77
- object = type_klass.all_types.reverse_each.find { |t| t.valid_member?(value) }
79
+ object = type_klass.all_types&.reverse_each&.find { |t| t.valid_member?(value) }
78
80
  object.nil? ? raise_invalid_member! : resolve_fields(object)
79
81
  end
80
82
 
@@ -105,7 +107,7 @@ module Rails # :nodoc:
105
107
  # A problem when an object-based value is not a valid member of the
106
108
  # +type_klass+ of this field
107
109
  def raise_invalid_member!
108
- raise(FieldError, <<~MSG.squish)
110
+ raise FieldError, (+<<~MSG).squish
109
111
  The #{gql_name} field result is not a member of #{type_klass.gql_name}.
110
112
  MSG
111
113
  end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module GraphQL
5
+ class Request
6
+ # = GraphQL Request Bypass Data
7
+ #
8
+ # This class works in collaboration with the prepare stage of a request
9
+ # execution. In that stage, the strategy must check if the request already
10
+ # have a prepared data for the field. The field can result in a instance
11
+ # of this class, which than bypasses the prepare stage by grabbing the
12
+ # next value from here
13
+ class PreparedData
14
+ SCHEMA_BASED = Helpers::WithSchemaFields::TYPE_FIELD_CLASS.keys.freeze
15
+ NULL = Object.new.freeze
16
+
17
+ REPEAT_OPTIONS = {
18
+ true => true,
19
+ false => 1,
20
+ cycle: true,
21
+ always: true,
22
+ }.freeze
23
+
24
+ # Look up the given +field+ using the request as a reference. It accepts
25
+ # any +Rails::GraphQL::Field+ or a string where +"query.create_user"+
26
+ # means a query field on the request schema with +create_user+ as name,
27
+ # or +create_user+ as gql_name, and "User.devices" will use the schema
28
+ # of the request to lookup the type +"User"+ and then pick the field
29
+ # +devices+
30
+ def self.lookup(request, field)
31
+ return field if field.is_a?(GraphQL::Field)
32
+
33
+ source, name = field.to_s.split('.')
34
+ return if source.nil? || name.nil?
35
+
36
+ if SCHEMA_BASED.any? { |item| item.to_s == source }
37
+ request.schema.find_field!(source.to_sym, name)
38
+ elsif (type = request.schema.find_type!(source)).is_a?(Helpers::WithFields)
39
+ type.find_field!(name)
40
+ else
41
+ field
42
+ end
43
+ rescue NotFoundError
44
+ # Return the original value, maybe it will be resolved somewhere else
45
+ field
46
+ end
47
+
48
+ def initialize(field, value, repeat: 1)
49
+ # Check if it has a valid field
50
+ raise ::ArgumentError, (+<<~MSG).squish unless field.is_a?(GraphQL::Field)
51
+ Unable to setup a prepared data for "#{field.inspect}".
52
+ You must provide a valid field.
53
+ MSG
54
+
55
+ @field = field
56
+ @value = value
57
+ @array = value.is_a?(Array) && !field.array?
58
+ @repeat =
59
+ case repeat
60
+ when Numeric then repeat
61
+ when Enumerator then repeat.size
62
+ else REPEAT_OPTIONS[repeat]
63
+ end
64
+ end
65
+
66
+ # Add one more item to the list of data
67
+ def push(*values)
68
+ return @value += values if @array
69
+ @value = [@value, *values]
70
+ end
71
+
72
+ # The the whole value, because the prepare stage always deal with all
73
+ # the information available
74
+ def all
75
+ @field.array? ? Array.wrap(@value) : @value
76
+ end
77
+
78
+ # Get the enumerable of the whole value
79
+ def enum
80
+ @enum ||= (@array ? @value.to_enum : @value.then)
81
+ end
82
+
83
+ # Get the next value, take into consideration the value on repeat
84
+ def next
85
+ value = enum.next
86
+ @field.array? ? Array.wrap(value) : value
87
+ rescue StopIteration
88
+ if @repeat == true || (@repeat != false && (@repeat -= 1) > 0)
89
+ enum.rewind
90
+ self.next
91
+ else
92
+ NULL
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -1,27 +1,34 @@
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
  # Helper methods for the authorize step of a request
7
7
  module Authorizable
8
8
  # Event used to perform an authorization step
9
9
  class Event < GraphQL::Event
10
+ # Same behavior as the request event
11
+ def same_source?(other)
12
+ super || (source.try(:kind) == :field && source.field == other)
13
+ end
14
+
10
15
  # Similar to trigger for object, but with an extra extension for
11
16
  # instance methods defined on the given object
12
17
  def authorize_using(object, send_args, events = nil)
13
- cache = data[:request].cache(name)[object] ||= []
18
+ @object = object
19
+
20
+ cache = data[:request].nested_cache(:authorize, object) { [] }
14
21
  return false if cache.present? && cache.none?
15
22
  args, xargs = send_args
16
23
 
17
24
  # Authorize through instance method
18
25
  using_object = cache[0] ||= authorize_on_object(object)
19
26
  set_on(using_object) do |instance|
20
- instance.public_send("#{name}!", self, *args, **xargs)
27
+ instance.public_send(:authorize!, *args, **xargs)
21
28
  end if using_object
22
29
 
23
30
  # Authorize through events
24
- using_events = cache[1] ||= (events || object.all_events[name]).presence
31
+ using_events = cache[1] ||= (events || object.all_events.try(:[], :authorize))
25
32
  using_events&.each { |block| block.call(self, *args, **xargs) }
26
33
 
27
34
  # Does any authorize process ran
@@ -29,14 +36,14 @@ module Rails # :nodoc:
29
36
  end
30
37
 
31
38
  # Simply unauthorize the operation
32
- def unauthorize!(*, message: nil, **)
33
- raise UnauthorizedFieldError, message || <<~MSG.squish
39
+ def unauthorized!(*, message: nil, **)
40
+ raise UnauthorizedFieldError, message || (+<<~MSG).squish
34
41
  Unauthorized access to "#{field.gql_name}" field.
35
42
  MSG
36
43
  end
37
44
 
38
45
  # Simply authorize the operation
39
- def authorize!(*)
46
+ def authorized!(*)
40
47
  throw :authorized
41
48
  end
42
49
 
@@ -54,16 +61,18 @@ module Rails # :nodoc:
54
61
  end
55
62
 
56
63
  # Check if the field is correctly authorized to be executed
64
+ # TODO: Implement reverse order of authorization
57
65
  def check_authorization!
58
66
  return unless field.authorizable?
59
67
  *args, block = field.authorizer
60
68
 
61
69
  catch(:authorized) do
62
70
  event = authorization_event
63
- schema_events = request.all_events[:authorize]
71
+ schema_events = request.all_events.try(:[], :authorize)
64
72
  executed = event.authorize_using(schema, args, schema_events)
73
+ executed = event.authorize_using(self, args) || executed
65
74
 
66
- element = field
75
+ element = field&.owner
67
76
  while element && element != schema
68
77
  executed = event.authorize_using(element, args) || executed
69
78
  element = element.try(:owner)
@@ -74,14 +83,14 @@ module Rails # :nodoc:
74
83
  executed = true
75
84
  end
76
85
 
77
- event.unauthorize!(message: <<~MSG.squish) unless executed
86
+ event.unauthorized!(message: (+<<~MSG).squish) unless executed
78
87
  Authorization required but unable to be executed
79
88
  MSG
80
89
  end
81
90
  rescue UnauthorizedFieldError => error
82
91
  request.rescue_with_handler(error)
83
- request.exception_to_error(error, @node)
84
- invalidate!
92
+ request.exception_to_error(error, self, stage: :authorization)
93
+ invalidate!(:authorization)
85
94
  end
86
95
 
87
96
  private
@@ -89,6 +98,7 @@ module Rails # :nodoc:
89
98
  # Build and store the authorization event
90
99
  def authorization_event
91
100
  Event.new(:authorize, self,
101
+ context: request.context,
92
102
  request: request,
93
103
  schema: schema,
94
104
  field: field,
@@ -1,18 +1,56 @@
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
  # Helper methods for the organize step of a request
7
7
  module Organizable
8
- # Check if it is already organized
9
- def organized?
10
- data.nil?
11
- end
12
-
13
8
  # Organize the object if it is not already organized
14
9
  def organize!
15
- capture_exception(:organize, true) { organize unless organized? }
10
+ organize unless defined?(@organized)
11
+ rescue => error
12
+ invalidate!
13
+ report_exception(error)
14
+ ensure
15
+ @organized = true
16
+ end
17
+
18
+ # Build the cache object
19
+ def cache_dump
20
+ arguments =
21
+ if @arguments === EMPTY_HASH
22
+ nil
23
+ elsif is_a?(Component::Operation)
24
+ all_to_gid(@arguments.transform_values)
25
+ else
26
+ @arguments.to_h
27
+ end
28
+
29
+ { arguments: arguments }
30
+ end
31
+
32
+ # Organize from cache data
33
+ def cache_load(data)
34
+ if (args = data[:arguments]).blank?
35
+ @arguments = EMPTY_HASH
36
+ elsif is_a?(Component::Operation)
37
+ @arguments = all_from_gid(args.transform_values)
38
+
39
+ unless defined?(@variables)
40
+ @variables = collect_arguments(self, request.args, var_access: false).freeze
41
+ end
42
+ else
43
+ @arguments = request.build(Request::Arguments, args).freeze
44
+ end
45
+
46
+ # Always trigger the organized event
47
+ unless unresolvable?
48
+ strategy.add_listeners_from(self)
49
+ trigger_event(:organized)
50
+ end
51
+
52
+ # Mark component as organized
53
+ @organized = true
16
54
  end
17
55
 
18
56
  protected
@@ -31,53 +69,67 @@ module Rails # :nodoc:
31
69
  def organize_then(after_block, &block)
32
70
  stacked do
33
71
  block.call
34
- strategy.add_listener(self)
72
+ strategy.add_listeners_from(self)
35
73
  trigger_event(:organized)
36
74
  after_block.call if after_block.present?
37
75
  end
38
- ensure
39
- @data = nil
40
76
  end
41
77
 
42
78
  # Helper parser for request arguments (operation variables) that
43
79
  # collect necessary arguments from the request
44
- def parse_variables
80
+ # Default values forces this method to run even without nodes
81
+ def parse_variables(nodes)
82
+ if nodes.blank?
83
+ @variables = EMPTY_HASH
84
+ @arguments = EMPTY_HASH
85
+ return
86
+ end
87
+
45
88
  @arguments = {}
46
89
 
47
- visitor.collect_variables(*data[:variables]) do |data, node|
48
- arg_name = data[:name]
49
- raise ExecutionError, <<~MSG.squish if arguments.key?(arg_name)
90
+ nodes&.each do |node|
91
+ arg_name, type, value, _directives = node
92
+ raise ExecutionError, (+<<~MSG).squish if arguments.key?(arg_name)
50
93
  The "#{arg_name}" argument is already defined for this #{kind}.
51
94
  MSG
52
95
 
53
- extra = data.to_h.except(:name, :type).merge(owner: schema)
54
- item = arguments[arg_name] = Argument.new(arg_name, data[:type], **extra)
96
+ # TODO: Move this to a better builder of the type
97
+ type_name, dimensions, nullability = type
98
+ xargs = { owner: schema, default: value, array: dimensions > 0 }
99
+ xargs[:nullable] = (nullability & 0b10) == 0
100
+ xargs[:null] = (nullability & 0b01) == 0
101
+
102
+ # TODO: Follow up with the support for directives
103
+ item = arguments[arg_name.to_s] = Argument.new(arg_name, type_name, **xargs)
55
104
  item.node = node
56
105
  item.validate!
57
- end unless data[:variables].blank?
58
-
59
- args = request.sanitized_arguments
60
- args = collect_arguments(self, args, var_access: false) do |errors|
61
- "Invalid arguments for #{log_source}: #{errors}."
62
106
  end
63
107
 
108
+ args = collect_arguments(self, request.args, var_access: false)
109
+
64
110
  @variables = args.freeze
65
111
  @arguments.freeze
112
+ rescue ArgumentsError => error
113
+ raise ArgumentsError, (+<<~MSG).squish
114
+ Invalid arguments for #{log_source}: #{error.message}.
115
+ MSG
66
116
  end
67
117
 
68
118
  # Helper parser for arguments that also collect necessary variables
69
- def parse_arguments
70
- args = {}
71
- visitor.collect_arguments(*data[:arguments]) do |data|
72
- args[data[:name]] = variable = data[:variable]
73
- args[data[:name]] = data[:value] if variable.nil? || variable.null?
74
- end unless data[:arguments].blank?
75
-
76
- args = collect_arguments(self, args) do |errors|
77
- "Invalid arguments for #{gql_name} #{kind}: #{errors}."
119
+ # Default values forces this method to run even without nodes
120
+ def parse_arguments(nodes)
121
+ return @arguments = EMPTY_HASH if nodes.blank?
122
+
123
+ args = nodes.each.with_object({}) do |(name, value, var_name), hash|
124
+ hash[name.to_s] = var_name.nil? ? value : var_name
78
125
  end
79
126
 
127
+ args = collect_arguments(self, args)
80
128
  @arguments = request.build(Request::Arguments, args).freeze
129
+ rescue ArgumentsError => error
130
+ raise ArgumentsError, (+<<~MSG).squish
131
+ Invalid arguments for #{gql_name} #{kind}: #{error.message}.
132
+ MSG
81
133
  end
82
134
 
83
135
  # Build a hash that collect validated values for a set of arguments.
@@ -89,41 +141,51 @@ module Rails # :nodoc:
89
141
 
90
142
  errors = []
91
143
  source = source.all_arguments if source.respond_to?(:all_arguments)
144
+ return unless source.present?
145
+
92
146
  result = source.each_pair.each_with_object({}) do |(key, argument), hash|
93
- next unless values.key?(key)
94
- value = values[key]
147
+ value = values && values[key]
95
148
 
96
- # Pointer means operation variable
97
- if value.is_a?(::FFI::Pointer)
98
- var_name = visitor.node_name(value)
99
- raise ArgumentError, <<~MSG.squish unless var_access
149
+ # A token means the name of a variable
150
+ if value.is_a?(::GQLParser::Token) && value.of_type?(:variable)
151
+ var_name = value.to_s
152
+ raise ArgumentError, (+<<~MSG).squish unless var_access
100
153
  Unable to use variable "$#{var_name}" in the current scope
101
154
  MSG
102
155
 
103
- op_vars ||= operation.all_arguments
104
- raise ArgumentError, <<~MSG.squish unless (op_var = op_vars[var_name]).present?
156
+ op_var = (op_vars ||= operation.all_arguments).try(:[], var_name)
157
+ raise ArgumentError, (+<<~MSG).squish unless op_var.present?
105
158
  The #{operation.log_source} does not define the $#{var_name} variable
106
159
  MSG
107
160
 
108
161
  # When arguments are not equivalent, they can ended up with
109
162
  # invalid values, so this already ensures that whatever the
110
163
  # variable value ended up being, it will be valid due to this
111
- raise ArgumentError, <<~MSG.squish unless op_var =~ argument
164
+ raise ArgumentError, (+<<~MSG).squish unless op_var =~ argument
112
165
  The $#{var_name} variable on #{operation.log_source} is not compatible
113
166
  with "#{key}" argument
114
167
  MSG
115
168
 
169
+ # Mark the variable as used and grab the value
116
170
  operation.used_variables << var_name
117
- next unless variables.key?(op_var.name)
118
171
  value = variables[op_var.name]
119
- else
172
+ elsif !value.nil?
120
173
  # Only when the given value is an actual value that we check if
121
174
  # it is valid
122
- raise ArgumentError, <<~MSG.squish unless argument.valid?(value)
123
- Invalid value provided to "#{key}" argument
175
+ raise ArgumentError, (+<<~MSG).squish unless argument.valid?(value)
176
+ Invalid value "#{value.to_s}" provided to
177
+ #{argument.node ? "$#{argument.name} variable" : "#{key} argument"}
178
+ on #{argument.node ? operation.log_source : gql_name}
124
179
  MSG
125
180
 
126
181
  value = argument.deserialize(value)
182
+ elsif argument.default_value?
183
+ # Ensure to always import arguments that have default values but
184
+ # were not included in the field
185
+ value = argument.deserialize
186
+ else
187
+ # Otherwise, simply just skip the argument
188
+ next
127
189
  end
128
190
 
129
191
  hash[argument.name] = value
@@ -131,14 +193,14 @@ module Rails # :nodoc:
131
193
  errors << error.message
132
194
  end
133
195
 
134
- # Checks for any required arugment that was not provided
196
+ # Checks for any required argument that was not provided
135
197
  source.each_value do |argument|
136
198
  next if result.key?(argument.name) || argument.null?
137
- errors << "The \"#{argument.gql_name}\" argument can not be null"
199
+ errors << +"the \"#{argument.gql_name}\" argument can not be null"
138
200
  end
139
201
 
140
202
  return result if errors.blank?
141
- raise ArgumentError, block.call(errors.to_sentence)
203
+ raise ArgumentsError, errors.to_sentence
142
204
  end
143
205
  end
144
206
  end
@@ -1,26 +1,39 @@
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
  # Helper methods for the prepare step of a request
7
- module Prepareable
7
+ module Preparable
8
8
  # Prepare the object
9
9
  def prepare!
10
- capture_exception(:prepare) { prepare }
10
+ prepare
11
+ rescue => error
12
+ report_exception(error)
13
+ end
14
+
15
+ # Marks that the field has prepared data that came outside from the
16
+ # request
17
+ def prepared_data!
18
+ @prepared_data = true
19
+ end
20
+
21
+ # Checks if the field has prepared data
22
+ def prepared_data?
23
+ defined?(@prepared_data) && @prepared_data
11
24
  end
12
25
 
13
26
  protected
14
27
 
15
28
  # Normal mode of the prepare step
16
29
  def prepare
17
- return if invalid?
18
30
  prepare_then { prepare_fields }
19
31
  end
20
32
 
21
33
  # The actual process that prepare the object
22
34
  def prepare_then(after_block = nil, &block)
23
- return if invalid?
35
+ return if unresolvable?
36
+
24
37
  stacked do
25
38
  block.call if block.present?
26
39
  trigger_event(:prepared)
@@ -1,25 +1,34 @@
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
  # Helper methods for the resolve step of a request
7
- module Resolveable
7
+ module Resolvable
8
8
  # Resolve the object
9
9
  def resolve!
10
- capture_exception(:resolve) { resolve }
10
+ resolve
11
+ rescue => error
12
+ report_exception(error)
11
13
  end
12
14
 
13
15
  protected
14
16
 
15
17
  # Normal mode of the resolve step
18
+ # TODO: Field a way to cache the resolved result, mostly for
19
+ # performance improvement in recursion
16
20
  def resolve
21
+ return if skipped?
17
22
  invalid? ? try(:resolve_invalid) : resolve_then
23
+ rescue
24
+ try(:resolve_invalid)
25
+ raise
18
26
  end
19
27
 
20
28
  # The actual process that resolve the object
21
29
  def resolve_then(after_block = nil, &block)
22
- return if invalid?
30
+ return if unresolvable?
31
+
23
32
  stacked do
24
33
  block.call if block.present?
25
34
  trigger_event(:finalize)