rails_twirp 0.11.0 → 0.13.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9dc494fd35fa5a920f6ca55c6a024f73bda616a10cc91336fefb3d7520f9c3ed
4
- data.tar.gz: 5da02487fde28997da7fb200c0a9693fc952fc5662a2e43864d0a12c788ec7d5
3
+ metadata.gz: 6af836779332d2bcf1810379884b7ef9e219f63e81b2972b501a45ae1880f1cb
4
+ data.tar.gz: b0a967815e9326fa7889fdf1f3ec77b08f835921b8a919cee5cc95150407aa20
5
5
  SHA512:
6
- metadata.gz: e67eba63003648f4ba6cbc604e4e2006783c5f2873e95745f56bb1f9cfea0660d1f55850b4128b088728f829ef41989c634839b6168d5a512480e465631a60e0
7
- data.tar.gz: 163e1cd73f34ed7ee62a531c71b6a5153c35231b731645ceba25e22f37e45d94f0a51fc1ff220928eb33d2297964f108cb71696779ea958cd80dc0e4410fe462
6
+ metadata.gz: 139e2f8517c0e1b2664b0d264d4722ebdc74fde78bfdb9724ea03d31d80ab99eec66a304a25bfaec23e17ed032b5396406742993ebabc2fc464c3eea338dd5bd
7
+ data.tar.gz: 4df604dc7f1772238a2ea95b3e573aa352b354a58a92f9caa157add1889ba59dd95d8bbab9283dbf9632b807514c28fc21eca913b03f0bb254d57eadfbef200b
@@ -6,7 +6,7 @@ jobs:
6
6
  runs-on: ubuntu-latest
7
7
  steps:
8
8
  - name: Checkout code
9
- uses: actions/checkout@v2
9
+ uses: actions/checkout@v3
10
10
 
11
11
  - name: Setup Ruby
12
12
  uses: ruby/setup-ruby@v1
data/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ ### 0.13.0
2
+ * Adding #controller_name methods to Metal/Base controller (used for instrumentation)
3
+ * Include `ActionController::Caching` with Base controller/helpers
4
+
5
+
6
+ ### 0.12.0
7
+
8
+ * Allow a custom exception handling proc to be assigned using `RailsTwirp.unhandled_exception_handler`
9
+
10
+ ### 0.11.0
11
+
12
+ * Update configuration and tests for Rails 7 compatibility
13
+
14
+ ### 0.10.0
15
+
16
+ * Handle exceptions more like the Rails controllers do it, do not capture all of them as if they were Twirp exceptions
data/Gemfile CHANGED
@@ -4,7 +4,7 @@ source "https://rubygems.org"
4
4
  gemspec
5
5
 
6
6
  gem "sqlite3"
7
- gem "pbbuilder", "~> 0.12.0"
7
+ gem "pbbuilder"
8
8
  gem "standard"
9
9
  gem "pry"
10
10
 
data/README.md CHANGED
@@ -1,8 +1,56 @@
1
1
  # RailsTwirp
2
- Short description and motivation.
2
+
3
+ Allows for effortless integration of [Twirp](https://github.com/twitchtv/twirp) endpoints into your Rails application. RailsTwirp permits you to define your Twirp endpoints using the familiar vocabulary of Rails routes, controllers, actions and views.
4
+
5
+ Using an extra routes file you map your Twirp RPCs to controllers, and define your views using [pbbuilder.](https://github.com/cheddar-me/pbbuilder) Even though you are now serving Twirp RPC endpoints instead of HTML or JSON, all the skills and tools you have for working your usual Rails infrastructure apply. `TwirpRails` provides a customised controller superclass called `RailsTwirp::Base` which includes all the relevant ActionController components which still make sense when using Protobufs.
3
6
 
4
7
  ## Usage
5
- How to use my plugin.
8
+
9
+ Add the gem to your project (see "Installation" below). Then add a file called `config/twirp/routes.rb` to your application, and map your RPCs inside of that file. We will map a `GreetWorld` RPC call as an example:
10
+
11
+ ```ruby
12
+ Rails.application.twirp.routes.draw do
13
+ service HelloService, module: :api do
14
+ rpc "GreetWorld", to: "greetings#greet"
15
+ end
16
+ end
17
+ ```
18
+
19
+ The `module` defines the module which will contain your `TwirpRails` controller. The `HelloService` module is your Protobuf mapping generated using Twirp, it inherits from `Twirp::Service` and could look like this:
20
+
21
+ ```ruby
22
+ class HelloService < Twirp::Service
23
+ package 'api'
24
+ service 'App'
25
+ rpc :GreetWorld, GreetWorldRequest, GreetWorldResponse, :ruby_method => :greet_world
26
+ end
27
+ ```
28
+
29
+ The `GreetWorldRequest` in this case is the autogenerated protobuf request class, the `GreetWorldResponse` is the response class.
30
+
31
+ Then you define your controller, which looks like a standard Rails controller - just with a different base class:
32
+
33
+ ```ruby
34
+ class API::GreetingsController < RailsTwirp::Base
35
+ def greet
36
+ render pb: API::GreetResponse.new
37
+ end
38
+ end
39
+ ```
40
+
41
+ Note that the controller is routed using the same naming conventions as Rails. Inside of a `TwirpRails` controller you can do most of the things you are used to from Rails controllers, such as renders and implicit renders - which have to be `pbbuilder` views. If our `GreetWorldResponse` has a `greeting` field, it can be filled inside the `pbbuilder` view like so:
42
+
43
+ ```ruby
44
+ pb.greeting "Hello stranger"
45
+ ```
46
+
47
+ Inside the controllers you have access to the call parameters of your RPC call using `request`:
48
+
49
+ ```ruby
50
+ def greet
51
+ request.name #=> # Returns the value of the "name" field from the request Protobuf
52
+ end
53
+ ```
6
54
 
7
55
  ## Installation
8
56
  Add this line to your application's Gemfile:
@@ -37,7 +85,14 @@ end
37
85
  ```
38
86
 
39
87
  ## Contributing
40
- Contribution directions go here.
88
+
89
+ * Check out the latest `main` to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
90
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
91
+ * Fork the project.
92
+ * Start a feature/bugfix branch.
93
+ * Commit and push until you are happy with your contribution.
94
+ * Make sure to add tests for your change. This is important so we don't break it in a future version unintentionally.
95
+ * Please try not to mess with the Rakefile, version, or history.
41
96
 
42
97
  ## License
43
98
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "abstract_controller/base"
2
4
  require "abstract_controller/rendering"
3
5
  require "action_view/rendering"
4
6
  require "rails_twirp/render_pb"
5
7
  require "rails_twirp/errors"
6
8
  require "abstract_controller/asset_paths"
7
- require "abstract_controller/caching"
8
9
  require "abstract_controller/logger"
9
10
  require "abstract_controller/callbacks"
10
11
  require "action_controller/metal/helpers"
@@ -13,11 +14,10 @@ require "rails_twirp/url_for"
13
14
  require "rails_twirp/implicit_render"
14
15
  require "rails_twirp/instrumentation"
15
16
  require "rails_twirp/exception_handling"
17
+ require "rails_twirp/metal"
16
18
 
17
19
  module RailsTwirp
18
- class Base < AbstractController::Base
19
- abstract!
20
-
20
+ class Base < RailsTwirp::Metal
21
21
  # The order of these includes matter.
22
22
  # The rendering modules extend each other, so need to be in this order.
23
23
  include AbstractController::Rendering
@@ -26,7 +26,6 @@ module RailsTwirp
26
26
  include ActionController::Helpers
27
27
  include UrlFor
28
28
  include AbstractController::AssetPaths
29
- include AbstractController::Caching
30
29
 
31
30
  include ActionView::Rendering
32
31
  include RenderPb
@@ -39,7 +38,14 @@ module RailsTwirp
39
38
  include Instrumentation
40
39
  include ExceptionHandling
41
40
 
42
- attr_internal :request, :env, :response_class, :rpc_name
41
+ ##
42
+ # :attr_reader: request
43
+ #
44
+ # The ActionDispatch::Request instance for the current request.
45
+ attr_internal :request
46
+
47
+ attr_internal :env, :response_class, :rpc_name
48
+
43
49
  def initialize
44
50
  @_request = nil
45
51
  @_env = nil
@@ -34,7 +34,11 @@ module RailsTwirp
34
34
 
35
35
  initializer "rails_twirp.helpers" do |app|
36
36
  ActiveSupport.on_load(:rails_twirp) do
37
- # Load all the application helpers into the controller
37
+ # Load all the application helpers into the controller.
38
+ # Note that helpers need to be set up here, because apparently
39
+ # the AbstractController::Helpers module won't be able to find
40
+ # the _helpers method on a reloaded controller
41
+ include ActionController::Caching
38
42
  include app.routes.mounted_helpers
39
43
  extend ::AbstractController::Railties::RoutesHelpers.with(app.routes, false)
40
44
  extend ::ActionController::Railties::Helpers
@@ -9,17 +9,27 @@ module RailsTwirp
9
9
  def process_action(*)
10
10
  super
11
11
  rescue Exception => e
12
+ # Only the exceptions which are not captured by ActionController-like "rescue_from" end up here.
13
+ # The idea is that any exception which is rescued by the controller is treated as part of the business
14
+ # logic, and thus taking action on it is the responsibility of the controller which uses "rescue_from".
15
+ # If an exception ends up here it means it wasn't captured by the handlers defined in the controller.
16
+
12
17
  # We adopt the same error handling logic as Rails' standard middlewares:
13
18
  # 1. When we 'show exceptions' we make the exception bubble up—this is useful for testing
19
+ # If the exception gets raised here error reporting will happen in the middleware of the APM package
20
+ # higher in the call stack.
14
21
  raise e unless http_request.show_exceptions?
15
22
 
16
- # 2. When we want to show detailed exceptions we include the exception message in the error
23
+ # 2. We report the error to the error tracking service, this needs to be configured.
24
+ RailsTwirp.unhandled_exception_handler&.call(e)
25
+
26
+ # 3. When we want to show detailed exceptions we include the exception message in the error
17
27
  if http_request.get_header("action_dispatch.show_detailed_exceptions")
18
28
  self.response_body = Twirp::Error.internal_with(e)
19
29
  return
20
30
  end
21
31
 
22
- # 3. Otherwise we just return a vague internal error message
32
+ # 4. Otherwise we just return a vague internal error message
23
33
  self.response_body = Twirp::Error.internal("Internal error")
24
34
  end
25
35
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsTwirp
4
+ # This is a simplest possible controller, providing a valid
5
+ # Rack interfacee without the additional niceties provided by RailsTwirp.
6
+ #
7
+ # Idiologially, it's similar to Rails version of ActionController::Metal
8
+ class Metal < AbstractController::Base
9
+ abstract!
10
+
11
+ # Returns the last part of the controller's name, underscored, without the ending
12
+ # <tt>Controller</tt>. For instance, PostsController returns <tt>posts</tt>.
13
+ # Namespaces are left out, so Admin::PostsController returns <tt>posts</tt> as well.
14
+ #
15
+ # ==== Returns
16
+ # * <tt>string</tt>
17
+ def self.controller_name
18
+ @controller_name ||= (name.demodulize.delete_suffix("Controller").underscore unless anonymous?)
19
+ end
20
+
21
+ # Delegates to the class's ::controller_name.
22
+ def controller_name
23
+ self.class.controller_name
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module RailsTwirp
2
- VERSION = "0.11.0"
2
+ VERSION = "0.13.0"
3
3
  end
data/lib/rails_twirp.rb CHANGED
@@ -8,6 +8,7 @@ require "rails_twirp/log_subscriber"
8
8
 
9
9
  module RailsTwirp
10
10
  mattr_accessor :test_app
11
+ mattr_accessor :unhandled_exception_handler
11
12
  end
12
13
 
13
14
  require "rails_twirp/engine" if defined?(Rails)
data/rails_twirp.gemspec CHANGED
@@ -10,7 +10,6 @@ Gem::Specification.new do |spec|
10
10
  spec.license = "MIT"
11
11
 
12
12
  spec.files = `git ls-files`.split("\n")
13
- spec.test_files = `git ls-files -- test/*`.split("\n")
14
13
 
15
14
  spec.add_dependency "rails", ">= 6.1.3"
16
15
  spec.add_dependency "twirp", ">= 1.7.2", "~> 1.9.0"
@@ -1 +1,3 @@
1
- pb.rpc_name @rpc_name
1
+ pb.cache! "rpc-#{@rpc_name}", expire_in: 1.minute do
2
+ pb.rpc_name @rpc_name
3
+ end
@@ -66,6 +66,24 @@ class PingControllerTest < RailsTwirp::IntegrationTest
66
66
  Rails.application.env_config["action_dispatch.show_exceptions"] = false
67
67
  end
68
68
 
69
+ test "uncaught errors should be fanned out to the exception handler proc if one is defined" do
70
+ Rails.application.env_config["action_dispatch.show_exceptions"] = true
71
+
72
+ captured_exception = nil
73
+ RailsTwirp.unhandled_exception_handler = ->(e) { captured_exception = e }
74
+
75
+ req = RPC::DummyAPI::PingRequest.new
76
+ rpc RPC::DummyAPI::DummyService, "UncaughtError", req
77
+ assert_instance_of Twirp::Error, response
78
+ assert_equal :internal, response.code
79
+ assert_equal "Uncaught", response.msg
80
+ assert_equal "StandardError", response.meta["cause"]
81
+ assert_kind_of StandardError, captured_exception
82
+ ensure
83
+ RailsTwirp.unhandled_exception_handler = nil
84
+ Rails.application.env_config["action_dispatch.show_exceptions"] = false
85
+ end
86
+
69
87
  test "uncaught errors should return an internal error without if show_exceptions is true and show_detailed_exceptions is false" do
70
88
  Rails.application.env_config["action_dispatch.show_exceptions"] = true
71
89
  Rails.application.env_config["action_dispatch.show_detailed_exceptions"] = false
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_twirp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bouke van der Bijl
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-12 00:00:00.000000000 Z
11
+ date: 2023-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -56,6 +56,7 @@ files:
56
56
  - ".gitignore"
57
57
  - ".ruby-version"
58
58
  - ".standard.yml"
59
+ - CHANGELOG.md
59
60
  - Gemfile
60
61
  - MIT-LICENSE
61
62
  - README.md
@@ -72,6 +73,7 @@ files:
72
73
  - lib/rails_twirp/instrumentation.rb
73
74
  - lib/rails_twirp/log_subscriber.rb
74
75
  - lib/rails_twirp/mapper.rb
76
+ - lib/rails_twirp/metal.rb
75
77
  - lib/rails_twirp/render_pb.rb
76
78
  - lib/rails_twirp/rescue.rb
77
79
  - lib/rails_twirp/route_set.rb
@@ -169,71 +171,4 @@ rubygems_version: 3.2.32
169
171
  signing_key:
170
172
  specification_version: 4
171
173
  summary: Integrate Twirp into Rails
172
- test_files:
173
- - test/dummy/Rakefile
174
- - test/dummy/app/assets/config/manifest.js
175
- - test/dummy/app/assets/images/.keep
176
- - test/dummy/app/assets/stylesheets/application.css
177
- - test/dummy/app/channels/application_cable/channel.rb
178
- - test/dummy/app/channels/application_cable/connection.rb
179
- - test/dummy/app/controllers/application_controller.rb
180
- - test/dummy/app/controllers/application_twirp_controller.rb
181
- - test/dummy/app/controllers/concerns/.keep
182
- - test/dummy/app/controllers/dummy_controller.rb
183
- - test/dummy/app/controllers/pings_controller.rb
184
- - test/dummy/app/controllers/testmod/nested/other_controller.rb
185
- - test/dummy/app/helpers/application_helper.rb
186
- - test/dummy/app/helpers/random_helper.rb
187
- - test/dummy/app/javascript/packs/application.js
188
- - test/dummy/app/jobs/application_job.rb
189
- - test/dummy/app/mailers/application_mailer.rb
190
- - test/dummy/app/models/application_record.rb
191
- - test/dummy/app/models/concerns/.keep
192
- - test/dummy/app/views/dummy/rpc_name_check.pb.pbbuilder
193
- - test/dummy/app/views/layouts/application.html.erb
194
- - test/dummy/app/views/layouts/mailer.html.erb
195
- - test/dummy/app/views/layouts/mailer.text.erb
196
- - test/dummy/app/views/pings/ping_template.pb.pbbuilder
197
- - test/dummy/bin/generate
198
- - test/dummy/bin/rails
199
- - test/dummy/bin/rake
200
- - test/dummy/bin/setup
201
- - test/dummy/config.ru
202
- - test/dummy/config/application.rb
203
- - test/dummy/config/boot.rb
204
- - test/dummy/config/cable.yml
205
- - test/dummy/config/database.yml
206
- - test/dummy/config/environment.rb
207
- - test/dummy/config/environments/development.rb
208
- - test/dummy/config/environments/production.rb
209
- - test/dummy/config/environments/test.rb
210
- - test/dummy/config/initializers/application_controller_renderer.rb
211
- - test/dummy/config/initializers/backtrace_silencers.rb
212
- - test/dummy/config/initializers/content_security_policy.rb
213
- - test/dummy/config/initializers/cookies_serializer.rb
214
- - test/dummy/config/initializers/filter_parameter_logging.rb
215
- - test/dummy/config/initializers/inflections.rb
216
- - test/dummy/config/initializers/mime_types.rb
217
- - test/dummy/config/initializers/permissions_policy.rb
218
- - test/dummy/config/initializers/wrap_parameters.rb
219
- - test/dummy/config/locales/en.yml
220
- - test/dummy/config/puma.rb
221
- - test/dummy/config/routes.rb
222
- - test/dummy/config/storage.yml
223
- - test/dummy/config/twirp/routes.rb
224
- - test/dummy/lib/assets/.keep
225
- - test/dummy/log/.keep
226
- - test/dummy/proto/api.proto
227
- - test/dummy/proto/api_pb.rb
228
- - test/dummy/proto/api_twirp.rb
229
- - test/dummy/public/404.html
230
- - test/dummy/public/422.html
231
- - test/dummy/public/500.html
232
- - test/dummy/public/apple-touch-icon-precomposed.png
233
- - test/dummy/public/apple-touch-icon.png
234
- - test/dummy/public/favicon.ico
235
- - test/dummy_test.rb
236
- - test/other_controller_test.rb
237
- - test/ping_controller_test.rb
238
- - test/rails_twirp_test.rb
239
- - test/test_helper.rb
174
+ test_files: []