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,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/railtie'
|
4
|
+
require 'action_controller'
|
5
|
+
require 'action_controller/railtie'
|
6
|
+
|
7
|
+
module Rails # :nodoc:
|
8
|
+
module GraphQL # :nodoc:
|
9
|
+
# = Rails GraphQL Railtie
|
10
|
+
#
|
11
|
+
# Rails integration and configuration
|
12
|
+
class Railtie < Rails::Railtie
|
13
|
+
config.eager_load_namespaces << Rails::GraphQL
|
14
|
+
config.graphql = GraphQL.config
|
15
|
+
|
16
|
+
rake_tasks do
|
17
|
+
load 'rails/graphql.rake'
|
18
|
+
end
|
19
|
+
|
20
|
+
runner do
|
21
|
+
require_relative './schema'
|
22
|
+
end
|
23
|
+
|
24
|
+
# Ensure a valid logger
|
25
|
+
initializer 'graphql.logger' do
|
26
|
+
ActiveSupport.on_load(:graphql) do
|
27
|
+
return if config.logger.present?
|
28
|
+
if ::Rails.logger.respond_to?(:tagged)
|
29
|
+
config.logger = ::Rails.logger
|
30
|
+
else
|
31
|
+
config.logger = ActiveSupport::TaggedLogging.new(::Rails.logger)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Expose database runtime to controller for logging.
|
37
|
+
initializer 'graphql.log_runtime' do
|
38
|
+
require_relative './railties/controller_runtime'
|
39
|
+
ActiveSupport.on_load(:action_controller) do
|
40
|
+
include GraphQL::ControllerRuntime
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Backtrace cleaner for removing gem paths
|
45
|
+
initializer 'graphql.backtrace_cleaner' do
|
46
|
+
require_relative './railties/log_subscriber'
|
47
|
+
ActiveSupport.on_load(:graphql) do
|
48
|
+
GraphQL::LogSubscriber.backtrace_cleaner = ::Rails.backtrace_cleaner
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Add reloader ability for files under 'app/graphql'
|
53
|
+
# TODO: Maybe improve to use rails auto loader
|
54
|
+
initializer 'graphql.reloader' do
|
55
|
+
Rails::GraphQL.eager_load!
|
56
|
+
ActiveSupport::Reloader.to_prepare do
|
57
|
+
Rails::GraphQL.type_map.use_checkpoint!
|
58
|
+
Rails::GraphQL.reload_ar_adapters!
|
59
|
+
|
60
|
+
Object.send(:remove_const, :GraphQL) if Object.const_defined?(:GraphQL)
|
61
|
+
|
62
|
+
load "#{__dir__}/shortcuts.rb"
|
63
|
+
|
64
|
+
$LOAD_PATH.each do |path|
|
65
|
+
next unless path =~ %r{\/app\/graphql$}
|
66
|
+
Dir.glob("#{path}/**/*.rb").sort.each(&method(:load))
|
67
|
+
end
|
68
|
+
|
69
|
+
GraphQL::Source.send(:build_pending!)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# initializer "active_record.set_reloader_hooks" do
|
74
|
+
# ActiveSupport.on_load(:active_record) do
|
75
|
+
# ActiveSupport::Reloader.before_class_unload do
|
76
|
+
# if ActiveRecord::Base.connected?
|
77
|
+
# ActiveRecord::Base.clear_cache!
|
78
|
+
# ActiveRecord::Base.clear_reloadable_connections!
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
# = GraphQL Base Generator
|
6
|
+
#
|
7
|
+
# A module to help generators to operate
|
8
|
+
module BaseGenerator
|
9
|
+
TEMPALTES_PATH = '../../../generators/graphql/templates'
|
10
|
+
|
11
|
+
def self.included(base)
|
12
|
+
base.send(:namespace, "graphql:#{base.name.demodulize.underscore[0..-11]}")
|
13
|
+
base.send(:source_root, File.expand_path(TEMPALTES_PATH, __dir__))
|
14
|
+
base.send(:class_option, :directory,
|
15
|
+
type: :string,
|
16
|
+
default: 'app/graphql',
|
17
|
+
desc: 'Directory where generated files should be saved',
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def app_module_name
|
24
|
+
require File.expand_path('config/application', destination_root)
|
25
|
+
|
26
|
+
app_class = Rails.application.class
|
27
|
+
source_name = app_class.respond_to?(:module_parent_name) \
|
28
|
+
? :module_parent_name \
|
29
|
+
: :parent_name
|
30
|
+
|
31
|
+
app_class.send(source_name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
# = GraphQL Controller
|
6
|
+
#
|
7
|
+
# The controller helper methods that allow GraphQL to be performed on a
|
8
|
+
# Rails Controller class.
|
9
|
+
module Controller
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
REQUEST_XARGS = %i[operation_name variables context schema].freeze
|
13
|
+
|
14
|
+
included do
|
15
|
+
# Each controller is assigned to a GraphQL schema on which the requests
|
16
|
+
# will be performed from. It can be a string or the class
|
17
|
+
class_attribute :gql_schema, instance_accessor: false
|
18
|
+
end
|
19
|
+
|
20
|
+
# POST /execute
|
21
|
+
def execute
|
22
|
+
gql_request_response(gql_query)
|
23
|
+
end
|
24
|
+
|
25
|
+
# GET /describe
|
26
|
+
def describe
|
27
|
+
render plain: gql_schema.to_gql(
|
28
|
+
with_descriptions: !params.key?(:without_descriptions),
|
29
|
+
with_spec: !params.key?(:without_spec),
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
# Render a response as a GraphQL request
|
36
|
+
def gql_request_response(*args, **xargs)
|
37
|
+
render json: gql_request(*args, **xargs)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Execute a GraphQL request
|
41
|
+
def gql_request(query, **xargs)
|
42
|
+
request_xargs = REQUEST_XARGS.inject({}) do |result, setting|
|
43
|
+
result.merge(setting => (xargs[setting] || send("gql_#{setting}")))
|
44
|
+
end
|
45
|
+
|
46
|
+
::Rails::GraphQL::Request.execute(query, **request_xargs)
|
47
|
+
end
|
48
|
+
|
49
|
+
# The schema on which the requests will be performed from
|
50
|
+
def gql_schema
|
51
|
+
schema = self.class.gql_schema
|
52
|
+
schema = schema.safe_constantize if schema.is_a?(String)
|
53
|
+
schema ||= application_default_schema
|
54
|
+
return schema if schema.is_a?(Module) && schema < ::Rails::GraphQL::Schema
|
55
|
+
|
56
|
+
raise ExecutionError, <<~MSG.squish
|
57
|
+
Unable to find a valid schema for #{self.class.name},
|
58
|
+
defined value: #{schema.inspect}.
|
59
|
+
MSG
|
60
|
+
end
|
61
|
+
|
62
|
+
# Get the GraphQL query to execute
|
63
|
+
def gql_query
|
64
|
+
params[:query]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get the GraphQL operation name
|
68
|
+
def gql_operation_name
|
69
|
+
params[:operationName]
|
70
|
+
end
|
71
|
+
|
72
|
+
# Get the GraphQL context for a requests
|
73
|
+
def gql_context
|
74
|
+
{}
|
75
|
+
end
|
76
|
+
|
77
|
+
# Get the GraphQL variables for a request
|
78
|
+
def gql_variables(variables = params[:variables])
|
79
|
+
case variables
|
80
|
+
when ::ActionController::Parameters then variables.permit!
|
81
|
+
when String then variables.present? ? JSON.parse(variables) : {}
|
82
|
+
when Hash then variables
|
83
|
+
else {}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# Find the default application schema
|
90
|
+
def application_default_schema
|
91
|
+
app_class = Rails.application.class
|
92
|
+
source_name = app_class.respond_to?(:module_parent_name) \
|
93
|
+
? :module_parent_name \
|
94
|
+
: :parent_name
|
95
|
+
|
96
|
+
klass = "::GraphQL::#{app_class.send(source_name)}Schema".constantize
|
97
|
+
self.class.gql_schema = klass
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/module/attr_internal'
|
4
|
+
|
5
|
+
module Rails # :nodoc:
|
6
|
+
module GraphQL # :nodoc:
|
7
|
+
# = GraphQL Controller Runtime
|
8
|
+
#
|
9
|
+
# Tool that calculates the runtime of a GraphQL operation. This works
|
10
|
+
# similar to how Rails ActiveRecord calculate its execution time while
|
11
|
+
# performing a request.
|
12
|
+
module ControllerRuntime
|
13
|
+
extend ActiveSupport::Concern
|
14
|
+
|
15
|
+
module ClassMethods # :nodoc: all
|
16
|
+
def log_process_action(payload)
|
17
|
+
messages, gql_runtime = super, payload[:gql_runtime]
|
18
|
+
messages << format('GraphQL: %.1fms', gql_runtime.to_f) if gql_runtime
|
19
|
+
messages
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_internal :gql_runtime
|
26
|
+
|
27
|
+
def process_action(*)
|
28
|
+
LogSubscriber.runtime = 0
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def append_info_to_payload(payload)
|
33
|
+
super
|
34
|
+
|
35
|
+
payload[:gql_runtime] = LogSubscriber.runtime \
|
36
|
+
if (LogSubscriber.runtime || 0).positive?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
# = GraphQL Log Subscriber
|
6
|
+
#
|
7
|
+
# This is the log tracker that workds the same way as ActiveRecord when it
|
8
|
+
# has to report on logs that a query was performed.
|
9
|
+
class LogSubscriber < ::ActiveSupport::LogSubscriber # :nodoc: all
|
10
|
+
class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
|
11
|
+
|
12
|
+
def self.runtime
|
13
|
+
RuntimeRegistry.gql_runtime ||= 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.runtime=(value)
|
17
|
+
RuntimeRegistry.gql_runtime = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def request(event)
|
21
|
+
self.class.runtime += event.duration
|
22
|
+
return unless logger.debug?
|
23
|
+
|
24
|
+
payload = event.payload
|
25
|
+
|
26
|
+
name = ['GraphQL', payload[:name].presence]
|
27
|
+
name.unshift('CACHE') if payload[:cached]
|
28
|
+
name = "#{name.compact.join(' ')} (#{event.duration.round(1)}ms)"
|
29
|
+
|
30
|
+
document = payload[:document].squish
|
31
|
+
variables = payload[:variables].blank? ? nil : begin
|
32
|
+
" (#{JSON.pretty_generate(payload[:variables]).squish})"
|
33
|
+
end
|
34
|
+
|
35
|
+
debug " #{color(name, MAGENTA, true)} #{document}#{variables}"
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def logger
|
41
|
+
GraphQL.config.logger
|
42
|
+
end
|
43
|
+
|
44
|
+
def debug(*)
|
45
|
+
return unless super
|
46
|
+
|
47
|
+
log_query_source if GraphQL.config.verbose_logs
|
48
|
+
end
|
49
|
+
|
50
|
+
def log_query_source
|
51
|
+
source = extract_query_source_location(caller)
|
52
|
+
logger.debug(" ↳ #{source}") if source
|
53
|
+
end
|
54
|
+
|
55
|
+
def extract_query_source_location(locations)
|
56
|
+
backtrace_cleaner.clean(locations.lazy).first
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
GraphQL::LogSubscriber.attach_to :graphql
|
62
|
+
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
# = GraphQL Request
|
6
|
+
#
|
7
|
+
# This class is responsible for processing a GraphQL response. It will
|
8
|
+
# handle queries, mutations, and subscription, as long as all of them are
|
9
|
+
# provided together. It also can be executed multiple times using the same
|
10
|
+
# context calling +execute+ multiple times.
|
11
|
+
#
|
12
|
+
# ==== Options
|
13
|
+
#
|
14
|
+
# * <tt>:namespace</tt> - Set what is the namespace used for the request
|
15
|
+
# (defaults to :base).
|
16
|
+
class Request
|
17
|
+
extend ActiveSupport::Autoload
|
18
|
+
|
19
|
+
RESPONSE_FORMATS = { string: :to_s, object: :to_h, hash: :to_h }.freeze
|
20
|
+
|
21
|
+
eager_autoload do
|
22
|
+
autoload_under :steps do
|
23
|
+
autoload :Organizable
|
24
|
+
autoload :Prepareable
|
25
|
+
autoload :Resolveable
|
26
|
+
end
|
27
|
+
|
28
|
+
autoload_under :helpers do
|
29
|
+
autoload :Directives
|
30
|
+
autoload :SelectionSet
|
31
|
+
autoload :ValueWriters
|
32
|
+
end
|
33
|
+
|
34
|
+
autoload :Arguments
|
35
|
+
autoload :Component
|
36
|
+
autoload :Context
|
37
|
+
autoload :Errors
|
38
|
+
autoload :Event
|
39
|
+
autoload :Strategy
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :schema, :visitor, :operations, :fragments, :errors,
|
43
|
+
:args, :response, :strategy, :stack
|
44
|
+
|
45
|
+
delegate :all_listeners, to: :schema
|
46
|
+
|
47
|
+
class << self
|
48
|
+
# Shortcut for initialize, set context, and execute
|
49
|
+
def execute(*args, schema: nil, namespace: :base, context: {}, **xargs)
|
50
|
+
result = new(schema, namespace: namespace)
|
51
|
+
result.context = context if context.present?
|
52
|
+
result.execute(*args, **xargs)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Allow accessing component-based objects through the request
|
56
|
+
def const_defined?(name, *)
|
57
|
+
Component.const_defined?(name) || super
|
58
|
+
end
|
59
|
+
|
60
|
+
# Allow accessing component-based objects through the request
|
61
|
+
def const_missing(name)
|
62
|
+
Component.const_defined?(name) ? Component.const_get(name) : super
|
63
|
+
end
|
64
|
+
|
65
|
+
def eager_load! # :nodoc:
|
66
|
+
super
|
67
|
+
|
68
|
+
Request::Component.eager_load!
|
69
|
+
Request::Strategy.eager_load!
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Forces the schema to be registered on type map before moving forward
|
74
|
+
def initialize(schema = nil, namespace: :base)
|
75
|
+
@namespace = schema&.namespace || namespace
|
76
|
+
@schema = GraphQL::Schema.find!(@namespace)
|
77
|
+
@extensions = {}
|
78
|
+
|
79
|
+
ensure_schema!
|
80
|
+
end
|
81
|
+
|
82
|
+
# Cache all the schema events for this current request
|
83
|
+
def all_events
|
84
|
+
@all_events ||= schema.all_events
|
85
|
+
end
|
86
|
+
|
87
|
+
# Get the context of the request
|
88
|
+
def context
|
89
|
+
@context ||= OpenStruct.new.freeze
|
90
|
+
end
|
91
|
+
|
92
|
+
# Set the context of the request, it must be a +Hash+
|
93
|
+
def context=(data)
|
94
|
+
@context = build_ostruct(data).freeze
|
95
|
+
end
|
96
|
+
|
97
|
+
# Execute a given document with the given arguments
|
98
|
+
def execute(document, as: :string, **xargs)
|
99
|
+
reset!(**xargs)
|
100
|
+
|
101
|
+
to = RESPONSE_FORMATS[as]
|
102
|
+
@response = initialize_response(as, to)
|
103
|
+
|
104
|
+
execute!(document)
|
105
|
+
response.public_send(to)
|
106
|
+
end
|
107
|
+
|
108
|
+
alias perform execute
|
109
|
+
|
110
|
+
# Build a easy-to-access object representing the current information of
|
111
|
+
# the execution to be used on +rescue_with_handler+
|
112
|
+
def build_rescue_object(**extra)
|
113
|
+
OpenStruct.new(extra.reverse_merge(
|
114
|
+
args: @args,
|
115
|
+
source: stack.first,
|
116
|
+
request: self,
|
117
|
+
response: @response,
|
118
|
+
document: @document,
|
119
|
+
)).freeze
|
120
|
+
end
|
121
|
+
|
122
|
+
# Use schema handlers for exceptions caught during the execution process
|
123
|
+
def rescue_with_handler(exception, **extra)
|
124
|
+
schema.rescue_with_handler(exception, object: build_rescue_object(**extra))
|
125
|
+
end
|
126
|
+
|
127
|
+
# Add the given +exception+ to the errors using the +node+ location
|
128
|
+
def exception_to_error(exception, node, **xargs)
|
129
|
+
xargs[:exception] = exception.class.name
|
130
|
+
report_node_error(exception.message, node, **xargs)
|
131
|
+
end
|
132
|
+
|
133
|
+
# A little helper to report an error on a given node
|
134
|
+
def report_node_error(message, node, **xargs)
|
135
|
+
node = node.instance_variable_get(:@node) if node.is_a?(Request::Component)
|
136
|
+
location = GraphQL::Native.get_location(node)
|
137
|
+
|
138
|
+
xargs[:locations] ||= location.to_errors unless xargs.key?(:line)
|
139
|
+
report_error(message, **xargs)
|
140
|
+
end
|
141
|
+
|
142
|
+
# The final helper that facilitates how errors are reported
|
143
|
+
def report_error(message, **xargs)
|
144
|
+
xargs[:path] ||= stack_to_path
|
145
|
+
errors.add(message, **xargs)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Add the given +object+ into the execution +stack+ and execute the given
|
149
|
+
# +block+ making sure to rescue exceptions using the +rescue_with_handler+
|
150
|
+
def stacked(object, &block)
|
151
|
+
stack.unshift(object)
|
152
|
+
block.call
|
153
|
+
rescue => exception
|
154
|
+
rescue_with_handler(exception) || raise
|
155
|
+
ensure
|
156
|
+
stack.shift
|
157
|
+
end
|
158
|
+
|
159
|
+
# Convert the current stack into a error path ignoring the schema
|
160
|
+
def stack_to_path
|
161
|
+
stack[0..-2].map do |item|
|
162
|
+
item.is_a?(Numeric) ? item : item.try(:gql_name)
|
163
|
+
end.compact.reverse
|
164
|
+
end
|
165
|
+
|
166
|
+
# Add extensions to the request, which ensures a bunch of extended
|
167
|
+
# behaviors for all the objects created through the request
|
168
|
+
def extend(*modules)
|
169
|
+
import_extensions(*modules)
|
170
|
+
request_ext = extensions[self.class]
|
171
|
+
super(request_ext) if request_ext && !is_a?(request_ext)
|
172
|
+
end
|
173
|
+
|
174
|
+
# This initiates a new object which is aware of extensions
|
175
|
+
def build(klass, *args, &block)
|
176
|
+
ext_module = extensions[klass]
|
177
|
+
obj = klass.new(*args, &block)
|
178
|
+
obj.extend(ext_module) if ext_module
|
179
|
+
obj
|
180
|
+
end
|
181
|
+
|
182
|
+
# Get and cache a sanitized version of the arguments
|
183
|
+
def sanitized_arguments
|
184
|
+
return {} unless args.each_pair.any?
|
185
|
+
cache(:sanitized_arguments) do
|
186
|
+
args.to_h.transform_keys { |key| key.to_s.camelize(:lower) }
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# A shared way to cache information across the execution of an request
|
191
|
+
def cache(key, init_value = nil, &block)
|
192
|
+
@cache[key] ||= (init_value || block&.call || {})
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
attr_reader :extensions
|
198
|
+
|
199
|
+
# Reset principal variables and set the given +args+
|
200
|
+
def reset!(args: nil, variables: {}, operation_name: nil)
|
201
|
+
@args = build_ostruct(args || variables).freeze
|
202
|
+
@errors = Request::Errors.new(self)
|
203
|
+
@visitor = GraphQL::Native::Visitor.new
|
204
|
+
@operation_name = operation_name
|
205
|
+
|
206
|
+
@stack = [schema]
|
207
|
+
@cache = {}
|
208
|
+
@fragments = {}
|
209
|
+
@operations = {}
|
210
|
+
end
|
211
|
+
|
212
|
+
# This executes the whole process capturing any exceptions and handling
|
213
|
+
# them as defined by the schema
|
214
|
+
def execute!(document)
|
215
|
+
log_execution(document) do
|
216
|
+
@document = GraphQL::Native.parse(document)
|
217
|
+
collect_definitions!
|
218
|
+
|
219
|
+
@strategy = find_strategy!
|
220
|
+
@strategy.trigger_event(:request)
|
221
|
+
@strategy.resolve!
|
222
|
+
end
|
223
|
+
rescue ParseError => err
|
224
|
+
parts = err.message.match(/\A(\d+)\.(\d+)(?:-\d+)?: (.*)\z/)
|
225
|
+
errors.add(parts[3], line: parts[1], col: parts[2])
|
226
|
+
ensure
|
227
|
+
@cache.clear
|
228
|
+
@fragments.clear
|
229
|
+
@operations.clear
|
230
|
+
|
231
|
+
@visitor.terminate
|
232
|
+
@visitor = nil
|
233
|
+
|
234
|
+
GraphQL::Native.free_node(@document) if defined?(@document)
|
235
|
+
@response.try(:append_errors, errors)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Use the visitor to collect the operations and fragments
|
239
|
+
def collect_definitions!
|
240
|
+
visitor.collect_definitions(@document) do |kind, node, data|
|
241
|
+
case kind
|
242
|
+
when :operation
|
243
|
+
operations[data[:name]] = Component::Operation.build(self, node, data)
|
244
|
+
when :fragment
|
245
|
+
fragments[data[:name]] = build(Component::Fragment, self, node, data)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Find the best strategy to resolve the request
|
251
|
+
def find_strategy!
|
252
|
+
klass = schema.config.request_strategies.lazy.map(&:constantize).select do |k|
|
253
|
+
k.can_resolve?(self)
|
254
|
+
end.max_by(&:priority)
|
255
|
+
build(klass, self)
|
256
|
+
end
|
257
|
+
|
258
|
+
# Find all necessary extensions inside the given +modules+ and prepare
|
259
|
+
# the extension base module
|
260
|
+
def import_extensions(*modules)
|
261
|
+
modules.each do |mod|
|
262
|
+
mod.constants.each do |const_name|
|
263
|
+
const_name = const_name.to_s
|
264
|
+
const = mod.const_get(const_name)
|
265
|
+
next unless const.is_a?(Module)
|
266
|
+
|
267
|
+
# Find the related request class to extend
|
268
|
+
klass = const_name === 'Request' ? self.class : begin
|
269
|
+
const_name.split('_').inject(self.class) do |k, next_const|
|
270
|
+
k.const_defined?(next_const) ? k.const_get(next_const) : break
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# Create the shared module and include the extension
|
275
|
+
next unless klass&.is_a?(Class)
|
276
|
+
extensions[klass] ||= Module.new
|
277
|
+
extensions[klass].include(const)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# Log the execution of a GraphQL document
|
283
|
+
def log_execution(document)
|
284
|
+
ActiveSupport::Notifications.instrument('request.graphql') do |payload|
|
285
|
+
yield.tap { log_payload(document, payload) }
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# Build the payload to be sent to the log
|
290
|
+
def log_payload(document, data)
|
291
|
+
name = @operation_name.presence
|
292
|
+
name ||= operations.keys.first if operations.size.eql?(1)
|
293
|
+
map_variables = args.to_h if args.each_pair.any?
|
294
|
+
|
295
|
+
data.merge!(
|
296
|
+
name: name,
|
297
|
+
cached: false,
|
298
|
+
document: document,
|
299
|
+
variables: map_variables,
|
300
|
+
)
|
301
|
+
end
|
302
|
+
|
303
|
+
# Initialize the class that responsible for storaging the response
|
304
|
+
def initialize_response(as_format, to)
|
305
|
+
raise ::ArgumentError, <<~MSG.squish if to.nil?
|
306
|
+
The given format #{as_format.inspect} is not a valid reponse format.
|
307
|
+
MSG
|
308
|
+
|
309
|
+
klass = schema.config.enable_string_collector \
|
310
|
+
? Collectors::JsonCollector \
|
311
|
+
: Collectors::HashCollector
|
312
|
+
|
313
|
+
obj = klass.new(self)
|
314
|
+
raise ::ArgumentError, <<~MSG.squish unless obj.respond_to?(to)
|
315
|
+
Unable to use "#{klass.name}" as response collector since it does
|
316
|
+
not implement a #{to.inspect} method.
|
317
|
+
MSG
|
318
|
+
|
319
|
+
obj
|
320
|
+
end
|
321
|
+
|
322
|
+
# Little helper to build an +OpenStruct+ ensure the given +value+ is a
|
323
|
+
# +Hash+. It can also +transform_keys+ with the given block
|
324
|
+
def build_ostruct(value, &block)
|
325
|
+
raise ::ArgumentError, <<~MSG.squish unless value.is_a?(Hash)
|
326
|
+
The "#{value.class.name}" is not a valid hash.
|
327
|
+
MSG
|
328
|
+
|
329
|
+
value = value.deep_transform_keys(&block) if block.present?
|
330
|
+
OpenStruct.new(value)
|
331
|
+
end
|
332
|
+
|
333
|
+
# Make sure that a schema was assigned by find the corresponding one for
|
334
|
+
# the namespace of the request
|
335
|
+
def ensure_schema!
|
336
|
+
raise ::ArgumentError, <<~MSG.squish if schema.nil?
|
337
|
+
Unable to perform a request under the #{@namespace.inspect} namespace,
|
338
|
+
because there are no schema assigned to it.
|
339
|
+
MSG
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|