rails-graphql 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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