twirp_rails 0.4.3 → 0.4.4
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 +4 -4
- data/CHANGELOG.md +18 -0
- data/Gemfile.lock +1 -1
- data/README.md +48 -0
- data/lib/twirp_rails.rb +9 -0
- data/lib/twirp_rails/error_handling/base.rb +114 -0
- data/lib/twirp_rails/error_handling/error_handling.rb +2 -0
- data/lib/twirp_rails/error_handling/error_handling_factory.rb +82 -0
- data/lib/twirp_rails/generators/twirp/init/templates/twirp_rails.rb +14 -0
- data/lib/twirp_rails/routes.rb +1 -1
- data/lib/twirp_rails/rspec/helper.rb +1 -1
- data/lib/twirp_rails/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 974aa187c0fa4e596194362b3aafc3b34e12baacd05b502a1a09bb808cf3ab0c
|
4
|
+
data.tar.gz: 9f121868824a0cda89c27a8e77d163640ae3b2b23f714c7122353abe2d39bd65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8898c1ae5bae4e1f024a69a5a29ac71a1130d8198fc376f18a6647f49228b2bbfb9d9b998f16e967a5036c8543e6e373374822e440685b0d6bb14033ecc359b1
|
7
|
+
data.tar.gz: 6515db4669525602ad53d090af51239ede7a7e2980981704751d210d6c8541e0405c1e8428dc0a13d9267e0140de95db4f93989c09b002185cb060e622627ac7
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
+
## 0.4.4 - 2020-05-05
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
- ability to translate twirp errors to exceptions and vise versa.
|
12
|
+
|
13
|
+
## 0.4.3 - 2020-04-14
|
14
|
+
|
15
|
+
### Fixed
|
16
|
+
|
17
|
+
- autorequire all ruby files from `lib/twirp_clients` folder.
|
18
|
+
|
19
|
+
## 0.4.2 - 2020-04-01
|
20
|
+
|
21
|
+
### Added
|
22
|
+
|
23
|
+
- `to_twirp` extension use model method unless attribute found
|
24
|
+
|
7
25
|
## 0.4.1 - 2020-03-30
|
8
26
|
|
9
27
|
### Added
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -9,6 +9,7 @@ TwirpRails helps to use [twirp-ruby gem](https://github.com/twitchtv/twirp-ruby)
|
|
9
9
|
* `mount_twirp` route helper to mount handlers
|
10
10
|
* `rpc` helper to dry your handlers specs
|
11
11
|
* ability to log twirp calls by Rails logger
|
12
|
+
* DSL to translate handler exceptions to twirp errors and client twirp errors to ruby exceptions
|
12
13
|
|
13
14
|
## Installation
|
14
15
|
|
@@ -151,6 +152,53 @@ generated by
|
|
151
152
|
$ rails g twirp:init
|
152
153
|
```
|
153
154
|
|
155
|
+
## Exception handling
|
156
|
+
|
157
|
+
Translate handler exceptions to twirp errors and client twirp errors to ruby exceptions.
|
158
|
+
|
159
|
+
This feature allow to use ruby style exception handling flow in ruby code.
|
160
|
+
|
161
|
+
Create class to describe translate error rules
|
162
|
+
```ruby
|
163
|
+
class ErrorTranslator < TwirpRails::ErrorHandling::Base
|
164
|
+
# rules to translate exception raised by handler to twirp
|
165
|
+
translate_exception ArgumentError, with: :invalid_argument
|
166
|
+
translate_exception ActiveRecord::NotFound do |exception, handler|
|
167
|
+
Twirp::Error.not_found
|
168
|
+
end
|
169
|
+
|
170
|
+
# rules to translate twirp errors returned from client to exception
|
171
|
+
translate_error :invalid_argument, with: ArgumentError
|
172
|
+
translate_error :not_found do |error, client|
|
173
|
+
ActiveRecord::NotFound.new(error.msg)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
And configure TwirpRails to use it:
|
179
|
+
```ruby
|
180
|
+
# config/initializers/twirp_ruby.rb
|
181
|
+
# ...
|
182
|
+
config.twirp_exception_translator_class = 'ErrorTranslator'
|
183
|
+
# ...
|
184
|
+
```
|
185
|
+
|
186
|
+
Services mounted by ```mount_twirp``` translates raised exception to twirp errors by this rules.
|
187
|
+
|
188
|
+
To raise errors by clients, you should create client by `TwirpRails.client`
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
client = TwirpRails.client(PeopleClient, 'http://localhost:3000/twirp')
|
192
|
+
|
193
|
+
client.get_name(uid: 'not found').error
|
194
|
+
# Twirp::Error code:not_found msg:"Couldn't find User with 'uid'='not found'"
|
195
|
+
client.get_name(uid: 'not found').data.name # NoMethodError: undefined method `name' for nil:NilClass
|
196
|
+
|
197
|
+
# use bang method to raise translated error and didn't check each twirp call result error
|
198
|
+
client.get_name!(uid: 'not found').data.name # ActiveRecord::NotFound: undefined method `name' for nil:NilClass
|
199
|
+
```
|
200
|
+
|
201
|
+
|
154
202
|
## API acronym
|
155
203
|
|
156
204
|
Gem adds inflector acronym `API` to correct using services with suffix `API`.
|
data/lib/twirp_rails.rb
CHANGED
@@ -5,6 +5,7 @@ require 'twirp_rails/engine'
|
|
5
5
|
require 'twirp_rails/generators/generators'
|
6
6
|
require 'twirp_rails/active_record_extension'
|
7
7
|
require 'twirp_rails/log_subscriber'
|
8
|
+
require 'twirp_rails/error_handling/error_handling'
|
8
9
|
|
9
10
|
module TwirpRails
|
10
11
|
class Error < StandardError; end
|
@@ -61,6 +62,8 @@ module TwirpRails
|
|
61
62
|
config_param :purge_old_twirp_code, true
|
62
63
|
|
63
64
|
config_param :add_api_acronym, true
|
65
|
+
|
66
|
+
config_param :twirp_exception_translator_class, nil
|
64
67
|
end
|
65
68
|
|
66
69
|
def self.configuration
|
@@ -119,4 +122,10 @@ module TwirpRails
|
|
119
122
|
|
120
123
|
Time.zone.at seconds
|
121
124
|
end
|
125
|
+
|
126
|
+
def self.client(klass, url)
|
127
|
+
client = klass.new(url)
|
128
|
+
|
129
|
+
TwirpRails::ErrorHandling.wrap_client(client)
|
130
|
+
end
|
122
131
|
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'twirp'
|
2
|
+
|
3
|
+
module TwirpRails
|
4
|
+
module ErrorHandling
|
5
|
+
class Base
|
6
|
+
ExceptionHandler = Struct.new(:exception, :proc) do
|
7
|
+
def match?(exception)
|
8
|
+
exception.is_a?(self.exception)
|
9
|
+
end
|
10
|
+
|
11
|
+
def handle(exception, handler)
|
12
|
+
proc.call(exception, handler)
|
13
|
+
end
|
14
|
+
|
15
|
+
def process(exception, handler)
|
16
|
+
match?(exception) ? handle(exception, handler) : nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
ErrorHandler = Struct.new(:code, :proc) do
|
21
|
+
def match?(error)
|
22
|
+
error.code == self.code
|
23
|
+
end
|
24
|
+
|
25
|
+
def handle(error, client)
|
26
|
+
proc.call(error, client)
|
27
|
+
end
|
28
|
+
|
29
|
+
def process(error, client)
|
30
|
+
match?(error) ? handle(error, client) : nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class << self
|
35
|
+
# translate_exception InvalidArgument, :invalid_argument
|
36
|
+
#
|
37
|
+
# translate_exception StandardError { |exception, service| Twirp::Error.internal_with exception }
|
38
|
+
def translate_exception(*exceptions, with: nil, &block)
|
39
|
+
raise 'unexpected with and block' if block_given? && with
|
40
|
+
raise 'with or block must be defined' unless block_given? || with
|
41
|
+
|
42
|
+
|
43
|
+
proc = if with
|
44
|
+
raise "invalid twirp code #{with}" unless ::Twirp::ERROR_CODES.include?(with)
|
45
|
+
|
46
|
+
proc do |exception, _service|
|
47
|
+
::Twirp::Error.new(with, exception.message).tap { |t| t.cause = exception }
|
48
|
+
end
|
49
|
+
else
|
50
|
+
proc { |exception, service| block.call(exception, service) }
|
51
|
+
end
|
52
|
+
|
53
|
+
exceptions.each do |exception|
|
54
|
+
exception_handlers << ExceptionHandler.new(exception, proc)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# translate_error :invalid_argument, InvalidArgument
|
59
|
+
#
|
60
|
+
# translate_error :internal_error { |error, client| StandardError.new(error.msg) }
|
61
|
+
def translate_error(*codes, with: nil, &block)
|
62
|
+
raise 'unexpected with and block' if block_given? && with.nil?
|
63
|
+
raise 'with or block must be defined' unless block_given? || !with.nil?
|
64
|
+
raise "invalid twirp code(s) #{codes - ::Twirp::ERROR_CODES}" if (codes - ::Twirp::ERROR_CODES).any?
|
65
|
+
|
66
|
+
proc = if with
|
67
|
+
raise 'with should be a exception class' unless with.is_a?(Class)
|
68
|
+
|
69
|
+
proc { |error, _client| with.new error.msg }
|
70
|
+
else
|
71
|
+
proc { |error, client| block.call(error, client) }
|
72
|
+
end
|
73
|
+
|
74
|
+
codes.each do |code|
|
75
|
+
error_handlers << ErrorHandler.new(code, proc)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def exception_handlers
|
80
|
+
@exception_handlers ||= []
|
81
|
+
end
|
82
|
+
|
83
|
+
def error_handlers
|
84
|
+
@error_handlers ||= []
|
85
|
+
end
|
86
|
+
|
87
|
+
def exception_to_twirp(exception, handler)
|
88
|
+
result = nil
|
89
|
+
|
90
|
+
exception_handlers.take_while do |exception_handler|
|
91
|
+
result = exception_handler.process(exception, handler)
|
92
|
+
|
93
|
+
result.nil?
|
94
|
+
end
|
95
|
+
|
96
|
+
result || ::Twirp::Error.internal_with(exception)
|
97
|
+
end
|
98
|
+
|
99
|
+
def twirp_to_exception(error, client)
|
100
|
+
result = nil
|
101
|
+
|
102
|
+
error_handlers.take_while do |handler|
|
103
|
+
result = handler.process(error, client)
|
104
|
+
|
105
|
+
result.nil?
|
106
|
+
end
|
107
|
+
|
108
|
+
result || StandardError.new(error.msg)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module TwirpRails
|
2
|
+
class ErrorHandlingFactory
|
3
|
+
class HandlerProxy
|
4
|
+
attr_reader :handler, :translator_class
|
5
|
+
|
6
|
+
def initialize(handler, translator_class)
|
7
|
+
@handler = handler
|
8
|
+
@translator_class = translator_class
|
9
|
+
end
|
10
|
+
|
11
|
+
# rubocop:disable Style/MethodMissingSuper
|
12
|
+
def method_missing(method, *args)
|
13
|
+
handler.public_send method, *args
|
14
|
+
rescue => e
|
15
|
+
translator_class.exception_to_twirp(e, handler)
|
16
|
+
end
|
17
|
+
# rubocop:enable Style/MethodMissingSuper
|
18
|
+
|
19
|
+
def respond_to_missing?(method)
|
20
|
+
handler.respond_to?(method)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class ClientProxy
|
25
|
+
attr_reader :client, :translator_class
|
26
|
+
|
27
|
+
def initialize(client, translator_class)
|
28
|
+
@client = client
|
29
|
+
@translator_class = translator_class
|
30
|
+
end
|
31
|
+
|
32
|
+
def raise_on_error(twirp_result)
|
33
|
+
if twirp_result.error
|
34
|
+
exception = translator_class.twirp_to_exception(twirp_result.error)
|
35
|
+
raise exception
|
36
|
+
else
|
37
|
+
twirp_result
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# rubocop:disable Style/MethodMissingSuper
|
42
|
+
def method_missing(method, *args)
|
43
|
+
if method =~ /!$/
|
44
|
+
# when we call a bang version of client method - raise exception translated from error
|
45
|
+
method = method[0..-2]
|
46
|
+
raise_on_error client.public_send(method, args)
|
47
|
+
else
|
48
|
+
client.public_send method, args
|
49
|
+
end
|
50
|
+
end
|
51
|
+
# rubocop:enable Style/MethodMissingSuper
|
52
|
+
|
53
|
+
def respond_to_missing?(method)
|
54
|
+
handler.respond_to?(method)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class << self
|
59
|
+
attr_reader :translator_class
|
60
|
+
|
61
|
+
def wrap_handler(handler)
|
62
|
+
enable_handling? ? HandlerProxy.new(handler, translator_class) : handler
|
63
|
+
end
|
64
|
+
|
65
|
+
def wrap_client(client)
|
66
|
+
enable_handling? ? ClientProxy.new(client, translator_class) : client
|
67
|
+
end
|
68
|
+
|
69
|
+
def enable_handling?
|
70
|
+
if @enable_handling.nil?
|
71
|
+
if (@translator_class = TwirpRails.configuration.twirp_exception_translator_class)
|
72
|
+
@translator_class = @translator_class.constantize unless @translator_class.is_a?(Class)
|
73
|
+
@enable_handling = true
|
74
|
+
else
|
75
|
+
@enable_handling = false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
@enable_handling
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -25,4 +25,18 @@ TwirpRails.configure do |config|
|
|
25
25
|
#
|
26
26
|
# Add API acronym to inflector
|
27
27
|
# config.add_api_acronym = true
|
28
|
+
#
|
29
|
+
# Translate exceptions to Twirp::Error on services and
|
30
|
+
# Twirp::Error to exceptions. To use service translation you
|
31
|
+
# should mount service via mount_twirp. To use client translation
|
32
|
+
# create client via TwirpRails.client(ClientClass, url) and
|
33
|
+
# call bang methods. E.g. client.hello! throws exception if returns error.
|
34
|
+
#
|
35
|
+
# class SampleTranslator < TwirpRails::ErrorHandling::Base
|
36
|
+
# translate_exception Mongoid::Errors::DocumentNotFound, with: :not_found
|
37
|
+
# translate_error :not_found, with: Mongoid::Errors::DocumentNotFound
|
38
|
+
# end
|
39
|
+
# config.twirp_exception_translator_class = 'SampleTranslator'
|
40
|
+
# default:
|
41
|
+
# config.twirp_exception_translator_class = nil
|
28
42
|
end
|
data/lib/twirp_rails/routes.rb
CHANGED
data/lib/twirp_rails/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: twirp_rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexandr Zimin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-05-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: twirp
|
@@ -142,6 +142,9 @@ files:
|
|
142
142
|
- lib/twirp_rails.rb
|
143
143
|
- lib/twirp_rails/active_record_extension.rb
|
144
144
|
- lib/twirp_rails/engine.rb
|
145
|
+
- lib/twirp_rails/error_handling/base.rb
|
146
|
+
- lib/twirp_rails/error_handling/error_handling.rb
|
147
|
+
- lib/twirp_rails/error_handling/error_handling_factory.rb
|
145
148
|
- lib/twirp_rails/generators/generators.rb
|
146
149
|
- lib/twirp_rails/generators/twirp/USAGE
|
147
150
|
- lib/twirp_rails/generators/twirp/clients/USAGE
|