action_controller-twirp 0.1.1 → 0.3.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: 22fa76662fce706d3efefc38981bfab8ed7cd01611e1d4de2bc8c7a8ad919e38
4
- data.tar.gz: 4479f03e25ef53f640feaaa9fb9e8c2e10e5202606f522da1949abbed88dfa5e
3
+ metadata.gz: 17541f1f7bae98a4beb49d20368f0bcbb60b5c0284e6763ead869f13bc333784
4
+ data.tar.gz: 272ddd872c4a7176abcba1b78baf20478e37f93338bc5002ef4537f21e4d8289
5
5
  SHA512:
6
- metadata.gz: b576978ee686292ac1150e876283101981094ce919e2d0c00bc202d7e5177badad3a9f29655aa3f7a6eed158e1ff02bbce89c8fb4998300cf4ba419f8a51729e
7
- data.tar.gz: b28a534f96ace98a8f2daad7bbe2e90f72cbce3533ad5133fadfa49aa0d4ab07006b649e79f0252e43be331868091255af951ae6934103a8659244684602dc4f
6
+ metadata.gz: 8b8ea6ac39900c875734119e47552d0425bb18e49e8ca878d30901e38ff5293cf2f59729220bef4184670dc80086d951cf055cfbdfdaa0d5f42d562139acde74
7
+ data.tar.gz: 3314d20c05db3d93fd07d75b840baec2b60f3d5a581e3e11e76df1da87d15e520a015cd4dbd426a6c3b5e3eba5f89da5c5ddffbd6a6ce4e6787c3cde740822ea
data/README.md CHANGED
@@ -29,7 +29,12 @@ It provides routing method `twirp` that maps twirp service class and Rails contr
29
29
 
30
30
  First, you should generate twirp service class. see: [example in twirp-ruby](https://github.com/twitchtv/twirp-ruby/wiki#usage-example)
31
31
 
32
- Next, add twirp action in config/routes.rb
32
+ Next, install initializer.
33
+ ```sh
34
+ bin/rails g action_controller:twirp:install
35
+ ```
36
+
37
+ Next, add twirp action in config/routes.rb.
33
38
  ```ruby
34
39
  Rails.application.routes.draw do
35
40
  scope :twirp do
@@ -38,7 +43,7 @@ Rails.application.routes.draw do
38
43
  end
39
44
  ```
40
45
 
41
- Result of `bin/rails routes`
46
+ Result of `bin/rails routes`.
42
47
  ```sh
43
48
  > bin/rails routes
44
49
  Prefix Verb URI Pattern Controller#Action
@@ -46,18 +51,11 @@ Result of `bin/rails routes`
46
51
  POST /twirp/example.v1.UserApi/GetUser(.:format) users#get_user
47
52
  ```
48
53
 
49
- Finally, implement rpc method your controller
54
+ Finally, implement rpc method your controller.
50
55
  ```ruby
51
- class UsersController < ApplicationController # :nodoc:
56
+ class UsersController < ApplicationController
52
57
  include ActionController::Twirp
53
58
 
54
- rescue_from ActiveRecord::RecordNotFound do |e|
55
- twerr = Twirp::Error.not_found('The message',
56
- reason: e.class.name.demodulize,
57
- id: params[:id].to_s)
58
- render twirp_error: twerr
59
- end
60
-
61
59
  USERS = [
62
60
  { id: 1, name: 'Anna' },
63
61
  { id: 2, name: 'Reina' }
@@ -77,6 +75,88 @@ class UsersController < ApplicationController # :nodoc:
77
75
  end
78
76
  ```
79
77
 
78
+ ### Error handling
79
+
80
+ We must follow protocol when using Twirp. Specifically,
81
+
82
+ - It accepts request parameters specified in proto file, and must return response body.
83
+ - If an exception occurs, a response body of Twirp::Error's JSON must be returned.
84
+
85
+ This section describes when an exception occurs.
86
+
87
+ The following blocks are in the order of method calls when requested to the Rails app. (It's quite simplified)
88
+ ```
89
+ :
90
+ ActionController::Metal#dispatch
91
+ AbstractController::Base#process
92
+ :
93
+ ActionController::Rescue#process_action
94
+ ActionController::Callbacks#process_action
95
+ :
96
+ (Some module's #process_action is called)
97
+ :
98
+ AbstractController::Base#process_action
99
+ AbstractController::Base#send_action
100
+ ```
101
+
102
+ The gem is only override `#send_action`. It follows twirp protocal in this one.
103
+
104
+ For example, if an exception occurs in the callback, Rails app will return a response, so we must be implemented so that the twirp protocol can be followed.
105
+
106
+ This is easy to do, see below.
107
+
108
+ First, modify `exception_codes` config.
109
+ ```sh
110
+ > cat config/initializers/action_controller_twirp.rb
111
+ # frozen_string_literal: true
112
+
113
+ ActionController::Twirp::Config.setup do |config|
114
+ # Mapping your exception classes and Twirp::Error::ERROR_CODES
115
+ # String => Symbol
116
+ config.exception_codes = {
117
+ 'ActiveRecord::RecordInvalid' => :invalid_argument,
118
+ 'ActiveRecord::RecordNotFound' => :not_found,
119
+ 'MyApp::Unauthenticated' => :unauthenticated,
120
+ }
121
+
122
+ # Block to make Twirp::Error message when exception_codes exist
123
+ # config.build_message = ->(exception) {}
124
+
125
+ # Block to make Twirp::Error metadata. when exception_codes exist
126
+ # It MUST return Hash value
127
+ # config.build_metadata = ->(exception) {}
128
+
129
+ # Block to run additional process. e.g. logging
130
+ # config.on_exceptions = ->(exception) {}
131
+ end
132
+ ```
133
+
134
+ Next, write a `rescue_from` block as usual and use the `:twirp_error` key on `#render`
135
+ ```ruby
136
+ class UsersController < ApplicationController
137
+ include ActionController::Twirp
138
+
139
+ before_action :authenticate!
140
+
141
+ rescue_from MyApp::Unauthenticated do |e|
142
+ render twirp_error: e
143
+ end
144
+
145
+ :
146
+ :
147
+
148
+ private
149
+
150
+ def authenticate!
151
+ return if your_logic
152
+
153
+ raise MyApp::Unauthenticated
154
+ end
155
+ end
156
+ ```
157
+
158
+ See [dummy app controller](test/dummy/app/controllers/users_controller.rb) and [integration test](test/integration/users_test.rb) for details.
159
+
80
160
  ## Contributing
81
161
  Contribution directions go here.
82
162
 
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/module/attribute_accessors'
4
+
5
+ module ActionController
6
+ module Twirp
7
+ # Configration for ActionController::Twirp
8
+ module Config
9
+ # Mapping your exception classes and Twirp::Error::ERROR_CODES
10
+ mattr_accessor :exception_codes, default: {}
11
+
12
+ # Block to make Twirp::Error message
13
+ mattr_accessor :build_message, default: ->(exception) {}
14
+
15
+ # Block to make Twirp::Error metadata
16
+ mattr_accessor :build_metadata, default: ->(exception) {}
17
+
18
+ # Block to run additional process. e.g. logging
19
+ mattr_accessor :on_exceptions, default: ->(exception) {}
20
+
21
+ def self.setup
22
+ yield self
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_controller/twirp/config'
4
+
5
+ module ActionController
6
+ module Twirp
7
+ # Class to generate Twirp::Error from an exception class and Config
8
+ class Error
9
+ # @param content [Exception] given as a value of :twirp_error key of render method.
10
+ def initialize(exception)
11
+ unless exception.is_a?(Exception)
12
+ raise ArgumentError, 'Instance of Exception class can be specified'
13
+ end
14
+
15
+ @exception = exception
16
+ end
17
+
18
+ def generate
19
+ if Config.exception_codes.key?(exception_name)
20
+ code = Config.exception_codes[exception_name]
21
+ message = Config.build_message.call(exception)
22
+ metadata = Config.build_metadata.call(exception)
23
+
24
+ ::Twirp::Error.public_send(code, message, metadata)
25
+ else
26
+ ::Twirp::Error.internal_with(exception)
27
+ end
28
+ rescue StandardError => e
29
+ ::Twirp::Error.internal_with(e)
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :exception
35
+
36
+ def exception_name
37
+ exception.class.to_s
38
+ end
39
+ end
40
+ end
41
+ end
@@ -3,12 +3,12 @@
3
3
  module ActionController
4
4
  module Twirp
5
5
  # Convert Twirp::Error to #render option
6
- class TwirpErrorRenderer
7
- # @param content [Twirp::Error] given as a value of :twirp_error key of render method.
6
+ class ErrorRenderer
7
+ # @param content [Exception] given as a value of :twirp_error key of render method.
8
8
  # @param options [Hash] options of render method.
9
9
  def initialize(content, options)
10
- unless content.is_a?(::Twirp::Error)
11
- raise ArgumentError, 'Only Twirp::Error instance can be specified'
10
+ unless content.is_a?(Exception)
11
+ raise ArgumentError, 'Instance of Exception class can be specified'
12
12
  end
13
13
 
14
14
  @content = content
@@ -24,16 +24,10 @@ module ActionController
24
24
 
25
25
  private
26
26
 
27
- def twirp_error
28
- return @twirp_error if defined? @twirp_error
29
-
30
- code = @content.code
27
+ attr_reader :content, :options
31
28
 
32
- @twirp_error = if ::Twirp::Error.valid_code?(code)
33
- @content
34
- else
35
- ::Twirp::Error.internal("Invalid code: #{code}", invalid_code: code.to_s)
36
- end
29
+ def twirp_error
30
+ @twirp_error ||= Error.new(content).generate
37
31
  end
38
32
 
39
33
  def status
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'action_controller/twirp/twirp_error_renderer'
3
+ require 'action_controller/twirp/error_renderer'
4
4
  require 'action_dispatch/twirp/railtie'
5
5
 
6
6
  module ActionController
@@ -9,7 +9,7 @@ module ActionController
9
9
  initializer 'action_controller.twirp' do
10
10
  ActiveSupport.on_load(:action_controller) do
11
11
  ActionController::Renderers.add :twirp_error do |content, options|
12
- renderer = TwirpErrorRenderer.new(content, options)
12
+ renderer = ErrorRenderer.new(content, options)
13
13
  render(**renderer.to_render_option)
14
14
  end
15
15
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActionController
4
4
  module Twirp
5
- VERSION = '0.1.1'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'action_controller/twirp/version'
4
4
  require 'action_controller/twirp/railtie'
5
+ require 'action_controller/twirp/config'
6
+ require 'action_controller/twirp/error'
5
7
 
6
8
  module ActionController
7
9
  # Module to execute your implemented methods on Twirp with Rails
@@ -12,7 +14,7 @@ module ActionController
12
14
  def send_action(*_args)
13
15
  twirp_service_class.raise_exceptions = true
14
16
 
15
- status, header, body = twirp_service_class.new(self)&.call(request.env)
17
+ status, header, body = twirp_action
16
18
 
17
19
  response.status = status
18
20
  response.header.merge!(header)
@@ -25,5 +27,21 @@ module ActionController
25
27
  class_name = request.path.split('/')[-2].underscore.gsub('.', '/').classify
26
28
  @twirp_service_class = "#{class_name}Service".constantize
27
29
  end
30
+
31
+ def twirp_action
32
+ twirp_service_class.new(self)&.call(request.env)
33
+ rescue StandardError => e
34
+ begin
35
+ Config.on_exceptions.call(e)
36
+ rescue StandardError => hook_e
37
+ e = hook_e
38
+ end
39
+ twirp_error_response(e)
40
+ end
41
+
42
+ def twirp_error_response(exception)
43
+ twerr = Error.new(exception).generate
44
+ twirp_service_class.error_response(twerr)
45
+ end
28
46
  end
29
47
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ module Twirp
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base # :nodoc:
7
+ source_root File.expand_path('../../templates', __dir__)
8
+
9
+ desc 'Creates a ActionController::Twirp initializer'
10
+ def copy_initializer
11
+ template 'action_controller_twirp.rb', 'config/initializers/action_controller_twirp.rb'
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActionController::Twirp::Config.setup do |config|
4
+ # Mapping your exception classes and Twirp::Error::ERROR_CODES
5
+ # String => Symbol
6
+ # config.exception_codes = {
7
+ # 'ActiveRecord::RecordInvalid' => :invalid_argument,
8
+ # 'ActiveRecord::RecordNotFound' => :not_found,
9
+ # 'My::Exception' => :aborted,
10
+ # }
11
+
12
+ # Block to make Twirp::Error message when exception_codes exist
13
+ # config.build_message = ->(exception) {}
14
+
15
+ # Block to make Twirp::Error metadata. when exception_codes exist
16
+ # It MUST return Hash value
17
+ # config.build_metadata = ->(exception) {}
18
+
19
+ # Block to run additional process. e.g. logging
20
+ # config.on_exceptions = ->(exception) {}
21
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_controller-twirp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kosuke Arisawa
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-21 00:00:00.000000000 Z
11
+ date: 2022-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -63,11 +63,15 @@ files:
63
63
  - README.md
64
64
  - Rakefile
65
65
  - lib/action_controller/twirp.rb
66
+ - lib/action_controller/twirp/config.rb
67
+ - lib/action_controller/twirp/error.rb
68
+ - lib/action_controller/twirp/error_renderer.rb
66
69
  - lib/action_controller/twirp/railtie.rb
67
- - lib/action_controller/twirp/twirp_error_renderer.rb
68
70
  - lib/action_controller/twirp/version.rb
69
71
  - lib/action_dispatch/routing/mapper/twirp.rb
70
72
  - lib/action_dispatch/twirp/railtie.rb
73
+ - lib/generators/action_controller/twirp/install_generator.rb
74
+ - lib/generators/templates/action_controller_twirp.rb
71
75
  - lib/tasks/action_controller/twirp_tasks.rake
72
76
  homepage: https://github.com/arisawa/action_controller-twirp
73
77
  licenses: