rails_twirp 0.11.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
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: []