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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +21 -0
  3. data/.gitignore +1 -0
  4. data/.standard.yml +4 -0
  5. data/Gemfile +3 -3
  6. data/lib/rails_twirp.rb +2 -0
  7. data/lib/rails_twirp/base.rb +36 -11
  8. data/lib/rails_twirp/engine.rb +29 -9
  9. data/lib/rails_twirp/errors.rb +11 -0
  10. data/lib/rails_twirp/implicit_render.rb +11 -0
  11. data/lib/rails_twirp/instrumentation.rb +32 -0
  12. data/lib/rails_twirp/log_subscriber.rb +64 -0
  13. data/lib/rails_twirp/mapper.rb +83 -0
  14. data/lib/rails_twirp/render_pb.rb +18 -0
  15. data/lib/rails_twirp/rescue.rb +14 -0
  16. data/lib/rails_twirp/route_set.rb +7 -48
  17. data/lib/rails_twirp/testing/integration_test.rb +73 -10
  18. data/lib/rails_twirp/url_for.rb +17 -0
  19. data/lib/rails_twirp/version.rb +1 -1
  20. data/test/dummy/Rakefile +6 -0
  21. data/test/dummy/app/assets/config/manifest.js +2 -0
  22. data/test/dummy/app/assets/images/.keep +0 -0
  23. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  24. data/test/dummy/app/channels/application_cable/channel.rb +4 -0
  25. data/test/dummy/app/channels/application_cable/connection.rb +4 -0
  26. data/test/dummy/app/controllers/application_controller.rb +2 -0
  27. data/test/dummy/app/controllers/application_twirp_controller.rb +7 -0
  28. data/test/dummy/app/controllers/concerns/.keep +0 -0
  29. data/test/dummy/app/controllers/pings_controller.rb +40 -0
  30. data/test/dummy/app/controllers/testmod/nested/other_controller.rb +10 -0
  31. data/test/dummy/app/helpers/application_helper.rb +2 -0
  32. data/test/dummy/app/helpers/random_helper.rb +5 -0
  33. data/test/dummy/app/javascript/packs/application.js +15 -0
  34. data/test/dummy/app/jobs/application_job.rb +7 -0
  35. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  36. data/test/dummy/app/models/application_record.rb +3 -0
  37. data/test/dummy/app/models/concerns/.keep +0 -0
  38. data/test/dummy/app/views/layouts/application.html.erb +15 -0
  39. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  40. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  41. data/test/dummy/app/views/pings/ping_template.pb.pbbuilder +1 -0
  42. data/test/dummy/bin/generate +3 -0
  43. data/test/dummy/bin/rails +4 -0
  44. data/test/dummy/bin/rake +4 -0
  45. data/test/dummy/bin/setup +33 -0
  46. data/test/dummy/config.ru +6 -0
  47. data/test/dummy/config/application.rb +23 -0
  48. data/test/dummy/config/boot.rb +5 -0
  49. data/test/dummy/config/cable.yml +10 -0
  50. data/test/dummy/config/database.yml +25 -0
  51. data/test/dummy/config/environment.rb +5 -0
  52. data/test/dummy/config/environments/development.rb +76 -0
  53. data/test/dummy/config/environments/production.rb +120 -0
  54. data/test/dummy/config/environments/test.rb +59 -0
  55. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  56. data/test/dummy/config/initializers/assets.rb +12 -0
  57. data/test/dummy/config/initializers/backtrace_silencers.rb +8 -0
  58. data/test/dummy/config/initializers/content_security_policy.rb +28 -0
  59. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  60. data/test/dummy/config/initializers/filter_parameter_logging.rb +6 -0
  61. data/test/dummy/config/initializers/inflections.rb +16 -0
  62. data/test/dummy/config/initializers/mime_types.rb +4 -0
  63. data/test/dummy/config/initializers/permissions_policy.rb +11 -0
  64. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  65. data/test/dummy/config/locales/en.yml +33 -0
  66. data/test/dummy/config/puma.rb +43 -0
  67. data/test/dummy/config/routes.rb +3 -0
  68. data/test/dummy/config/storage.yml +34 -0
  69. data/test/dummy/config/twirp/routes.rb +21 -0
  70. data/test/dummy/lib/assets/.keep +0 -0
  71. data/test/dummy/log/.keep +0 -0
  72. data/test/dummy/proto/api.proto +24 -0
  73. data/test/dummy/proto/api_pb.rb +22 -0
  74. data/test/dummy/proto/api_twirp.rb +24 -0
  75. data/test/dummy/public/404.html +67 -0
  76. data/test/dummy/public/422.html +67 -0
  77. data/test/dummy/public/500.html +66 -0
  78. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  79. data/test/dummy/public/apple-touch-icon.png +0 -0
  80. data/test/dummy/public/favicon.ico +0 -0
  81. data/test/other_controller_test.rb +9 -0
  82. data/test/ping_controller_test.rb +64 -0
  83. data/test/test_helper.rb +10 -1
  84. metadata +138 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4aa4bf745efcab88ddcf8cc58f6bf76e482901d37c8736102a5c32a6f20d0892
4
- data.tar.gz: 8d6dbf635e44bf444d16d2eeaa7046a53027767745972a7add35e204ae73eaec
3
+ metadata.gz: 0ed88410115867b689de324aeaf99b91ba473d3aa573915d9bec5de1142b7986
4
+ data.tar.gz: dadc45ad3c6ed38f6791e8a45573127c69297772a7b0ae4861a2867dadf26d09
5
5
  SHA512:
6
- metadata.gz: b48547bdb5e77c0c508f1e5dcdce7b01a692d94ce79aac38f837b0397adb696a6f68d36f116e02221ed24c04152b8fb3cdb77072bfbda81fe4b5247bcf359c1e
7
- data.tar.gz: e4ed631582628257e281c78201177f351a6855f9bb9ebdfe6bb3ca0bc52349a59f56995cbcaa8c781640308bd4f032cd8fee7d5bc18191bb239082874379e2bc
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
@@ -11,3 +11,4 @@
11
11
  .byebug_history
12
12
  Gemfile.lock
13
13
  *.gem
14
+ /.idea
data/.standard.yml ADDED
@@ -0,0 +1,4 @@
1
+ ignore:
2
+ - 'test/dummy/proto/*'
3
+ - '**/*':
4
+ - Lint/RescueException
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
- # To use a debugger
8
- # gem 'byebug', group: [:development, :test]
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
@@ -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
- include AbstractController::Logger
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
- include AbstractController::Rendering
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
@@ -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 whenever a RailsTwirp::Base is initialized, and it sets the logger
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
- ActiveSupport.on_load(:rails_twirp) { prepend_view_path "app/twirp/views" }
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
- route_configs = [
59
+ [
41
60
  *app.config.paths["config/twirp/routes.rb"].existent,
42
61
  *app.config.paths["config/twirp/routes"].existent
43
- ]
44
- load(*route_configs)
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,11 @@
1
+ require "action_controller/metal/basic_implicit_render"
2
+
3
+ module RailsTwirp
4
+ module ImplicitRender
5
+ include ActionController::BasicImplicitRender
6
+
7
+ def default_render
8
+ render
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
@@ -0,0 +1,14 @@
1
+ module RailsTwirp
2
+ module Rescue
3
+ extend ActiveSupport::Concern
4
+ include ActiveSupport::Rescuable
5
+
6
+ private
7
+
8
+ def process_action(*)
9
+ super
10
+ rescue Exception => exception
11
+ rescue_with_handler(exception) || raise
12
+ end
13
+ end
14
+ end