rails_twirp 0.2.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +21 -0
- data/.gitignore +1 -0
- data/.standard.yml +4 -0
- data/Gemfile +3 -3
- data/lib/rails_twirp.rb +2 -0
- data/lib/rails_twirp/base.rb +36 -11
- data/lib/rails_twirp/engine.rb +29 -9
- data/lib/rails_twirp/errors.rb +11 -0
- data/lib/rails_twirp/implicit_render.rb +11 -0
- data/lib/rails_twirp/instrumentation.rb +32 -0
- data/lib/rails_twirp/log_subscriber.rb +64 -0
- data/lib/rails_twirp/mapper.rb +83 -0
- data/lib/rails_twirp/render_pb.rb +18 -0
- data/lib/rails_twirp/rescue.rb +14 -0
- data/lib/rails_twirp/route_set.rb +7 -48
- data/lib/rails_twirp/testing/integration_test.rb +73 -10
- data/lib/rails_twirp/url_for.rb +17 -0
- data/lib/rails_twirp/version.rb +1 -1
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/config/manifest.js +2 -0
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/channels/application_cable/channel.rb +4 -0
- data/test/dummy/app/channels/application_cable/connection.rb +4 -0
- data/test/dummy/app/controllers/application_controller.rb +2 -0
- data/test/dummy/app/controllers/application_twirp_controller.rb +7 -0
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/controllers/pings_controller.rb +40 -0
- data/test/dummy/app/controllers/testmod/nested/other_controller.rb +10 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/helpers/random_helper.rb +5 -0
- data/test/dummy/app/javascript/packs/application.js +15 -0
- data/test/dummy/app/jobs/application_job.rb +7 -0
- data/test/dummy/app/mailers/application_mailer.rb +4 -0
- data/test/dummy/app/models/application_record.rb +3 -0
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +15 -0
- data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/test/dummy/app/views/pings/ping_template.pb.pbbuilder +1 -0
- data/test/dummy/bin/generate +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +33 -0
- data/test/dummy/config.ru +6 -0
- data/test/dummy/config/application.rb +23 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/cable.yml +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +76 -0
- data/test/dummy/config/environments/production.rb +120 -0
- data/test/dummy/config/environments/test.rb +59 -0
- data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/test/dummy/config/initializers/assets.rb +12 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +8 -0
- data/test/dummy/config/initializers/content_security_policy.rb +28 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +6 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/permissions_policy.rb +11 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +33 -0
- data/test/dummy/config/puma.rb +43 -0
- data/test/dummy/config/routes.rb +3 -0
- data/test/dummy/config/storage.yml +34 -0
- data/test/dummy/config/twirp/routes.rb +21 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/log/.keep +0 -0
- data/test/dummy/proto/api.proto +24 -0
- data/test/dummy/proto/api_pb.rb +22 -0
- data/test/dummy/proto/api_twirp.rb +24 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/test/dummy/public/apple-touch-icon.png +0 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/other_controller_test.rb +9 -0
- data/test/ping_controller_test.rb +64 -0
- data/test/test_helper.rb +10 -1
- metadata +138 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ed88410115867b689de324aeaf99b91ba473d3aa573915d9bec5de1142b7986
|
4
|
+
data.tar.gz: dadc45ad3c6ed38f6791e8a45573127c69297772a7b0ae4861a2867dadf26d09
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 976f3755035c22c65db62aa4b82e8019bf816e5e30cec968e19c5345630aa1b7c6d51d38439e267fde4d9696ce05667c5a2ed2566953bbe87a175ac2ea9df93f
|
7
|
+
data.tar.gz: f3c1460d7cd0ae988e8aabbb516afd1983b975e8aee872f295eb3017061a51500667ee1ecbd226f2260c03f179ad94560227710fc98095e0e60749288271d406
|
@@ -0,0 +1,21 @@
|
|
1
|
+
name: Ruby Test
|
2
|
+
on: push
|
3
|
+
|
4
|
+
jobs:
|
5
|
+
test:
|
6
|
+
runs-on: ubuntu-latest
|
7
|
+
steps:
|
8
|
+
- name: Checkout code
|
9
|
+
uses: actions/checkout@v2
|
10
|
+
|
11
|
+
- name: Setup Ruby
|
12
|
+
uses: ruby/setup-ruby@v1
|
13
|
+
with:
|
14
|
+
ruby-version: 3.0.0
|
15
|
+
bundler-cache: true
|
16
|
+
|
17
|
+
- name: Run tests
|
18
|
+
run: bin/test
|
19
|
+
|
20
|
+
- name: Run standardrb
|
21
|
+
run: bundle exec standardrb
|
data/.gitignore
CHANGED
data/.standard.yml
ADDED
data/Gemfile
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
source "https://rubygems.org"
|
2
|
-
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
3
2
|
|
4
3
|
# Specify your gem's dependencies in rails_twirp.gemspec.
|
5
4
|
gemspec
|
6
5
|
|
7
|
-
|
8
|
-
|
6
|
+
gem "sqlite3"
|
7
|
+
gem "pbbuilder", "~> 0.3.0"
|
8
|
+
gem "standard"
|
data/lib/rails_twirp.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
require "rails_twirp/version"
|
2
2
|
|
3
|
+
require "rails_twirp/application"
|
3
4
|
require "rails_twirp/base"
|
4
5
|
require "rails_twirp/route_set"
|
5
6
|
require "rails_twirp/testing/integration_test"
|
7
|
+
require "rails_twirp/log_subscriber"
|
6
8
|
|
7
9
|
module RailsTwirp
|
8
10
|
mattr_accessor :test_app
|
data/lib/rails_twirp/base.rb
CHANGED
@@ -1,13 +1,41 @@
|
|
1
|
+
require "abstract_controller/base"
|
2
|
+
require "abstract_controller/rendering"
|
3
|
+
require "action_view/rendering"
|
4
|
+
require "rails_twirp/render_pb"
|
5
|
+
require "rails_twirp/errors"
|
6
|
+
require "abstract_controller/asset_paths"
|
7
|
+
require "abstract_controller/caching"
|
8
|
+
require "abstract_controller/logger"
|
9
|
+
require "abstract_controller/callbacks"
|
10
|
+
require "action_controller/metal/helpers"
|
11
|
+
require "rails_twirp/rescue"
|
12
|
+
require "rails_twirp/url_for"
|
13
|
+
require "rails_twirp/implicit_render"
|
14
|
+
require "rails_twirp/instrumentation"
|
15
|
+
|
1
16
|
module RailsTwirp
|
2
17
|
class Base < AbstractController::Base
|
3
18
|
abstract!
|
4
19
|
|
5
|
-
|
20
|
+
# The order of these includes matter.
|
21
|
+
# The rendering modules extend each other, so need to be in this order.
|
22
|
+
include AbstractController::Rendering
|
23
|
+
|
24
|
+
# These add helpers for the controller
|
25
|
+
include ActionController::Helpers
|
26
|
+
include UrlFor
|
6
27
|
include AbstractController::AssetPaths
|
7
|
-
include AbstractController::Callbacks
|
8
28
|
include AbstractController::Caching
|
9
|
-
|
29
|
+
|
10
30
|
include ActionView::Rendering
|
31
|
+
include RenderPb
|
32
|
+
include Errors
|
33
|
+
include ImplicitRender
|
34
|
+
|
35
|
+
# These need to be last so errors can be handled as early as possible.
|
36
|
+
include AbstractController::Callbacks
|
37
|
+
include Rescue
|
38
|
+
include Instrumentation
|
11
39
|
|
12
40
|
attr_internal :request, :env, :response_class
|
13
41
|
def initialize
|
@@ -30,21 +58,18 @@ module RailsTwirp
|
|
30
58
|
|
31
59
|
process(action)
|
32
60
|
|
33
|
-
# Implicit render
|
34
|
-
self.response_body = render unless response_body
|
35
61
|
response_body
|
36
62
|
end
|
37
63
|
|
38
|
-
def render(*args)
|
39
|
-
options = {formats: :pb, handlers: :pbbuilder, locals: {response_class: response_class}}
|
40
|
-
options.deep_merge! args.extract_options!
|
41
|
-
super(*args, options)
|
42
|
-
end
|
43
|
-
|
44
64
|
def self.dispatch(action, request, response_class, env = {})
|
45
65
|
new.dispatch(action, request, response_class, env)
|
46
66
|
end
|
47
67
|
|
68
|
+
# Used by the template renderer to figure out which template to use
|
69
|
+
def details_for_lookup
|
70
|
+
{formats: [:pb], handlers: [:pbbuilder]}
|
71
|
+
end
|
72
|
+
|
48
73
|
ActiveSupport.run_load_hooks(:rails_twirp, self)
|
49
74
|
end
|
50
75
|
end
|
data/lib/rails_twirp/engine.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
require "rails/railtie"
|
2
1
|
require "rails_twirp/application"
|
2
|
+
require "rails_twirp/route_set"
|
3
|
+
require "rails/railtie"
|
3
4
|
|
4
5
|
module RailsTwirp
|
5
6
|
# Even though this is an engine, we don't inherit from Rails::Engine because we don't want anything it provides.
|
@@ -14,20 +15,38 @@ module RailsTwirp
|
|
14
15
|
end
|
15
16
|
|
16
17
|
initializer "rails_twirp.logger" do
|
17
|
-
# This hook is called
|
18
|
+
# This hook is called when RailsTwirp::Base is initialized, and it sets the logger
|
18
19
|
ActiveSupport.on_load(:rails_twirp) { self.logger ||= Rails.logger }
|
19
20
|
end
|
20
21
|
|
21
22
|
initializer :add_paths, before: :bootstrap_hook do |app|
|
22
23
|
app.config.paths.add "config/twirp/routes.rb"
|
23
24
|
app.config.paths.add "config/twirp/routes", glob: "**/*.rb"
|
24
|
-
app.config.paths.add "app/twirp/controllers", eager_load: true
|
25
25
|
app.config.paths.add "proto", load_path: true
|
26
|
-
app.config.paths.add "app/twirp/views", load_path: true
|
27
26
|
end
|
28
27
|
|
29
|
-
initializer :set_controller_view_path do
|
30
|
-
|
28
|
+
initializer :set_controller_view_path do |app|
|
29
|
+
views = app.config.paths["app/views"].existent
|
30
|
+
unless views.empty?
|
31
|
+
ActiveSupport.on_load(:rails_twirp) { prepend_view_path views }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
initializer "rails_twirp.helpers" do |app|
|
36
|
+
ActiveSupport.on_load(:rails_twirp) do
|
37
|
+
# Load all the application helpers into the controller
|
38
|
+
include app.routes.mounted_helpers
|
39
|
+
extend ::AbstractController::Railties::RoutesHelpers.with(app.routes, false)
|
40
|
+
extend ::ActionController::Railties::Helpers
|
41
|
+
define_singleton_method(:inherited) do |klass|
|
42
|
+
super(klass)
|
43
|
+
|
44
|
+
# Have to call this explicitely, because ::ActionController::Railties::Helpers
|
45
|
+
# checks if ActionController::Base is a parent class, which it isn't.
|
46
|
+
# If we don't call this, the helpers don't get loaded
|
47
|
+
klass.helper :all
|
48
|
+
end
|
49
|
+
end
|
31
50
|
end
|
32
51
|
|
33
52
|
initializer :add_twirp do |app|
|
@@ -37,11 +56,12 @@ module RailsTwirp
|
|
37
56
|
|
38
57
|
initializer :load_twirp_routes do |app|
|
39
58
|
# Load route files
|
40
|
-
|
59
|
+
[
|
41
60
|
*app.config.paths["config/twirp/routes.rb"].existent,
|
42
61
|
*app.config.paths["config/twirp/routes"].existent
|
43
|
-
]
|
44
|
-
|
62
|
+
].each do |path|
|
63
|
+
load path
|
64
|
+
end
|
45
65
|
|
46
66
|
# Create a router that knows how to route all the registered services
|
47
67
|
services = app.twirp.routes.to_services
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require "twirp/error"
|
2
|
+
|
3
|
+
module RailsTwirp
|
4
|
+
module Errors
|
5
|
+
# Helper that sets the response to a Twirp Error
|
6
|
+
# The valid error codes can be found in Twirp::ERROR_CODES
|
7
|
+
def error(code, message, meta = nil)
|
8
|
+
self.response_body = Twirp::Error.new(code, message, meta)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module RailsTwirp
|
2
|
+
module Instrumentation
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
include AbstractController::Logger
|
6
|
+
|
7
|
+
def process_action(*)
|
8
|
+
raw_payload = {
|
9
|
+
controller: self.class.name,
|
10
|
+
action: action_name,
|
11
|
+
request: request,
|
12
|
+
http_request: http_request,
|
13
|
+
headers: http_request.headers,
|
14
|
+
path: http_request.fullpath
|
15
|
+
}
|
16
|
+
|
17
|
+
ActiveSupport::Notifications.instrument("start_processing.rails_twirp", raw_payload)
|
18
|
+
|
19
|
+
ActiveSupport::Notifications.instrument("process_action.rails_twirp", raw_payload) do |payload|
|
20
|
+
result = super
|
21
|
+
if response_body.is_a?(Twirp::Error)
|
22
|
+
payload[:code] = response_body.code
|
23
|
+
payload[:msg] = response_body.msg
|
24
|
+
else
|
25
|
+
payload[:code] = :success
|
26
|
+
end
|
27
|
+
payload[:response] = response_body
|
28
|
+
result
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "active_support/log_subscriber"
|
2
|
+
|
3
|
+
module RailsTwirp
|
4
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
5
|
+
def start_processing(event)
|
6
|
+
return unless logger.info?
|
7
|
+
|
8
|
+
payload = event.payload
|
9
|
+
|
10
|
+
info "Processing by #{payload[:controller]}##{payload[:action]}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def process_action(event)
|
14
|
+
payload = event.payload
|
15
|
+
exception_raised(payload[:http_request], payload[:exception_object]) if payload[:exception_object]
|
16
|
+
|
17
|
+
info do
|
18
|
+
code = payload.fetch(:code, :internal)
|
19
|
+
|
20
|
+
message = +"Completed #{code} in #{event.duration.round}ms (Allocations: #{event.allocations})"
|
21
|
+
message << "\n\n" if defined?(Rails.env) && Rails.env.development?
|
22
|
+
|
23
|
+
message
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def exception_raised(request, exception)
|
30
|
+
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
31
|
+
wrapper = ActionDispatch::ExceptionWrapper.new(backtrace_cleaner, exception)
|
32
|
+
|
33
|
+
log_error(wrapper)
|
34
|
+
end
|
35
|
+
|
36
|
+
def log_error(wrapper)
|
37
|
+
exception = wrapper.exception
|
38
|
+
trace = wrapper.exception_trace
|
39
|
+
|
40
|
+
message = []
|
41
|
+
message << " "
|
42
|
+
message << "#{exception.class} (#{exception.message}):"
|
43
|
+
message.concat(exception.annotated_source_code) if exception.respond_to?(:annotated_source_code)
|
44
|
+
message << " "
|
45
|
+
message.concat(trace)
|
46
|
+
|
47
|
+
log_array(message)
|
48
|
+
end
|
49
|
+
|
50
|
+
def log_array(array)
|
51
|
+
lines = Array(array)
|
52
|
+
|
53
|
+
return if lines.empty?
|
54
|
+
|
55
|
+
if logger.formatter&.respond_to?(:tags_text)
|
56
|
+
fatal { lines.join("\n#{logger.formatter.tags_text}") }
|
57
|
+
else
|
58
|
+
fatal { lines.join("\n") }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
RailsTwirp::LogSubscriber.attach_to :rails_twirp
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module RailsTwirp
|
4
|
+
class ServiceMapper
|
5
|
+
class Mapping
|
6
|
+
attr_reader :controller, :action
|
7
|
+
|
8
|
+
def initialize(to:, **options)
|
9
|
+
controller, @action = split_to(to)
|
10
|
+
@controller = add_controller_module(controller, options.delete(:module))
|
11
|
+
raise ArgumentError, "Unknown argument #{options.keys.first}" unless options.empty?
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"#{controller}##{action}"
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# copied from Rails
|
21
|
+
def split_to(to)
|
22
|
+
if /#/.match?(to)
|
23
|
+
to.split("#").map!(&:-@)
|
24
|
+
else
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_controller_module(controller, modyoule)
|
30
|
+
return controller unless modyoule
|
31
|
+
|
32
|
+
if controller&.start_with?("/")
|
33
|
+
-controller[1..]
|
34
|
+
else
|
35
|
+
-[modyoule, controller].compact.join("/")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
extend Forwardable
|
41
|
+
def_delegator :@mapper, :scope
|
42
|
+
|
43
|
+
def initialize(service_route_set, mapper)
|
44
|
+
@service_route_set = service_route_set
|
45
|
+
@mapper = mapper
|
46
|
+
end
|
47
|
+
|
48
|
+
def rpc(name, to:)
|
49
|
+
mapping = Mapping.new(to: to, module: @mapper.send(:_module))
|
50
|
+
@service_route_set.add_route(name, mapping)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Mapper
|
55
|
+
def initialize(route_set)
|
56
|
+
@route_set = route_set
|
57
|
+
@module = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def service(service_definition, **options, &block)
|
61
|
+
service_route_set = @route_set.services[service_definition]
|
62
|
+
service_mapper = ServiceMapper.new(service_route_set, self)
|
63
|
+
scope(**options) { service_mapper.instance_exec(&block) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def scope(**options)
|
67
|
+
last_module = @module
|
68
|
+
if (modyoule = options.delete(:module))
|
69
|
+
@module = @module.nil? ? modyoule : "#{@module}/#{modyoule}"
|
70
|
+
end
|
71
|
+
raise ArgumentError, "Unknown scope argument #{options.keys.first}" unless options.empty?
|
72
|
+
yield
|
73
|
+
ensure
|
74
|
+
@module = last_module
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def _module
|
80
|
+
@module
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module RailsTwirp
|
2
|
+
# RenderPb makes it possible to do 'render pb: <proto object>', skipping templates
|
3
|
+
# The way this module is written is inspired by ActionController::Renderers
|
4
|
+
module RenderPb
|
5
|
+
def render_to_body(options)
|
6
|
+
_render_to_body_with_pb(options) || super
|
7
|
+
end
|
8
|
+
|
9
|
+
def _render_to_body_with_pb(options)
|
10
|
+
if options.include? :pb
|
11
|
+
_process_options(options)
|
12
|
+
return options[:pb]
|
13
|
+
end
|
14
|
+
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|