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 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