rails_twirp 0.9.0 → 0.12.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: e1ac2840887dd15ea43114d7b0d25901c05e3288fa10d6cdcd8d00d4dabc8e74
4
- data.tar.gz: 59ff25b65ad6bd62f1ebc537bc11017da58b6ce6e1f4a74cae9e1c7f35454508
3
+ metadata.gz: '09e97be65f570cd7a2cecaaf0ef83d2ead287fd1fc8c556b833ae45395ee1c2b'
4
+ data.tar.gz: 1b30d280c8c724747b0b71ef7996ad23fd0a8e189818156602f284677ac9c637
5
5
  SHA512:
6
- metadata.gz: fd193ba67f59a313b463a2745418329d33585505164124810abdbd556ce808717064e6dde4c249cd50865951a9e1155f085adc0b556860eb1a83d5714206833a
7
- data.tar.gz: 2795b73a1cf25688ff7abea8db81af893b23374297dbd9128e6d9b642ee3bce4d4026d83d660aa3158367fd0e357987e2d71717e8b4dabe5e2b86720fbd4b74a
6
+ metadata.gz: 858ea0680c3efd03f2f1fdc766f6097b6e0a7b19ff39974412207139cd28a432d4c620a7a4f9243b63b9f70eff1919cb5a92d0c3dc6b5cd73ba99eb1349ac870
7
+ data.tar.gz: ba6311d0b7cb64755380eff9d1168d24458d5804f36acf21c7fceab2a8b0db305722353d8c5f493bf1692225f973342e3194f825d995df286802da6958631e3a
@@ -0,0 +1,12 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "bundler"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+
8
+ - package-ecosystem: "github-actions"
9
+ # Checks for workflow files stored in the default location of `.github/workflows`
10
+ directory: "/"
11
+ schedule:
12
+ interval: "weekly"
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.0.3
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ ### 0.12.0
2
+
3
+ * Allow a custom exception handling proc to be assigned using `RailsTwirp.unhandled_exception_handler`
4
+
5
+ ### 0.11.0
6
+
7
+ * Update configuration and tests for Rails 7 compatibility
8
+
9
+ ### 0.10.0
10
+
11
+ * 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,6 +4,15 @@ source "https://rubygems.org"
4
4
  gemspec
5
5
 
6
6
  gem "sqlite3"
7
- gem "pbbuilder", "~> 0.10.0"
7
+ gem "pbbuilder", "~> 0.12.0"
8
8
  gem "standard"
9
9
  gem "pry"
10
+
11
+ # HACK(bouk): Overwrite Bundler's platform matcher to ignore universal CPU
12
+ # The protobuf and gRPC 'universal' macOS gems break on M1
13
+ module Bundler::MatchPlatform
14
+ def match_platform(p)
15
+ return false if ::Gem::Platform === platform && platform.cpu == "universal"
16
+ Bundler::MatchPlatform.platforms_match?(platform, p)
17
+ end
18
+ end
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).
@@ -12,6 +12,7 @@ require "rails_twirp/rescue"
12
12
  require "rails_twirp/url_for"
13
13
  require "rails_twirp/implicit_render"
14
14
  require "rails_twirp/instrumentation"
15
+ require "rails_twirp/exception_handling"
15
16
 
16
17
  module RailsTwirp
17
18
  class Base < AbstractController::Base
@@ -36,6 +37,7 @@ module RailsTwirp
36
37
  include AbstractController::Callbacks
37
38
  include Rescue
38
39
  include Instrumentation
40
+ include ExceptionHandling
39
41
 
40
42
  attr_internal :request, :env, :response_class, :rpc_name
41
43
  def initialize
@@ -0,0 +1,36 @@
1
+ require "twirp/error"
2
+
3
+ module RailsTwirp
4
+ module ExceptionHandling
5
+ extend ActiveSupport::Concern
6
+
7
+ include AbstractController::Logger
8
+
9
+ def process_action(*)
10
+ super
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
+
17
+ # We adopt the same error handling logic as Rails' standard middlewares:
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.
21
+ raise e unless http_request.show_exceptions?
22
+
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
27
+ if http_request.get_header("action_dispatch.show_detailed_exceptions")
28
+ self.response_body = Twirp::Error.internal_with(e)
29
+ return
30
+ end
31
+
32
+ # 4. Otherwise we just return a vague internal error message
33
+ self.response_body = Twirp::Error.internal("Internal error")
34
+ end
35
+ end
36
+ end
@@ -29,6 +29,8 @@ module RailsTwirp
29
29
 
30
30
  def initialize(service_class)
31
31
  @service_class = service_class
32
+ @service_class.raise_exceptions = true
33
+
32
34
  @rpcs = {}
33
35
  end
34
36
 
@@ -100,7 +100,7 @@ module RailsTwirp
100
100
  end
101
101
 
102
102
  def set_controller_from_rack_env(env)
103
- @controller = ActionDispatch::Request.new(env).controller_class
103
+ @controller = ActionDispatch::Request.new(env).controller_instance
104
104
  end
105
105
  end
106
106
  end
@@ -1,3 +1,3 @@
1
1
  module RailsTwirp
2
- VERSION = "0.9.0"
2
+ VERSION = "0.12.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
@@ -13,6 +13,6 @@ Gem::Specification.new do |spec|
13
13
  spec.test_files = `git ls-files -- test/*`.split("\n")
14
14
 
15
15
  spec.add_dependency "rails", ">= 6.1.3"
16
- spec.add_dependency "twirp", "~> 1.7.2"
16
+ spec.add_dependency "twirp", ">= 1.7.2", "~> 1.9.0"
17
17
  spec.required_ruby_version = ">= 3"
18
18
  end
@@ -46,12 +46,56 @@ class PingControllerTest < RailsTwirp::IntegrationTest
46
46
  assert_equal :not_found, response.code
47
47
  end
48
48
 
49
- test "uncaught error" do
49
+ test "uncaught errors should bubble up to the test" do
50
+ req = RPC::DummyAPI::PingRequest.new
51
+ assert_raises StandardError, "Uncaught" do
52
+ rpc RPC::DummyAPI::DummyService, "UncaughtError", req
53
+ end
54
+ end
55
+
56
+ test "uncaught errors should return an internal error with details if show_exceptions is true" do
57
+ Rails.application.env_config["action_dispatch.show_exceptions"] = true
58
+
59
+ req = RPC::DummyAPI::PingRequest.new
60
+ rpc RPC::DummyAPI::DummyService, "UncaughtError", req
61
+ assert_instance_of Twirp::Error, response
62
+ assert_equal :internal, response.code
63
+ assert_equal "Uncaught", response.msg
64
+ assert_equal "StandardError", response.meta["cause"]
65
+ ensure
66
+ Rails.application.env_config["action_dispatch.show_exceptions"] = false
67
+ end
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
+
50
75
  req = RPC::DummyAPI::PingRequest.new
51
76
  rpc RPC::DummyAPI::DummyService, "UncaughtError", req
52
77
  assert_instance_of Twirp::Error, response
78
+ assert_equal :internal, response.code
53
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
+
87
+ test "uncaught errors should return an internal error without if show_exceptions is true and show_detailed_exceptions is false" do
88
+ Rails.application.env_config["action_dispatch.show_exceptions"] = true
89
+ Rails.application.env_config["action_dispatch.show_detailed_exceptions"] = false
90
+
91
+ req = RPC::DummyAPI::PingRequest.new
92
+ rpc RPC::DummyAPI::DummyService, "UncaughtError", req
93
+ assert_instance_of Twirp::Error, response
54
94
  assert_equal :internal, response.code
95
+ assert_equal "Internal error", response.msg
96
+ ensure
97
+ Rails.application.env_config["action_dispatch.show_detailed_exceptions"] = true
98
+ Rails.application.env_config["action_dispatch.show_exceptions"] = false
55
99
  end
56
100
 
57
101
  test "before error" do
@@ -61,4 +105,10 @@ class PingControllerTest < RailsTwirp::IntegrationTest
61
105
  assert_equal "yOuR ReQuEsT Is mAlFoRmEd", response.msg
62
106
  assert_equal :malformed, response.code
63
107
  end
108
+
109
+ test "controller is set to the controller that handled the request" do
110
+ req = RPC::DummyAPI::PingRequest.new(name: "Bouke")
111
+ rpc RPC::DummyAPI::DummyService, "Ping", req
112
+ assert_instance_of PingsController, controller
113
+ end
64
114
  end
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.9.0
4
+ version: 0.12.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: 2021-11-18 00:00:00.000000000 Z
11
+ date: 2022-01-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -28,16 +28,22 @@ dependencies:
28
28
  name: twirp
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: 1.7.2
34
+ - - "~>"
35
+ - !ruby/object:Gem::Version
36
+ version: 1.9.0
34
37
  type: :runtime
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
- - - "~>"
41
+ - - ">="
39
42
  - !ruby/object:Gem::Version
40
43
  version: 1.7.2
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 1.9.0
41
47
  description:
42
48
  email:
43
49
  - bouke@cheddar.me
@@ -45,9 +51,12 @@ executables: []
45
51
  extensions: []
46
52
  extra_rdoc_files: []
47
53
  files:
54
+ - ".github/dependabot.yml"
48
55
  - ".github/workflows/test.yml"
49
56
  - ".gitignore"
57
+ - ".ruby-version"
50
58
  - ".standard.yml"
59
+ - CHANGELOG.md
51
60
  - Gemfile
52
61
  - MIT-LICENSE
53
62
  - README.md
@@ -59,6 +68,7 @@ files:
59
68
  - lib/rails_twirp/base.rb
60
69
  - lib/rails_twirp/engine.rb
61
70
  - lib/rails_twirp/errors.rb
71
+ - lib/rails_twirp/exception_handling.rb
62
72
  - lib/rails_twirp/implicit_render.rb
63
73
  - lib/rails_twirp/instrumentation.rb
64
74
  - lib/rails_twirp/log_subscriber.rb
@@ -108,7 +118,6 @@ files:
108
118
  - test/dummy/config/environments/production.rb
109
119
  - test/dummy/config/environments/test.rb
110
120
  - test/dummy/config/initializers/application_controller_renderer.rb
111
- - test/dummy/config/initializers/assets.rb
112
121
  - test/dummy/config/initializers/backtrace_silencers.rb
113
122
  - test/dummy/config/initializers/content_security_policy.rb
114
123
  - test/dummy/config/initializers/cookies_serializer.rb
@@ -157,7 +166,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
157
166
  - !ruby/object:Gem::Version
158
167
  version: '0'
159
168
  requirements: []
160
- rubygems_version: 3.2.3
169
+ rubygems_version: 3.2.32
161
170
  signing_key:
162
171
  specification_version: 4
163
172
  summary: Integrate Twirp into Rails
@@ -200,7 +209,6 @@ test_files:
200
209
  - test/dummy/config/environments/production.rb
201
210
  - test/dummy/config/environments/test.rb
202
211
  - test/dummy/config/initializers/application_controller_renderer.rb
203
- - test/dummy/config/initializers/assets.rb
204
212
  - test/dummy/config/initializers/backtrace_silencers.rb
205
213
  - test/dummy/config/initializers/content_security_policy.rb
206
214
  - test/dummy/config/initializers/cookies_serializer.rb
@@ -1,12 +0,0 @@
1
- # Be sure to restart your server when you modify this file.
2
-
3
- # Version of your assets, change this if you want to expire all your assets.
4
- Rails.application.config.assets.version = "1.0"
5
-
6
- # Add additional assets to the asset load path.
7
- # Rails.application.config.assets.paths << Emoji.images_path
8
-
9
- # Precompile additional assets.
10
- # application.js, application.css, and all non-JS/CSS in the app/assets
11
- # folder are already added.
12
- # Rails.application.config.assets.precompile += %w( admin.js admin.css )