twirp_rails 0.4.3 → 0.4.4

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: c1fe8510dbe0b5f3334dd440ea3f6b5685a81ce8cca14f349e5151fd9da75dd7
4
- data.tar.gz: ab737943d90ec1e0a691d9b08c7c9803690de1e907c19bfbda7ba3b63274439d
3
+ metadata.gz: 974aa187c0fa4e596194362b3aafc3b34e12baacd05b502a1a09bb808cf3ab0c
4
+ data.tar.gz: 9f121868824a0cda89c27a8e77d163640ae3b2b23f714c7122353abe2d39bd65
5
5
  SHA512:
6
- metadata.gz: 32f77264c8398dc7249c89a979655a6cd37e812669b2e1fd24778afcd2ec6ea11cd33636ddb711df4c2954f8c500371552cc3ca800e9f750c1bd6b9dcc1d4a8a
7
- data.tar.gz: 04a932ed0cb583d0505741bb1be9c0c8f0c4a5d7e9dc86d8d4229bbe2dccbc00fad311f8b9cb5cca774b01a07981ea7116dba68bcf52aa9509c5a545885cbebd
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- twirp_rails (0.4.3)
4
+ twirp_rails (0.4.4)
5
5
  railties (~> 6.0)
6
6
  twirp (~> 1)
7
7
 
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,2 @@
1
+ require 'twirp_rails/error_handling/base'
2
+ require 'twirp_rails/error_handling/error_handling_factory'
@@ -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
@@ -25,7 +25,7 @@ module TwirpRails
25
25
  raise 'twirp service name required'
26
26
  end
27
27
 
28
- service = service_class.new(handler.new)
28
+ service = service_class.new(ErrorHandlingFactory.wrap_handler(handler.new))
29
29
  Helper.run_create_hooks service
30
30
 
31
31
  if scope
@@ -65,7 +65,7 @@ module TwirpRails
65
65
  end
66
66
 
67
67
  included do
68
- let(:handler) { described_class.new }
68
+ let(:handler) { TwirpRails::ErrorHandlingFactory.wrap_handler(described_class.new) }
69
69
  let(:service) { service_class.new(handler) }
70
70
  end
71
71
  end
@@ -1,3 +1,3 @@
1
1
  module TwirpRails
2
- VERSION = '0.4.3'.freeze
2
+ VERSION = '0.4.4'.freeze
3
3
  end
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.3
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-04-14 00:00:00.000000000 Z
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