rails-graphql 0.1.0

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 (266) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +19 -0
  4. data/Rakefile +31 -0
  5. data/ext/depend +3 -0
  6. data/ext/extconf.rb +57 -0
  7. data/ext/graphqlparser/Ast.cpp +346 -0
  8. data/ext/graphqlparser/Ast.h +1214 -0
  9. data/ext/graphqlparser/AstNode.h +36 -0
  10. data/ext/graphqlparser/AstVisitor.h +137 -0
  11. data/ext/graphqlparser/GraphQLParser.cpp +76 -0
  12. data/ext/graphqlparser/GraphQLParser.h +55 -0
  13. data/ext/graphqlparser/JsonVisitor.cpp +161 -0
  14. data/ext/graphqlparser/JsonVisitor.cpp.inc +456 -0
  15. data/ext/graphqlparser/JsonVisitor.h +121 -0
  16. data/ext/graphqlparser/JsonVisitor.h.inc +110 -0
  17. data/ext/graphqlparser/VERSION +1 -0
  18. data/ext/graphqlparser/c/GraphQLAst.cpp +324 -0
  19. data/ext/graphqlparser/c/GraphQLAst.h +180 -0
  20. data/ext/graphqlparser/c/GraphQLAstForEachConcreteType.h +44 -0
  21. data/ext/graphqlparser/c/GraphQLAstNode.cpp +25 -0
  22. data/ext/graphqlparser/c/GraphQLAstNode.h +33 -0
  23. data/ext/graphqlparser/c/GraphQLAstToJSON.cpp +21 -0
  24. data/ext/graphqlparser/c/GraphQLAstToJSON.h +24 -0
  25. data/ext/graphqlparser/c/GraphQLAstVisitor.cpp +55 -0
  26. data/ext/graphqlparser/c/GraphQLAstVisitor.h +53 -0
  27. data/ext/graphqlparser/c/GraphQLParser.cpp +35 -0
  28. data/ext/graphqlparser/c/GraphQLParser.h +54 -0
  29. data/ext/graphqlparser/dump_json_ast.cpp +48 -0
  30. data/ext/graphqlparser/lexer.lpp +324 -0
  31. data/ext/graphqlparser/parser.ypp +693 -0
  32. data/ext/graphqlparser/parsergen/lexer.cpp +2633 -0
  33. data/ext/graphqlparser/parsergen/lexer.h +528 -0
  34. data/ext/graphqlparser/parsergen/location.hh +189 -0
  35. data/ext/graphqlparser/parsergen/parser.tab.cpp +3300 -0
  36. data/ext/graphqlparser/parsergen/parser.tab.hpp +646 -0
  37. data/ext/graphqlparser/parsergen/position.hh +179 -0
  38. data/ext/graphqlparser/parsergen/stack.hh +156 -0
  39. data/ext/graphqlparser/syntaxdefs.h +19 -0
  40. data/ext/libgraphqlparser/AstNode.h +36 -0
  41. data/ext/libgraphqlparser/CMakeLists.txt +148 -0
  42. data/ext/libgraphqlparser/CONTRIBUTING.md +23 -0
  43. data/ext/libgraphqlparser/GraphQLParser.cpp +76 -0
  44. data/ext/libgraphqlparser/GraphQLParser.h +55 -0
  45. data/ext/libgraphqlparser/JsonVisitor.cpp +161 -0
  46. data/ext/libgraphqlparser/JsonVisitor.h +121 -0
  47. data/ext/libgraphqlparser/LICENSE +22 -0
  48. data/ext/libgraphqlparser/README.clang-tidy +7 -0
  49. data/ext/libgraphqlparser/README.md +84 -0
  50. data/ext/libgraphqlparser/ast/ast.ast +203 -0
  51. data/ext/libgraphqlparser/ast/ast.py +61 -0
  52. data/ext/libgraphqlparser/ast/c.py +100 -0
  53. data/ext/libgraphqlparser/ast/c.pyc +0 -0
  54. data/ext/libgraphqlparser/ast/c_impl.py +61 -0
  55. data/ext/libgraphqlparser/ast/c_impl.pyc +0 -0
  56. data/ext/libgraphqlparser/ast/c_visitor_impl.py +39 -0
  57. data/ext/libgraphqlparser/ast/c_visitor_impl.pyc +0 -0
  58. data/ext/libgraphqlparser/ast/casing.py +26 -0
  59. data/ext/libgraphqlparser/ast/casing.pyc +0 -0
  60. data/ext/libgraphqlparser/ast/cxx.py +197 -0
  61. data/ext/libgraphqlparser/ast/cxx.pyc +0 -0
  62. data/ext/libgraphqlparser/ast/cxx_impl.py +61 -0
  63. data/ext/libgraphqlparser/ast/cxx_impl.pyc +0 -0
  64. data/ext/libgraphqlparser/ast/cxx_json_visitor_header.py +42 -0
  65. data/ext/libgraphqlparser/ast/cxx_json_visitor_header.pyc +0 -0
  66. data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.py +80 -0
  67. data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.pyc +0 -0
  68. data/ext/libgraphqlparser/ast/cxx_visitor.py +64 -0
  69. data/ext/libgraphqlparser/ast/cxx_visitor.pyc +0 -0
  70. data/ext/libgraphqlparser/ast/js.py +65 -0
  71. data/ext/libgraphqlparser/ast/license.py +10 -0
  72. data/ext/libgraphqlparser/ast/license.pyc +0 -0
  73. data/ext/libgraphqlparser/c/GraphQLAstNode.cpp +25 -0
  74. data/ext/libgraphqlparser/c/GraphQLAstNode.h +33 -0
  75. data/ext/libgraphqlparser/c/GraphQLAstToJSON.cpp +21 -0
  76. data/ext/libgraphqlparser/c/GraphQLAstToJSON.h +24 -0
  77. data/ext/libgraphqlparser/c/GraphQLAstVisitor.cpp +55 -0
  78. data/ext/libgraphqlparser/c/GraphQLAstVisitor.h +53 -0
  79. data/ext/libgraphqlparser/c/GraphQLParser.cpp +35 -0
  80. data/ext/libgraphqlparser/c/GraphQLParser.h +54 -0
  81. data/ext/libgraphqlparser/clang-tidy-all.sh +3 -0
  82. data/ext/libgraphqlparser/cmake/version.cmake +16 -0
  83. data/ext/libgraphqlparser/dump_json_ast.cpp +48 -0
  84. data/ext/libgraphqlparser/go/README.md +20 -0
  85. data/ext/libgraphqlparser/go/callbacks.go +18 -0
  86. data/ext/libgraphqlparser/go/gotest.go +64 -0
  87. data/ext/libgraphqlparser/lexer.lpp +324 -0
  88. data/ext/libgraphqlparser/libgraphqlparser.pc.in +11 -0
  89. data/ext/libgraphqlparser/parser.ypp +693 -0
  90. data/ext/libgraphqlparser/parsergen/lexer.cpp +2633 -0
  91. data/ext/libgraphqlparser/parsergen/lexer.h +528 -0
  92. data/ext/libgraphqlparser/parsergen/location.hh +189 -0
  93. data/ext/libgraphqlparser/parsergen/parser.tab.cpp +3300 -0
  94. data/ext/libgraphqlparser/parsergen/parser.tab.hpp +646 -0
  95. data/ext/libgraphqlparser/parsergen/position.hh +179 -0
  96. data/ext/libgraphqlparser/parsergen/stack.hh +156 -0
  97. data/ext/libgraphqlparser/python/CMakeLists.txt +14 -0
  98. data/ext/libgraphqlparser/python/README.md +5 -0
  99. data/ext/libgraphqlparser/python/example.py +31 -0
  100. data/ext/libgraphqlparser/syntaxdefs.h +19 -0
  101. data/ext/libgraphqlparser/test/BuildCAPI.c +5 -0
  102. data/ext/libgraphqlparser/test/CMakeLists.txt +25 -0
  103. data/ext/libgraphqlparser/test/JsonVisitorTests.cpp +28 -0
  104. data/ext/libgraphqlparser/test/ParserTests.cpp +352 -0
  105. data/ext/libgraphqlparser/test/kitchen-sink.graphql +59 -0
  106. data/ext/libgraphqlparser/test/kitchen-sink.json +1 -0
  107. data/ext/libgraphqlparser/test/schema-kitchen-sink.graphql +78 -0
  108. data/ext/libgraphqlparser/test/schema-kitchen-sink.json +1 -0
  109. data/ext/libgraphqlparser/test/valgrind.supp +33 -0
  110. data/ext/version.cpp +21 -0
  111. data/lib/generators/graphql/controller_generator.rb +22 -0
  112. data/lib/generators/graphql/schema_generator.rb +22 -0
  113. data/lib/generators/graphql/templates/controller.erb +5 -0
  114. data/lib/generators/graphql/templates/schema.erb +6 -0
  115. data/lib/graphqlparser.so +0 -0
  116. data/lib/rails-graphql.rb +2 -0
  117. data/lib/rails/graphql.rake +1 -0
  118. data/lib/rails/graphql.rb +185 -0
  119. data/lib/rails/graphql/adapters/mysql_adapter.rb +0 -0
  120. data/lib/rails/graphql/adapters/pg_adapter.rb +50 -0
  121. data/lib/rails/graphql/adapters/sqlite_adapter.rb +39 -0
  122. data/lib/rails/graphql/argument.rb +220 -0
  123. data/lib/rails/graphql/callback.rb +124 -0
  124. data/lib/rails/graphql/collectors.rb +14 -0
  125. data/lib/rails/graphql/collectors/hash_collector.rb +83 -0
  126. data/lib/rails/graphql/collectors/idented_collector.rb +73 -0
  127. data/lib/rails/graphql/collectors/json_collector.rb +114 -0
  128. data/lib/rails/graphql/config.rb +61 -0
  129. data/lib/rails/graphql/directive.rb +203 -0
  130. data/lib/rails/graphql/directive/deprecated_directive.rb +59 -0
  131. data/lib/rails/graphql/directive/include_directive.rb +24 -0
  132. data/lib/rails/graphql/directive/skip_directive.rb +24 -0
  133. data/lib/rails/graphql/errors.rb +42 -0
  134. data/lib/rails/graphql/event.rb +141 -0
  135. data/lib/rails/graphql/field.rb +318 -0
  136. data/lib/rails/graphql/field/input_field.rb +92 -0
  137. data/lib/rails/graphql/field/mutation_field.rb +52 -0
  138. data/lib/rails/graphql/field/output_field.rb +96 -0
  139. data/lib/rails/graphql/field/proxied_field.rb +131 -0
  140. data/lib/rails/graphql/field/resolved_field.rb +96 -0
  141. data/lib/rails/graphql/field/scoped_config.rb +22 -0
  142. data/lib/rails/graphql/field/typed_field.rb +104 -0
  143. data/lib/rails/graphql/helpers.rb +40 -0
  144. data/lib/rails/graphql/helpers/attribute_delegator.rb +39 -0
  145. data/lib/rails/graphql/helpers/inherited_collection.rb +152 -0
  146. data/lib/rails/graphql/helpers/leaf_from_ar.rb +141 -0
  147. data/lib/rails/graphql/helpers/registerable.rb +103 -0
  148. data/lib/rails/graphql/helpers/with_arguments.rb +125 -0
  149. data/lib/rails/graphql/helpers/with_assignment.rb +113 -0
  150. data/lib/rails/graphql/helpers/with_callbacks.rb +55 -0
  151. data/lib/rails/graphql/helpers/with_directives.rb +126 -0
  152. data/lib/rails/graphql/helpers/with_events.rb +81 -0
  153. data/lib/rails/graphql/helpers/with_fields.rb +141 -0
  154. data/lib/rails/graphql/helpers/with_namespace.rb +40 -0
  155. data/lib/rails/graphql/helpers/with_owner.rb +35 -0
  156. data/lib/rails/graphql/helpers/with_schema_fields.rb +230 -0
  157. data/lib/rails/graphql/helpers/with_validator.rb +52 -0
  158. data/lib/rails/graphql/introspection.rb +53 -0
  159. data/lib/rails/graphql/native.rb +56 -0
  160. data/lib/rails/graphql/native/functions.rb +38 -0
  161. data/lib/rails/graphql/native/location.rb +41 -0
  162. data/lib/rails/graphql/native/pointers.rb +23 -0
  163. data/lib/rails/graphql/native/visitor.rb +349 -0
  164. data/lib/rails/graphql/railtie.rb +85 -0
  165. data/lib/rails/graphql/railties/base_generator.rb +35 -0
  166. data/lib/rails/graphql/railties/controller.rb +101 -0
  167. data/lib/rails/graphql/railties/controller_runtime.rb +40 -0
  168. data/lib/rails/graphql/railties/log_subscriber.rb +62 -0
  169. data/lib/rails/graphql/request.rb +343 -0
  170. data/lib/rails/graphql/request/arguments.rb +93 -0
  171. data/lib/rails/graphql/request/component.rb +100 -0
  172. data/lib/rails/graphql/request/component/field.rb +225 -0
  173. data/lib/rails/graphql/request/component/fragment.rb +118 -0
  174. data/lib/rails/graphql/request/component/operation.rb +178 -0
  175. data/lib/rails/graphql/request/component/operation/subscription.rb +16 -0
  176. data/lib/rails/graphql/request/component/spread.rb +119 -0
  177. data/lib/rails/graphql/request/component/typename.rb +82 -0
  178. data/lib/rails/graphql/request/context.rb +51 -0
  179. data/lib/rails/graphql/request/errors.rb +54 -0
  180. data/lib/rails/graphql/request/event.rb +112 -0
  181. data/lib/rails/graphql/request/helpers/directives.rb +64 -0
  182. data/lib/rails/graphql/request/helpers/selection_set.rb +87 -0
  183. data/lib/rails/graphql/request/helpers/value_writers.rb +115 -0
  184. data/lib/rails/graphql/request/steps/organizable.rb +146 -0
  185. data/lib/rails/graphql/request/steps/prepareable.rb +33 -0
  186. data/lib/rails/graphql/request/steps/resolveable.rb +32 -0
  187. data/lib/rails/graphql/request/strategy.rb +249 -0
  188. data/lib/rails/graphql/request/strategy/dynamic_instance.rb +41 -0
  189. data/lib/rails/graphql/request/strategy/multi_query_strategy.rb +36 -0
  190. data/lib/rails/graphql/request/strategy/sequenced_strategy.rb +28 -0
  191. data/lib/rails/graphql/schema.rb +272 -0
  192. data/lib/rails/graphql/shortcuts.rb +77 -0
  193. data/lib/rails/graphql/source.rb +371 -0
  194. data/lib/rails/graphql/source/active_record/builders.rb +154 -0
  195. data/lib/rails/graphql/source/active_record_source.rb +231 -0
  196. data/lib/rails/graphql/source/scoped_arguments.rb +87 -0
  197. data/lib/rails/graphql/to_gql.rb +368 -0
  198. data/lib/rails/graphql/type.rb +138 -0
  199. data/lib/rails/graphql/type/enum.rb +206 -0
  200. data/lib/rails/graphql/type/enum/directive_location_enum.rb +30 -0
  201. data/lib/rails/graphql/type/enum/type_kind_enum.rb +57 -0
  202. data/lib/rails/graphql/type/input.rb +134 -0
  203. data/lib/rails/graphql/type/interface.rb +82 -0
  204. data/lib/rails/graphql/type/object.rb +111 -0
  205. data/lib/rails/graphql/type/object/directive_object.rb +34 -0
  206. data/lib/rails/graphql/type/object/enum_value_object.rb +25 -0
  207. data/lib/rails/graphql/type/object/field_object.rb +54 -0
  208. data/lib/rails/graphql/type/object/input_value_object.rb +49 -0
  209. data/lib/rails/graphql/type/object/schema_object.rb +40 -0
  210. data/lib/rails/graphql/type/object/type_object.rb +136 -0
  211. data/lib/rails/graphql/type/scalar.rb +71 -0
  212. data/lib/rails/graphql/type/scalar/bigint_scalar.rb +34 -0
  213. data/lib/rails/graphql/type/scalar/binary_scalar.rb +30 -0
  214. data/lib/rails/graphql/type/scalar/boolean_scalar.rb +37 -0
  215. data/lib/rails/graphql/type/scalar/date_scalar.rb +34 -0
  216. data/lib/rails/graphql/type/scalar/date_time_scalar.rb +32 -0
  217. data/lib/rails/graphql/type/scalar/decimal_scalar.rb +35 -0
  218. data/lib/rails/graphql/type/scalar/float_scalar.rb +32 -0
  219. data/lib/rails/graphql/type/scalar/id_scalar.rb +39 -0
  220. data/lib/rails/graphql/type/scalar/int_scalar.rb +36 -0
  221. data/lib/rails/graphql/type/scalar/string_scalar.rb +28 -0
  222. data/lib/rails/graphql/type/scalar/time_scalar.rb +40 -0
  223. data/lib/rails/graphql/type/union.rb +87 -0
  224. data/lib/rails/graphql/type_map.rb +347 -0
  225. data/lib/rails/graphql/version.rb +7 -0
  226. data/test/assets/introspection-db.json +0 -0
  227. data/test/assets/introspection-mem.txt +1 -0
  228. data/test/assets/introspection.gql +91 -0
  229. data/test/assets/luke.jpg +0 -0
  230. data/test/assets/mem.gql +428 -0
  231. data/test/assets/sqlite.gql +423 -0
  232. data/test/config.rb +80 -0
  233. data/test/graphql/request/context_test.rb +70 -0
  234. data/test/graphql/schema_test.rb +190 -0
  235. data/test/graphql/source_test.rb +237 -0
  236. data/test/graphql/type/enum_test.rb +203 -0
  237. data/test/graphql/type/input_test.rb +138 -0
  238. data/test/graphql/type/interface_test.rb +72 -0
  239. data/test/graphql/type/object_test.rb +104 -0
  240. data/test/graphql/type/scalar/bigint_scalar_test.rb +42 -0
  241. data/test/graphql/type/scalar/binary_scalar_test.rb +17 -0
  242. data/test/graphql/type/scalar/boolean_scalar_test.rb +40 -0
  243. data/test/graphql/type/scalar/date_scalar_test.rb +29 -0
  244. data/test/graphql/type/scalar/date_time_scalar_test.rb +29 -0
  245. data/test/graphql/type/scalar/decimal_scalar_test.rb +28 -0
  246. data/test/graphql/type/scalar/float_scalar_test.rb +22 -0
  247. data/test/graphql/type/scalar/id_scalar_test.rb +26 -0
  248. data/test/graphql/type/scalar/int_scalar_test.rb +26 -0
  249. data/test/graphql/type/scalar/string_scalar_test.rb +17 -0
  250. data/test/graphql/type/scalar/time_scalar_test.rb +36 -0
  251. data/test/graphql/type/scalar_test.rb +45 -0
  252. data/test/graphql/type/union_test.rb +82 -0
  253. data/test/graphql/type_map_test.rb +362 -0
  254. data/test/graphql/type_test.rb +68 -0
  255. data/test/graphql_test.rb +55 -0
  256. data/test/integration/config.rb +56 -0
  257. data/test/integration/memory/star_wars_introspection_test.rb +144 -0
  258. data/test/integration/memory/star_wars_query_test.rb +184 -0
  259. data/test/integration/memory/star_wars_validation_test.rb +99 -0
  260. data/test/integration/schemas/memory.rb +232 -0
  261. data/test/integration/schemas/sqlite.rb +82 -0
  262. data/test/integration/sqlite/star_wars_introspection_test.rb +15 -0
  263. data/test/integration/sqlite/star_wars_mutation_test.rb +82 -0
  264. data/test/integration/sqlite/star_wars_query_test.rb +71 -0
  265. data/test/test_ext.rb +48 -0
  266. metadata +509 -0
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails # :nodoc:
4
+ module GraphQL # :nodoc:
5
+ class Request # :nodoc:
6
+ # = GraphQL Request Errors
7
+ #
8
+ # This class is inspired by +ActiveModel::Erros+. The idea is to hold all
9
+ # the errors that happened during the execution of a request. It also
10
+ # helps to export such information to the result object.
11
+ class Errors
12
+ include Enumerable
13
+
14
+ delegate :empty?, :size, :each, :to_json, :last, :first, to: :@items
15
+
16
+ def initialize(request)
17
+ @request = request
18
+ @items = []
19
+ end
20
+
21
+ def reset!
22
+ @items = []
23
+ end
24
+
25
+ # Return a deep duplicated version of the items
26
+ def to_a
27
+ @items.deep_dup
28
+ end
29
+
30
+ # Add +message+ to the list of errors. Any other keywork argument will
31
+ # be used on set on the +:extensions+ part.
32
+ #
33
+ # ==== Options
34
+ #
35
+ # * <tt>:line</tt> - The line associated with the error.
36
+ # * <tt>:col</tt> - The column associated with the error.
37
+ # * <tt>:path</tt> - The path of the field that generated the error.
38
+ def add(message, line: nil, col: nil, path: nil, **extra)
39
+ item = { 'message' => message }
40
+
41
+ item['locations'] = extra.delete(:locations)
42
+ item['locations'] ||= [{ line: line.to_i, column: col.to_i }] \
43
+ if line.present? && col.present?
44
+
45
+ item['path'] = path if path.present? && path.is_a?(Array)
46
+ item['extensions'] = extra.deep_stringify_keys if extra.present?
47
+ item['locations'].map!(&:stringify_keys)
48
+
49
+ @items << item
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails # :nodoc:
4
+ module GraphQL # :nodoc:
5
+ class Request # :nodoc:
6
+ # = GraphQL Request Event
7
+ #
8
+ # A small extension of the original event to support extra methods and
9
+ # helpers when performing events during a request
10
+ class Event < GraphQL::Event
11
+ OBJECT_BASED_READERS = %i[fragment operation spread].freeze
12
+
13
+ delegate :schema, :errors, :context, to: :request
14
+ delegate :instance_for, to: :strategy
15
+ delegate :memo, to: :source
16
+
17
+ attr_reader :strategy, :request, :index
18
+
19
+ # Enhance the trigger settings based on the default for a request event
20
+ def self.trigger(event_name, object, *args, **xargs, &block)
21
+ xargs[:phase] ||= :execution
22
+ xargs[:fallback_trigger!] ||= :trigger_all unless block.present?
23
+ super(event_name, object, *args, **xargs, &block)
24
+ end
25
+
26
+ def initialize(name, strategy, source = nil, **data)
27
+ @request = strategy.request
28
+ @strategy = strategy
29
+
30
+ source ||= request.stack.first
31
+ @index, source = source, request.stack[1] if source.is_a?(Numeric)
32
+
33
+ super(name, source, **data)
34
+ end
35
+
36
+ # Provide a way to access the current field value
37
+ def current_value
38
+ resolver&.current_value
39
+ end
40
+
41
+ alias current current_value
42
+
43
+ # Provide a way to set the current value
44
+ def current_value=(value)
45
+ resolver&.override_value(value)
46
+ end
47
+
48
+ # Return the strategy context as the resolver
49
+ def resolver
50
+ strategy.context
51
+ end
52
+
53
+ # Return the actual field when the source is a request field
54
+ def field
55
+ source.field if source.try(:kind) === :field
56
+ end
57
+
58
+ # Check if the event source is of the given +type+
59
+ def for?(type)
60
+ source.of_type?(type)
61
+ end
62
+
63
+ # Check if the current +object+ is of the given +item+
64
+ def on?(item)
65
+ object.of_type?(item)
66
+ end
67
+
68
+ # Provide access to field arguments
69
+ def argument(name)
70
+ args_source.try(:[], name.to_sym)
71
+ end
72
+
73
+ # A combined helper for +instance_for+ and +set_on+
74
+ def on_instance(klass, &block)
75
+ set_on(klass.is_a?(Class) ? instance_for(klass) : klass, &block)
76
+ end
77
+
78
+ alias arg argument
79
+
80
+ protected
81
+
82
+ # When performing an event under a field object, the keyed-based
83
+ # parameters of a proc callback will be associated with actual field
84
+ # arguments
85
+ def args_source
86
+ source.arguments if source.try(:kind) === :field
87
+ end
88
+
89
+ private
90
+
91
+ # Check for object based readers
92
+ def respond_to_missing?(method_name, include_private = false)
93
+ OBJECT_BASED_READERS.include?(method_name) ||
94
+ current_value&.respond_to?(method_name, include_private) ||
95
+ super
96
+ end
97
+
98
+ # If the +method_name+ matches the kind of the current +object+, then
99
+ # it will return the object
100
+ def method_missing(method_name, *args, **xargs, &block)
101
+ if OBJECT_BASED_READERS.include?(method_name)
102
+ object if object.kind === method_name
103
+ elsif current_value&.respond_to?(method_name)
104
+ current_value&.public_send(method_name, *args, **xargs, &block)
105
+ else
106
+ super
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails # :nodoc:
4
+ module GraphQL # :nodoc:
5
+ class Request # :nodoc:
6
+ # Helper module to collect the directives from fragments, operations, and
7
+ # fields.
8
+ module Directives
9
+ # Get the list of listeners from all directives
10
+ def all_listeners
11
+ directives.map(&:all_listeners).reduce(:+) || Set.new
12
+ end
13
+
14
+ # Get the list of events from all directives and caches it by request
15
+ def all_events
16
+ @all_events ||= directives.map(&:all_events).inject({}) do |lhash, rhash|
17
+ Helpers.merge_hash_array(lhash, rhash)
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ # Make sure to always return a set
24
+ def directives
25
+ @directives || Set.new
26
+ end
27
+
28
+ alias all_directives directives
29
+
30
+ # Helper parser for directives that also collect necessary variables
31
+ def parse_directives(location = nil)
32
+ list = []
33
+
34
+ visitor.collect_directives(*data[:directives]) do |data|
35
+ instance = find_directive!(data[:name])
36
+
37
+ args = directive_arguments(instance)
38
+ args = collect_arguments(args, data[:arguments]) do |errors|
39
+ "Invalid arguments for @#{instance.gql_name} directive" \
40
+ " added to #{gql_name} #{kind}: #{errors}."
41
+ end
42
+
43
+ list << instance.new(request.build(Request::Arguments, args))
44
+ end unless data[:directives].blank?
45
+
46
+ if list.present?
47
+ event = Event.new(:attach, strategy, self, phase: :execution)
48
+ list = GraphQL.directives_to_set(list, [], event, location: location || kind)
49
+ end
50
+
51
+ @directives = list.freeze
52
+ end
53
+
54
+ # Get and cache all the arguments for this given +directive+
55
+ def directive_arguments(directive)
56
+ request.cache(:arguments)[directive] ||= begin
57
+ result = directive.all_arguments
58
+ result.each_value.map(&:gql_name).zip(result.each_value).to_h
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails # :nodoc:
4
+ module GraphQL # :nodoc:
5
+ class Request # :nodoc:
6
+ # Helper module to collect the fields from fragments, operations, and also
7
+ # other fields.
8
+ module SelectionSet
9
+ attr_reader :selection
10
+
11
+ protected
12
+
13
+ # Helper parser for selection fields that also asssign the actual
14
+ # field defined under the schema structure
15
+ def parse_selection
16
+ @selection = {}
17
+ assigners = Hash.new { |h, k| h[k] = [] }
18
+
19
+ visitor.collect_fields(*data[:selection]) do |kind, node, data|
20
+ component = add_component(kind, node, data)
21
+ assigners[component.name] << component if component.assignable?
22
+ end unless data[:selection].nil? || data[:selection].null?
23
+
24
+ assing_fields!(assigners)
25
+ @selection.freeze
26
+ end
27
+
28
+ # Using +fields_source+, find the needed ones to be assigned to the
29
+ # current requested fields. As shown by benchmark, since the index is
30
+ # based on Symbols, the best way to find +gql_name+ based fields is
31
+ # through interation and search. Complexity O(n)
32
+ def assing_fields!(assigners)
33
+ pending = assigners.map(&:size).reduce(:+) || 0
34
+ return if pending.zero?
35
+
36
+ fields_source.each_value do |field|
37
+ next unless assigners.key?(field.gql_name)
38
+
39
+ items = assigners[field.gql_name]
40
+ items.each_with_object(field).each(&:assing_to)
41
+ break if (pending -= items.size) === 0
42
+ end
43
+ end
44
+
45
+ # Recursive operation that perform the organization step for the
46
+ # selection
47
+ def organize_fields
48
+ selection.each_value(&:organize!) if selection.any?
49
+ end
50
+
51
+ # Find all the fields that have a prepare step and execute them
52
+ def prepare_fields
53
+ return unless selection.any?
54
+ (strategy.listeners[:prepare] & selection.values).each(&:prepare!)
55
+ end
56
+
57
+ # Trigger the process of resolving the value of all the fields. Since
58
+ # complex object may or may not be inside an array, this helps to
59
+ # decide if a new stack should be started or not
60
+ def resolve_fields(object = nil)
61
+ return unless selection.any?
62
+
63
+ items = selection.each_value
64
+ items = items.each_with_object(object) unless object.nil?
65
+ iterator = object.nil? ? :resolve! : :resolve_with!
66
+
67
+ return items.each(&iterator) unless stacked_selection?
68
+ response.with_stack(gql_name) { items.each(&iterator) }
69
+ end
70
+
71
+ private
72
+
73
+ def add_component(kind, node, data)
74
+ item_name = data.try(:alias).presence || data[:name]
75
+
76
+ if kind === :spread
77
+ selection[selection.size] = request.build(Component::Spread, self, node, data)
78
+ elsif data[:name] === '__typename'
79
+ selection[item_name] ||= request.build(Component::Typename, self, node, data)
80
+ else
81
+ selection[item_name] ||= request.build(Component::Field, self, node, data)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails # :nodoc:
4
+ module GraphQL # :nodoc:
5
+ class Request # :nodoc:
6
+ # A set of helper methods to write a value to the response
7
+ module ValueWriters
8
+ # TODO: Maybe move this to a setting so it allow extensions
9
+ KIND_WRITERS = {
10
+ union: 'write_union',
11
+ interface: 'write_interface',
12
+ object: 'write_object',
13
+ }.freeze
14
+
15
+ # Write a value to the response
16
+ def write_value(value)
17
+ return write_leaf(value) if value.nil?
18
+ send(KIND_WRITERS[field.kind] || 'write_leaf', value)
19
+ end
20
+
21
+ # Resolve a given value when it is an array
22
+ def write_array(value, idx = -1, &block)
23
+ write_array!(value) do |item|
24
+ stacked(idx += 1) do
25
+ block.call(item, idx)
26
+ response.next
27
+ rescue StandardError => error
28
+ raise if item.nil?
29
+
30
+ block.call(nil, idx)
31
+ response.next
32
+
33
+ format_array_execption(error, idx)
34
+ request.exception_to_error(error, @node)
35
+ end
36
+ rescue StandardError => error
37
+ format_array_execption(error, idx)
38
+ raise
39
+ end
40
+ end
41
+
42
+ # Helper to start writing as array
43
+ def write_array!(value, &block)
44
+ raise InvalidValueError, <<~MSG.squish unless value.respond_to?(:each)
45
+ The #{gql_name} field is excepting an array
46
+ but got an "#{value.class.name}" instead.
47
+ MSG
48
+
49
+ @writing_array = true
50
+ response.with_stack(field.gql_name, array: true, plain: leaf_type?) do
51
+ value.each(&block)
52
+ end
53
+ ensure
54
+ @writing_array = nil
55
+ end
56
+
57
+ # Add the item index to the exception message
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"
62
+
63
+ message = error.message.gsub(source_error, real_error)
64
+ error.define_singleton_method(:message) { message }
65
+ end
66
+
67
+ protected
68
+
69
+ # Write a value based on a Union type
70
+ def write_union(value)
71
+ object = type_klass.all_members.reverse_each.find { |t| t.valid_member?(value) }
72
+ object.nil? ? raise_invalid_member! : resolve_fields(object)
73
+ end
74
+
75
+ # Write a value based on a Interface type
76
+ def write_interface(value)
77
+ object = type_klass.all_types.reverse_each.find { |t| t.valid_member?(value) }
78
+ object.nil? ? raise_invalid_member! : resolve_fields(object)
79
+ end
80
+
81
+ # Write a value based on a Object type
82
+ def write_object(value)
83
+ type_klass.valid_member?(value) ? resolve_fields : raise_invalid_member!
84
+ end
85
+
86
+ # Write a value with the correct serialize mode. Validate the output
87
+ # but do not use array mode because this method will be called
88
+ # multiple times inside of an array.
89
+ def write_leaf(value)
90
+ validate_output!(value)
91
+ return response.safe_add(gql_name, nil) if value.nil?
92
+
93
+ # Necessary call #itself to loose the dynamic reference
94
+ response.serialize(type_klass, gql_name, value.itself)
95
+ end
96
+
97
+ # Trigger the plain field output validation
98
+ def validate_output!(value)
99
+ checker = defined?(@writing_array) && @writing_array ? :nullable? : :null?
100
+ field&.validate_output!(value, checker: checker, array: false)
101
+ end
102
+
103
+ private
104
+
105
+ # A problem when an object-based value is not a valid member of the
106
+ # +type_klass+ of this field
107
+ def raise_invalid_member!
108
+ raise(FieldError, <<~MSG.squish)
109
+ The #{gql_name} field result is not a member of #{type_klass.gql_name}.
110
+ MSG
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails # :nodoc:
4
+ module GraphQL # :nodoc:
5
+ class Request # :nodoc:
6
+ # Helper methods for the organize step of a request
7
+ module Organizable
8
+ # Check if it is already organized
9
+ def organized?
10
+ data.nil?
11
+ end
12
+
13
+ # Organize the object if it is not already organized
14
+ def organize!
15
+ capture_exception(:organize, true) { organize unless organized? }
16
+ end
17
+
18
+ protected
19
+
20
+ # Normally, fields come from the +type_klass+
21
+ def fields_source
22
+ type_klass.fields
23
+ end
24
+
25
+ # Normal mode of the organize step
26
+ def organize
27
+ organize_then { organize_fields }
28
+ end
29
+
30
+ # The actual process that organizes the object
31
+ def organize_then(after_block, &block)
32
+ stacked do
33
+ block.call
34
+ strategy.add_listener(self)
35
+ trigger_event(:organized)
36
+ after_block.call if after_block.present?
37
+ end
38
+ ensure
39
+ @data = nil
40
+ end
41
+
42
+ # Helper parser for request arguments (operation variables) that
43
+ # collect necessary arguments from the request
44
+ def parse_variables
45
+ @arguments = {}
46
+
47
+ visitor.collect_variables(*data[:variables]) do |data, node|
48
+ arg_name = data[:name]
49
+ raise ExecutionError, <<~MSG.squish if arguments.key?(arg_name)
50
+ The "#{arg_name}" argument is already defined for this #{kind}.
51
+ MSG
52
+
53
+ extra = data.to_h.except(:name, :type).merge(owner: schema)
54
+ item = arguments[arg_name] = Argument.new(arg_name, data[:type], **extra)
55
+ item.node = node
56
+ 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
+ end
63
+
64
+ @variables = args.freeze
65
+ @arguments.freeze
66
+ end
67
+
68
+ # 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}."
78
+ end
79
+
80
+ @arguments = request.build(Request::Arguments, args).freeze
81
+ end
82
+
83
+ # Build a hash that collect validated values for a set of arguments.
84
+ # The +source+ can either be the list of arguments or an object that
85
+ # responds to +all_arguments+. The +block+ is called when something
86
+ # goes wrong to collect a formatted message.
87
+ def collect_arguments(source, values, var_access: true, &block)
88
+ op_vars = nil
89
+
90
+ errors = []
91
+ source = source.all_arguments if source.respond_to?(:all_arguments)
92
+ result = source.each_pair.each_with_object({}) do |(key, argument), hash|
93
+ next unless values.key?(key)
94
+ value = values[key]
95
+
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
100
+ Unable to use variable "$#{var_name}" in the current scope
101
+ MSG
102
+
103
+ op_vars ||= operation.all_arguments
104
+ raise ArgumentError, <<~MSG.squish unless (op_var = op_vars[var_name]).present?
105
+ The #{operation.log_source} does not define the $#{var_name} variable
106
+ MSG
107
+
108
+ # When arguments are not equivalent, they can ended up with
109
+ # invalid values, so this already ensures that whatever the
110
+ # variable value ended up being, it will be valid due to this
111
+ raise ArgumentError, <<~MSG.squish unless op_var =~ argument
112
+ The $#{var_name} variable on #{operation.log_source} is not compatible
113
+ with "#{key}" argument
114
+ MSG
115
+
116
+ operation.used_variables << var_name
117
+ next unless variables.key?(op_var.name)
118
+ value = variables[op_var.name]
119
+ else
120
+ # Only when the given value is an actual value that we check if
121
+ # it is valid
122
+ raise ArgumentError, <<~MSG.squish unless argument.valid?(value)
123
+ Invalid value provided to "#{key}" argument
124
+ MSG
125
+
126
+ value = argument.deserialize(value)
127
+ end
128
+
129
+ hash[argument.name] = value
130
+ rescue ArgumentError => error
131
+ errors << error.message
132
+ end
133
+
134
+ # Checks for any required arugment that was not provided
135
+ source.each_value do |argument|
136
+ next if result.key?(argument.name) || argument.null?
137
+ errors << "The \"#{argument.gql_name}\" argument can not be null"
138
+ end
139
+
140
+ return result if errors.blank?
141
+ raise ArgumentError, block.call(errors.to_sentence)
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end