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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +31 -0
- data/ext/depend +3 -0
- data/ext/extconf.rb +57 -0
- data/ext/graphqlparser/Ast.cpp +346 -0
- data/ext/graphqlparser/Ast.h +1214 -0
- data/ext/graphqlparser/AstNode.h +36 -0
- data/ext/graphqlparser/AstVisitor.h +137 -0
- data/ext/graphqlparser/GraphQLParser.cpp +76 -0
- data/ext/graphqlparser/GraphQLParser.h +55 -0
- data/ext/graphqlparser/JsonVisitor.cpp +161 -0
- data/ext/graphqlparser/JsonVisitor.cpp.inc +456 -0
- data/ext/graphqlparser/JsonVisitor.h +121 -0
- data/ext/graphqlparser/JsonVisitor.h.inc +110 -0
- data/ext/graphqlparser/VERSION +1 -0
- data/ext/graphqlparser/c/GraphQLAst.cpp +324 -0
- data/ext/graphqlparser/c/GraphQLAst.h +180 -0
- data/ext/graphqlparser/c/GraphQLAstForEachConcreteType.h +44 -0
- data/ext/graphqlparser/c/GraphQLAstNode.cpp +25 -0
- data/ext/graphqlparser/c/GraphQLAstNode.h +33 -0
- data/ext/graphqlparser/c/GraphQLAstToJSON.cpp +21 -0
- data/ext/graphqlparser/c/GraphQLAstToJSON.h +24 -0
- data/ext/graphqlparser/c/GraphQLAstVisitor.cpp +55 -0
- data/ext/graphqlparser/c/GraphQLAstVisitor.h +53 -0
- data/ext/graphqlparser/c/GraphQLParser.cpp +35 -0
- data/ext/graphqlparser/c/GraphQLParser.h +54 -0
- data/ext/graphqlparser/dump_json_ast.cpp +48 -0
- data/ext/graphqlparser/lexer.lpp +324 -0
- data/ext/graphqlparser/parser.ypp +693 -0
- data/ext/graphqlparser/parsergen/lexer.cpp +2633 -0
- data/ext/graphqlparser/parsergen/lexer.h +528 -0
- data/ext/graphqlparser/parsergen/location.hh +189 -0
- data/ext/graphqlparser/parsergen/parser.tab.cpp +3300 -0
- data/ext/graphqlparser/parsergen/parser.tab.hpp +646 -0
- data/ext/graphqlparser/parsergen/position.hh +179 -0
- data/ext/graphqlparser/parsergen/stack.hh +156 -0
- data/ext/graphqlparser/syntaxdefs.h +19 -0
- data/ext/libgraphqlparser/AstNode.h +36 -0
- data/ext/libgraphqlparser/CMakeLists.txt +148 -0
- data/ext/libgraphqlparser/CONTRIBUTING.md +23 -0
- data/ext/libgraphqlparser/GraphQLParser.cpp +76 -0
- data/ext/libgraphqlparser/GraphQLParser.h +55 -0
- data/ext/libgraphqlparser/JsonVisitor.cpp +161 -0
- data/ext/libgraphqlparser/JsonVisitor.h +121 -0
- data/ext/libgraphqlparser/LICENSE +22 -0
- data/ext/libgraphqlparser/README.clang-tidy +7 -0
- data/ext/libgraphqlparser/README.md +84 -0
- data/ext/libgraphqlparser/ast/ast.ast +203 -0
- data/ext/libgraphqlparser/ast/ast.py +61 -0
- data/ext/libgraphqlparser/ast/c.py +100 -0
- data/ext/libgraphqlparser/ast/c.pyc +0 -0
- data/ext/libgraphqlparser/ast/c_impl.py +61 -0
- data/ext/libgraphqlparser/ast/c_impl.pyc +0 -0
- data/ext/libgraphqlparser/ast/c_visitor_impl.py +39 -0
- data/ext/libgraphqlparser/ast/c_visitor_impl.pyc +0 -0
- data/ext/libgraphqlparser/ast/casing.py +26 -0
- data/ext/libgraphqlparser/ast/casing.pyc +0 -0
- data/ext/libgraphqlparser/ast/cxx.py +197 -0
- data/ext/libgraphqlparser/ast/cxx.pyc +0 -0
- data/ext/libgraphqlparser/ast/cxx_impl.py +61 -0
- data/ext/libgraphqlparser/ast/cxx_impl.pyc +0 -0
- data/ext/libgraphqlparser/ast/cxx_json_visitor_header.py +42 -0
- data/ext/libgraphqlparser/ast/cxx_json_visitor_header.pyc +0 -0
- data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.py +80 -0
- data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.pyc +0 -0
- data/ext/libgraphqlparser/ast/cxx_visitor.py +64 -0
- data/ext/libgraphqlparser/ast/cxx_visitor.pyc +0 -0
- data/ext/libgraphqlparser/ast/js.py +65 -0
- data/ext/libgraphqlparser/ast/license.py +10 -0
- data/ext/libgraphqlparser/ast/license.pyc +0 -0
- data/ext/libgraphqlparser/c/GraphQLAstNode.cpp +25 -0
- data/ext/libgraphqlparser/c/GraphQLAstNode.h +33 -0
- data/ext/libgraphqlparser/c/GraphQLAstToJSON.cpp +21 -0
- data/ext/libgraphqlparser/c/GraphQLAstToJSON.h +24 -0
- data/ext/libgraphqlparser/c/GraphQLAstVisitor.cpp +55 -0
- data/ext/libgraphqlparser/c/GraphQLAstVisitor.h +53 -0
- data/ext/libgraphqlparser/c/GraphQLParser.cpp +35 -0
- data/ext/libgraphqlparser/c/GraphQLParser.h +54 -0
- data/ext/libgraphqlparser/clang-tidy-all.sh +3 -0
- data/ext/libgraphqlparser/cmake/version.cmake +16 -0
- data/ext/libgraphqlparser/dump_json_ast.cpp +48 -0
- data/ext/libgraphqlparser/go/README.md +20 -0
- data/ext/libgraphqlparser/go/callbacks.go +18 -0
- data/ext/libgraphqlparser/go/gotest.go +64 -0
- data/ext/libgraphqlparser/lexer.lpp +324 -0
- data/ext/libgraphqlparser/libgraphqlparser.pc.in +11 -0
- data/ext/libgraphqlparser/parser.ypp +693 -0
- data/ext/libgraphqlparser/parsergen/lexer.cpp +2633 -0
- data/ext/libgraphqlparser/parsergen/lexer.h +528 -0
- data/ext/libgraphqlparser/parsergen/location.hh +189 -0
- data/ext/libgraphqlparser/parsergen/parser.tab.cpp +3300 -0
- data/ext/libgraphqlparser/parsergen/parser.tab.hpp +646 -0
- data/ext/libgraphqlparser/parsergen/position.hh +179 -0
- data/ext/libgraphqlparser/parsergen/stack.hh +156 -0
- data/ext/libgraphqlparser/python/CMakeLists.txt +14 -0
- data/ext/libgraphqlparser/python/README.md +5 -0
- data/ext/libgraphqlparser/python/example.py +31 -0
- data/ext/libgraphqlparser/syntaxdefs.h +19 -0
- data/ext/libgraphqlparser/test/BuildCAPI.c +5 -0
- data/ext/libgraphqlparser/test/CMakeLists.txt +25 -0
- data/ext/libgraphqlparser/test/JsonVisitorTests.cpp +28 -0
- data/ext/libgraphqlparser/test/ParserTests.cpp +352 -0
- data/ext/libgraphqlparser/test/kitchen-sink.graphql +59 -0
- data/ext/libgraphqlparser/test/kitchen-sink.json +1 -0
- data/ext/libgraphqlparser/test/schema-kitchen-sink.graphql +78 -0
- data/ext/libgraphqlparser/test/schema-kitchen-sink.json +1 -0
- data/ext/libgraphqlparser/test/valgrind.supp +33 -0
- data/ext/version.cpp +21 -0
- data/lib/generators/graphql/controller_generator.rb +22 -0
- data/lib/generators/graphql/schema_generator.rb +22 -0
- data/lib/generators/graphql/templates/controller.erb +5 -0
- data/lib/generators/graphql/templates/schema.erb +6 -0
- data/lib/graphqlparser.so +0 -0
- data/lib/rails-graphql.rb +2 -0
- data/lib/rails/graphql.rake +1 -0
- data/lib/rails/graphql.rb +185 -0
- data/lib/rails/graphql/adapters/mysql_adapter.rb +0 -0
- data/lib/rails/graphql/adapters/pg_adapter.rb +50 -0
- data/lib/rails/graphql/adapters/sqlite_adapter.rb +39 -0
- data/lib/rails/graphql/argument.rb +220 -0
- data/lib/rails/graphql/callback.rb +124 -0
- data/lib/rails/graphql/collectors.rb +14 -0
- data/lib/rails/graphql/collectors/hash_collector.rb +83 -0
- data/lib/rails/graphql/collectors/idented_collector.rb +73 -0
- data/lib/rails/graphql/collectors/json_collector.rb +114 -0
- data/lib/rails/graphql/config.rb +61 -0
- data/lib/rails/graphql/directive.rb +203 -0
- data/lib/rails/graphql/directive/deprecated_directive.rb +59 -0
- data/lib/rails/graphql/directive/include_directive.rb +24 -0
- data/lib/rails/graphql/directive/skip_directive.rb +24 -0
- data/lib/rails/graphql/errors.rb +42 -0
- data/lib/rails/graphql/event.rb +141 -0
- data/lib/rails/graphql/field.rb +318 -0
- data/lib/rails/graphql/field/input_field.rb +92 -0
- data/lib/rails/graphql/field/mutation_field.rb +52 -0
- data/lib/rails/graphql/field/output_field.rb +96 -0
- data/lib/rails/graphql/field/proxied_field.rb +131 -0
- data/lib/rails/graphql/field/resolved_field.rb +96 -0
- data/lib/rails/graphql/field/scoped_config.rb +22 -0
- data/lib/rails/graphql/field/typed_field.rb +104 -0
- data/lib/rails/graphql/helpers.rb +40 -0
- data/lib/rails/graphql/helpers/attribute_delegator.rb +39 -0
- data/lib/rails/graphql/helpers/inherited_collection.rb +152 -0
- data/lib/rails/graphql/helpers/leaf_from_ar.rb +141 -0
- data/lib/rails/graphql/helpers/registerable.rb +103 -0
- data/lib/rails/graphql/helpers/with_arguments.rb +125 -0
- data/lib/rails/graphql/helpers/with_assignment.rb +113 -0
- data/lib/rails/graphql/helpers/with_callbacks.rb +55 -0
- data/lib/rails/graphql/helpers/with_directives.rb +126 -0
- data/lib/rails/graphql/helpers/with_events.rb +81 -0
- data/lib/rails/graphql/helpers/with_fields.rb +141 -0
- data/lib/rails/graphql/helpers/with_namespace.rb +40 -0
- data/lib/rails/graphql/helpers/with_owner.rb +35 -0
- data/lib/rails/graphql/helpers/with_schema_fields.rb +230 -0
- data/lib/rails/graphql/helpers/with_validator.rb +52 -0
- data/lib/rails/graphql/introspection.rb +53 -0
- data/lib/rails/graphql/native.rb +56 -0
- data/lib/rails/graphql/native/functions.rb +38 -0
- data/lib/rails/graphql/native/location.rb +41 -0
- data/lib/rails/graphql/native/pointers.rb +23 -0
- data/lib/rails/graphql/native/visitor.rb +349 -0
- data/lib/rails/graphql/railtie.rb +85 -0
- data/lib/rails/graphql/railties/base_generator.rb +35 -0
- data/lib/rails/graphql/railties/controller.rb +101 -0
- data/lib/rails/graphql/railties/controller_runtime.rb +40 -0
- data/lib/rails/graphql/railties/log_subscriber.rb +62 -0
- data/lib/rails/graphql/request.rb +343 -0
- data/lib/rails/graphql/request/arguments.rb +93 -0
- data/lib/rails/graphql/request/component.rb +100 -0
- data/lib/rails/graphql/request/component/field.rb +225 -0
- data/lib/rails/graphql/request/component/fragment.rb +118 -0
- data/lib/rails/graphql/request/component/operation.rb +178 -0
- data/lib/rails/graphql/request/component/operation/subscription.rb +16 -0
- data/lib/rails/graphql/request/component/spread.rb +119 -0
- data/lib/rails/graphql/request/component/typename.rb +82 -0
- data/lib/rails/graphql/request/context.rb +51 -0
- data/lib/rails/graphql/request/errors.rb +54 -0
- data/lib/rails/graphql/request/event.rb +112 -0
- data/lib/rails/graphql/request/helpers/directives.rb +64 -0
- data/lib/rails/graphql/request/helpers/selection_set.rb +87 -0
- data/lib/rails/graphql/request/helpers/value_writers.rb +115 -0
- data/lib/rails/graphql/request/steps/organizable.rb +146 -0
- data/lib/rails/graphql/request/steps/prepareable.rb +33 -0
- data/lib/rails/graphql/request/steps/resolveable.rb +32 -0
- data/lib/rails/graphql/request/strategy.rb +249 -0
- data/lib/rails/graphql/request/strategy/dynamic_instance.rb +41 -0
- data/lib/rails/graphql/request/strategy/multi_query_strategy.rb +36 -0
- data/lib/rails/graphql/request/strategy/sequenced_strategy.rb +28 -0
- data/lib/rails/graphql/schema.rb +272 -0
- data/lib/rails/graphql/shortcuts.rb +77 -0
- data/lib/rails/graphql/source.rb +371 -0
- data/lib/rails/graphql/source/active_record/builders.rb +154 -0
- data/lib/rails/graphql/source/active_record_source.rb +231 -0
- data/lib/rails/graphql/source/scoped_arguments.rb +87 -0
- data/lib/rails/graphql/to_gql.rb +368 -0
- data/lib/rails/graphql/type.rb +138 -0
- data/lib/rails/graphql/type/enum.rb +206 -0
- data/lib/rails/graphql/type/enum/directive_location_enum.rb +30 -0
- data/lib/rails/graphql/type/enum/type_kind_enum.rb +57 -0
- data/lib/rails/graphql/type/input.rb +134 -0
- data/lib/rails/graphql/type/interface.rb +82 -0
- data/lib/rails/graphql/type/object.rb +111 -0
- data/lib/rails/graphql/type/object/directive_object.rb +34 -0
- data/lib/rails/graphql/type/object/enum_value_object.rb +25 -0
- data/lib/rails/graphql/type/object/field_object.rb +54 -0
- data/lib/rails/graphql/type/object/input_value_object.rb +49 -0
- data/lib/rails/graphql/type/object/schema_object.rb +40 -0
- data/lib/rails/graphql/type/object/type_object.rb +136 -0
- data/lib/rails/graphql/type/scalar.rb +71 -0
- data/lib/rails/graphql/type/scalar/bigint_scalar.rb +34 -0
- data/lib/rails/graphql/type/scalar/binary_scalar.rb +30 -0
- data/lib/rails/graphql/type/scalar/boolean_scalar.rb +37 -0
- data/lib/rails/graphql/type/scalar/date_scalar.rb +34 -0
- data/lib/rails/graphql/type/scalar/date_time_scalar.rb +32 -0
- data/lib/rails/graphql/type/scalar/decimal_scalar.rb +35 -0
- data/lib/rails/graphql/type/scalar/float_scalar.rb +32 -0
- data/lib/rails/graphql/type/scalar/id_scalar.rb +39 -0
- data/lib/rails/graphql/type/scalar/int_scalar.rb +36 -0
- data/lib/rails/graphql/type/scalar/string_scalar.rb +28 -0
- data/lib/rails/graphql/type/scalar/time_scalar.rb +40 -0
- data/lib/rails/graphql/type/union.rb +87 -0
- data/lib/rails/graphql/type_map.rb +347 -0
- data/lib/rails/graphql/version.rb +7 -0
- data/test/assets/introspection-db.json +0 -0
- data/test/assets/introspection-mem.txt +1 -0
- data/test/assets/introspection.gql +91 -0
- data/test/assets/luke.jpg +0 -0
- data/test/assets/mem.gql +428 -0
- data/test/assets/sqlite.gql +423 -0
- data/test/config.rb +80 -0
- data/test/graphql/request/context_test.rb +70 -0
- data/test/graphql/schema_test.rb +190 -0
- data/test/graphql/source_test.rb +237 -0
- data/test/graphql/type/enum_test.rb +203 -0
- data/test/graphql/type/input_test.rb +138 -0
- data/test/graphql/type/interface_test.rb +72 -0
- data/test/graphql/type/object_test.rb +104 -0
- data/test/graphql/type/scalar/bigint_scalar_test.rb +42 -0
- data/test/graphql/type/scalar/binary_scalar_test.rb +17 -0
- data/test/graphql/type/scalar/boolean_scalar_test.rb +40 -0
- data/test/graphql/type/scalar/date_scalar_test.rb +29 -0
- data/test/graphql/type/scalar/date_time_scalar_test.rb +29 -0
- data/test/graphql/type/scalar/decimal_scalar_test.rb +28 -0
- data/test/graphql/type/scalar/float_scalar_test.rb +22 -0
- data/test/graphql/type/scalar/id_scalar_test.rb +26 -0
- data/test/graphql/type/scalar/int_scalar_test.rb +26 -0
- data/test/graphql/type/scalar/string_scalar_test.rb +17 -0
- data/test/graphql/type/scalar/time_scalar_test.rb +36 -0
- data/test/graphql/type/scalar_test.rb +45 -0
- data/test/graphql/type/union_test.rb +82 -0
- data/test/graphql/type_map_test.rb +362 -0
- data/test/graphql/type_test.rb +68 -0
- data/test/graphql_test.rb +55 -0
- data/test/integration/config.rb +56 -0
- data/test/integration/memory/star_wars_introspection_test.rb +144 -0
- data/test/integration/memory/star_wars_query_test.rb +184 -0
- data/test/integration/memory/star_wars_validation_test.rb +99 -0
- data/test/integration/schemas/memory.rb +232 -0
- data/test/integration/schemas/sqlite.rb +82 -0
- data/test/integration/sqlite/star_wars_introspection_test.rb +15 -0
- data/test/integration/sqlite/star_wars_mutation_test.rb +82 -0
- data/test/integration/sqlite/star_wars_query_test.rb +71 -0
- data/test/test_ext.rb +48 -0
- metadata +509 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
class Request # :nodoc:
|
6
|
+
# = GraphQL Request Arguments
|
7
|
+
#
|
8
|
+
# This is an extension of an +OpenStruct+ since argument values can be
|
9
|
+
# assigned a Proc, which means that in order to collect their value, we
|
10
|
+
# need to rely on the current operation being processed.
|
11
|
+
#
|
12
|
+
# They lazy variable-based value is used for fragments, so that they can
|
13
|
+
# be organized only once and have their variables changed accordingly to
|
14
|
+
# the spread and operation.
|
15
|
+
class Arguments < OpenStruct
|
16
|
+
THREAD_KEY = :_rails_graphql_operation
|
17
|
+
LAZY_LOADER = ->(key, object) { object.variables[key] }.curry
|
18
|
+
|
19
|
+
delegate :key?, to: :@table
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# Easy access to the easy loader method
|
23
|
+
def lazy
|
24
|
+
LAZY_LOADER
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get the current operation thread safely
|
28
|
+
def operation
|
29
|
+
Thread.current[THREAD_KEY]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Execute a block inside a scoped thread-safe arguments
|
33
|
+
def scoped(value)
|
34
|
+
old_value, Thread.current[THREAD_KEY] = operation, value
|
35
|
+
|
36
|
+
yield
|
37
|
+
ensure
|
38
|
+
Thread.current[THREAD_KEY] = old_value
|
39
|
+
end
|
40
|
+
|
41
|
+
# Check if it's performing inside a scoped value
|
42
|
+
def scoped?
|
43
|
+
operation.present?
|
44
|
+
end
|
45
|
+
|
46
|
+
# If it's running under a scope, transform proc based values
|
47
|
+
def transform(value)
|
48
|
+
return if value.nil?
|
49
|
+
scoped? && value.is_a?(Proc) ? value.call(operation) : value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Transform any proc by its actual value before returning the hash
|
54
|
+
def to_h(*)
|
55
|
+
super.transform_values(&self.class.method(:transform))
|
56
|
+
end
|
57
|
+
|
58
|
+
# Before iterating, transform any needed value
|
59
|
+
def each_pair
|
60
|
+
enum = to_h.to_enum
|
61
|
+
return enum unless block_given?
|
62
|
+
enum.each { |v| yield v }
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
# rubocop:disable Style/MissingRespondToMissing
|
67
|
+
# Transform the value before returning
|
68
|
+
def method_missing(*)
|
69
|
+
self.class.transform(super)
|
70
|
+
end
|
71
|
+
# rubocop:enable Style/MissingRespondToMissing
|
72
|
+
|
73
|
+
# Transform the value before returning
|
74
|
+
def [](*)
|
75
|
+
self.class.transform(super)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Transform the value before returning
|
79
|
+
def dig(name, *names)
|
80
|
+
result = self.class.transform(super(name))
|
81
|
+
names.empty? ? result : result&.dig(*names)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Override the freeze method to just freeze the table and do not create
|
85
|
+
# the getters and setter methods
|
86
|
+
def freeze
|
87
|
+
@table.freeze
|
88
|
+
super
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
class Request # :nodoc:
|
6
|
+
# = GraphQL Request Component
|
7
|
+
#
|
8
|
+
# Component is an abstraction of any possible type of object represented
|
9
|
+
# by a not of the document of a request. This class helps building
|
10
|
+
# cross-component features, like holding event listeners, setting up
|
11
|
+
# commom initializer and providing helpers
|
12
|
+
class Component
|
13
|
+
extend ActiveSupport::Autoload
|
14
|
+
|
15
|
+
include Request::Organizable
|
16
|
+
include Request::Prepareable
|
17
|
+
include Request::Resolveable
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Return the kind of the component
|
21
|
+
def kind
|
22
|
+
@kind ||= name.demodulize.underscore.to_sym
|
23
|
+
end
|
24
|
+
|
25
|
+
# Helper to memoize results from parent delegation
|
26
|
+
def parent_memoize(*methods)
|
27
|
+
methods.each do |method_name|
|
28
|
+
define_method(method_name) do
|
29
|
+
result = parent.public_send(method_name)
|
30
|
+
define_singleton_method(method_name) { result }
|
31
|
+
result
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :data
|
38
|
+
|
39
|
+
delegate :schema, :visitor, :response, :strategy, to: :request
|
40
|
+
delegate :find_type!, :find_directive!, :trigger_event, to: :strategy
|
41
|
+
delegate :memo, to: :operation
|
42
|
+
delegate :kind, to: :class
|
43
|
+
|
44
|
+
alias of_type? is_a?
|
45
|
+
|
46
|
+
eager_autoload do
|
47
|
+
autoload :Field
|
48
|
+
autoload :Fragment
|
49
|
+
autoload :Operation
|
50
|
+
autoload :Spread
|
51
|
+
autoload :Typename
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(node, data)
|
55
|
+
@node = node
|
56
|
+
@data = data
|
57
|
+
end
|
58
|
+
|
59
|
+
# Check if the component is in a invalid state
|
60
|
+
def invalid?
|
61
|
+
defined?(@invalid) && @invalid
|
62
|
+
end
|
63
|
+
|
64
|
+
# Mark the component as invalid
|
65
|
+
def invalidate!
|
66
|
+
@invalid = true
|
67
|
+
end
|
68
|
+
|
69
|
+
# Normaly, components are not assignable, only fields are
|
70
|
+
def assignable?
|
71
|
+
false
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
# It's extremely important to have a way to access the current request
|
77
|
+
# since not all objects stores s direct pointer to it
|
78
|
+
def request
|
79
|
+
raise NotImplementedError
|
80
|
+
end
|
81
|
+
|
82
|
+
# Use the strategy to set the component into the stack
|
83
|
+
def stacked(value = self, &block)
|
84
|
+
strategy.stacked(value, &block)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Run a given block and ensure to capture exceptions to set them as
|
88
|
+
# errors
|
89
|
+
def capture_exception(stage, invalidate = false)
|
90
|
+
yield
|
91
|
+
rescue StandardError => error
|
92
|
+
invalidate! if invalidate
|
93
|
+
stack_path = request.stack_to_path
|
94
|
+
stack_path << gql_name if respond_to?(:gql_name) && gql_name.present?
|
95
|
+
request.exception_to_error(error, @node, path: stack_path, stage: stage.to_s)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
class Request # :nodoc:
|
6
|
+
# = GraphQL Request Component Field
|
7
|
+
#
|
8
|
+
# This class holds information about a given field that should be
|
9
|
+
# collected from the source of where it was requested.
|
10
|
+
class Component::Field < Component
|
11
|
+
include ValueWriters
|
12
|
+
include SelectionSet
|
13
|
+
include Directives
|
14
|
+
|
15
|
+
delegate :decorate, to: :type_klass
|
16
|
+
delegate :operation, :variables, to: :parent
|
17
|
+
delegate :method_name, :resolver, :performer, :type_klass, :leaf_type?,
|
18
|
+
:dynamic_resolver?, to: :field
|
19
|
+
|
20
|
+
parent_memoize :request
|
21
|
+
|
22
|
+
attr_reader :name, :alias_name, :parent, :field, :arguments, :current_object
|
23
|
+
|
24
|
+
alias args arguments
|
25
|
+
|
26
|
+
def initialize(parent, node, data)
|
27
|
+
@parent = parent
|
28
|
+
|
29
|
+
@name = data[:name]
|
30
|
+
@alias_name = data[:alias]
|
31
|
+
|
32
|
+
super(node, data)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return both the field directives and the request directives
|
36
|
+
def all_directives
|
37
|
+
field.all_directives + super
|
38
|
+
end
|
39
|
+
|
40
|
+
# Override that considers the requested field directives and also the
|
41
|
+
# definition field events, both from itself and its directives events
|
42
|
+
def all_listeners
|
43
|
+
field.all_listeners + super
|
44
|
+
end
|
45
|
+
|
46
|
+
# Override that considers the requested field directives and also the
|
47
|
+
# definition field events, both from itself and its directives events
|
48
|
+
def all_events
|
49
|
+
@all_events ||= Helpers.merge_hash_array(field.all_events, super)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get and cache all the arguments for the field
|
53
|
+
def all_arguments
|
54
|
+
request.cache(:arguments)[field] ||= begin
|
55
|
+
if (result = field.all_arguments).any?
|
56
|
+
result.each_value.map(&:gql_name).zip(result.each_value).to_h
|
57
|
+
else
|
58
|
+
{}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Assign a given +field+ to this class. The field must be an output
|
64
|
+
# field, which means that +output_type?+ must be true. It also must be
|
65
|
+
# called exactly once per field.
|
66
|
+
def assing_to(field)
|
67
|
+
raise ArgumentError, <<~MSG.squish if defined?(@assigned)
|
68
|
+
The "#{gql_name}" field is already assigned to #{@field.inspect}.
|
69
|
+
MSG
|
70
|
+
|
71
|
+
@field = field
|
72
|
+
end
|
73
|
+
|
74
|
+
# Return the name of the field to be used on the response
|
75
|
+
def gql_name
|
76
|
+
alias_name || name
|
77
|
+
end
|
78
|
+
|
79
|
+
# A little helper for finding the correct parent type name
|
80
|
+
def typename
|
81
|
+
(try(:current_object) || try(:type_klass))&.gql_name
|
82
|
+
end
|
83
|
+
|
84
|
+
# Check if the field is an entry point, meaning that its parent is the
|
85
|
+
# operation and it is associated to a schema field
|
86
|
+
def entry_point?
|
87
|
+
parent.kind === :operation
|
88
|
+
end
|
89
|
+
|
90
|
+
# Fields are assignable because they are actually the selection, so they
|
91
|
+
# need to be assigned to a filed
|
92
|
+
def assignable?
|
93
|
+
true
|
94
|
+
end
|
95
|
+
|
96
|
+
# A little extension of the +is_a?+ method that allows checking it using
|
97
|
+
# the underlying +field+
|
98
|
+
def of_type?(klass)
|
99
|
+
super || field.of_type?(klass)
|
100
|
+
end
|
101
|
+
|
102
|
+
# When the field is invalid, there's no much to do
|
103
|
+
# TODO: Maybe add a invalid event trigger here
|
104
|
+
def resolve_invalid(error = nil)
|
105
|
+
request.exception_to_error(error, @node) if error.present?
|
106
|
+
|
107
|
+
validate_output!(nil)
|
108
|
+
response.safe_add(gql_name, nil)
|
109
|
+
rescue InvalidValueError
|
110
|
+
raise unless entry_point?
|
111
|
+
end
|
112
|
+
|
113
|
+
# When the +type_klass+ of an object is an interface or a union, the
|
114
|
+
# field needs to be redirected to the one from the actual resolved
|
115
|
+
# +object+ type
|
116
|
+
def resolve_with!(object)
|
117
|
+
return resolve! if invalid?
|
118
|
+
|
119
|
+
old_field, @field = @field, object[@field.name]
|
120
|
+
@current_object = object
|
121
|
+
resolve!
|
122
|
+
ensure
|
123
|
+
@field, @current_object = old_field, nil
|
124
|
+
end
|
125
|
+
|
126
|
+
protected
|
127
|
+
|
128
|
+
# Perform the organization step
|
129
|
+
def organize_then(&block)
|
130
|
+
super(block) do
|
131
|
+
check_assignment!
|
132
|
+
|
133
|
+
parse_arguments
|
134
|
+
parse_directives
|
135
|
+
parse_selection
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Perform the prepare step
|
140
|
+
def prepare_then(&block)
|
141
|
+
super { strategy.prepare(self, &block) }
|
142
|
+
end
|
143
|
+
|
144
|
+
# Perform the resolve step
|
145
|
+
def resolve_then(&block)
|
146
|
+
stacked do
|
147
|
+
strategy.perform(self) if field.mutation?
|
148
|
+
send(field.array? ? 'resolve_many' : 'resolve_one', &block)
|
149
|
+
rescue StandardError => error
|
150
|
+
resolve_invalid(error)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Don't stack over response when it's processing as array
|
155
|
+
def stacked_selection?
|
156
|
+
!field.array?
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
# Resolve the value of the field for a single information
|
162
|
+
def resolve_one
|
163
|
+
strategy.resolve(self, decorate: true) do |value|
|
164
|
+
yield value if block_given?
|
165
|
+
trigger_event(:finalize)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Resolve the field for a list of information
|
170
|
+
def resolve_many
|
171
|
+
strategy.resolve(self, array: true) do |item|
|
172
|
+
strategy.resolve(self, item, decorate: true) do |value|
|
173
|
+
yield value if block_given?
|
174
|
+
trigger_event(:finalize)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# This override allows reasigned fields to perform events. This
|
180
|
+
# happens when fields are originally organized from interfaces. If
|
181
|
+
# the event is stopped for the object, then it doesn't proceed to the
|
182
|
+
# strategy implementation, ensuring compatibility
|
183
|
+
def trigger_event(event_name, **xargs)
|
184
|
+
return super if !defined?(@current_object) || @current_object.nil?
|
185
|
+
|
186
|
+
listeners = request.cache(:dynamic_listeners)[field] ||= field.all_listeners
|
187
|
+
return super unless listeners.include?(event_name)
|
188
|
+
|
189
|
+
callbacks = request.cache(:dynamic_events)[field] ||= field.all_events
|
190
|
+
old_events, @all_events = @all_events, callbacks
|
191
|
+
super
|
192
|
+
ensure
|
193
|
+
@all_events = old_events
|
194
|
+
end
|
195
|
+
|
196
|
+
# Check if the field was assigned correctly to an output field
|
197
|
+
def check_assignment!
|
198
|
+
raise MissingFieldError, <<~MSG.squish if field.nil?
|
199
|
+
Unable to find a field named "#{gql_name}" on
|
200
|
+
#{entry_point? ? operation.kind : parent.type_klass.name}.
|
201
|
+
MSG
|
202
|
+
|
203
|
+
raise FieldError, <<~MSG.squish unless field.output_type?
|
204
|
+
The "#{gql_name}" was assigned to a non-output type of field: #{field.inspect}.
|
205
|
+
MSG
|
206
|
+
|
207
|
+
empty_selection = data[:selection].nil? || data[:selection].null?
|
208
|
+
raise FieldError, <<~MSG.squish if field.leaf_type? && !empty_selection
|
209
|
+
The "#{gql_name}" was assigned to the #{type_klass.gql_name} which
|
210
|
+
is a leaf type and does not have nested fields.
|
211
|
+
MSG
|
212
|
+
|
213
|
+
raise FieldError, <<~MSG.squish if !field.leaf_type? && empty_selection
|
214
|
+
The "#{gql_name}" was assigned to the #{type_klass.gql_name} which
|
215
|
+
is not a leaf type and requires a selection of fields.
|
216
|
+
MSG
|
217
|
+
|
218
|
+
raise DisabledFieldError, <<~MSG.squish if field.disabled?
|
219
|
+
The "#{gql_name}" was found but it is marked as disabled.
|
220
|
+
MSG
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
class Request # :nodoc:
|
6
|
+
# = GraphQL Request Component Fragment
|
7
|
+
#
|
8
|
+
# This class holds information about a given fragment defined using the
|
9
|
+
# +fragment+ statement during an execution. This will guide the validation
|
10
|
+
# and execution of it.
|
11
|
+
class Component::Fragment < Component
|
12
|
+
include SelectionSet
|
13
|
+
include Directives
|
14
|
+
|
15
|
+
attr_reader :name, :type_klass, :request
|
16
|
+
|
17
|
+
def initialize(request, node, data)
|
18
|
+
@name = data[:name]
|
19
|
+
@request = request
|
20
|
+
|
21
|
+
super(node, data)
|
22
|
+
|
23
|
+
check_duplicated_fragment!
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return a lazy loaded variable proc
|
27
|
+
# TODO: Mark all the dependent variables
|
28
|
+
def variables
|
29
|
+
Request::Arguments.lazy
|
30
|
+
end
|
31
|
+
|
32
|
+
# Access the operation through the Request::Arguments
|
33
|
+
def operation
|
34
|
+
Request::Arguments.operation
|
35
|
+
end
|
36
|
+
|
37
|
+
# Spread should always be performed with a current object, thus the
|
38
|
+
# typename comes from it
|
39
|
+
def typename
|
40
|
+
@current_object.gql_name
|
41
|
+
end
|
42
|
+
|
43
|
+
# Only resolve if the +type_klass+ is equivalent to the given +object+
|
44
|
+
def resolve_with!(object)
|
45
|
+
return if invalid?
|
46
|
+
|
47
|
+
@current_object = object
|
48
|
+
resolve!
|
49
|
+
ensure
|
50
|
+
@current_object = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
# Fragments always resolve selection unstacked on response, meaning
|
56
|
+
# that its fields will be set in the same level as the parent
|
57
|
+
def stacked_selection?
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
# Perform the organization step
|
62
|
+
def organize_then(&block)
|
63
|
+
super(block) do
|
64
|
+
@type_klass = find_type!(data[:type])
|
65
|
+
parse_directives(:fragment_definition)
|
66
|
+
|
67
|
+
check_assignment!
|
68
|
+
parse_selection
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Resolve the spread operation
|
73
|
+
def resolve
|
74
|
+
return if invalid?
|
75
|
+
|
76
|
+
object = @current_object || type_klass
|
77
|
+
resolve_then if type_klass =~ object
|
78
|
+
end
|
79
|
+
|
80
|
+
# This will just trigger the selection resolver
|
81
|
+
def resolve_then(&block)
|
82
|
+
super(block) { resolve_fields(@current_object) }
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Check if the field was assigned correctly to an output field
|
88
|
+
def check_assignment!
|
89
|
+
raise ExecutionError, <<~MSG.squish unless type_klass.output_type?
|
90
|
+
Unable to assing #{type_klass.gql_name} to "#{name}" fragment because
|
91
|
+
it is not a output type.
|
92
|
+
MSG
|
93
|
+
|
94
|
+
raise ExecutionError, <<~MSG.squish if type_klass.leaf_type?
|
95
|
+
Unable to assing #{type_klass.gql_name} to "#{name}" fragment because
|
96
|
+
a "#{type_klass.kind}" type can not be the source of a fragmnet.
|
97
|
+
MSG
|
98
|
+
end
|
99
|
+
|
100
|
+
# If there is another fragment with the same name already defined,
|
101
|
+
# raise an error
|
102
|
+
def check_duplicated_fragment!
|
103
|
+
return unless request.fragments.key?(name)
|
104
|
+
|
105
|
+
invalidate!
|
106
|
+
|
107
|
+
other_node = request.fragments[name].instance_variable_get(:@node)
|
108
|
+
location = GraphQL::Native.get_location(other_node)
|
109
|
+
|
110
|
+
request.report_node_error(<<~MSG.squish, @node)
|
111
|
+
Duplicated fragment named "#{name}" defined on
|
112
|
+
line #{location.begin_line}:#{location.begin_column}
|
113
|
+
MSG
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|