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.
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