rails_twirp 0.2.0 → 0.7.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 +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
|