rails_twirp 0.9.0 → 0.12.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: 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 )