rubybear 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +77 -0
  4. data/LICENSE +41 -0
  5. data/README.md +559 -0
  6. data/Rakefile +140 -0
  7. data/gource.sh +8 -0
  8. data/images/Anthony Martin.png +0 -0
  9. data/images/Marvin Hofmann.jpg +0 -0
  10. data/images/Marvin Hofmann.png +0 -0
  11. data/lib/bears.rb +17 -0
  12. data/lib/rubybear.rb +45 -0
  13. data/lib/rubybear/account_by_key_api.rb +7 -0
  14. data/lib/rubybear/account_history_api.rb +15 -0
  15. data/lib/rubybear/api.rb +907 -0
  16. data/lib/rubybear/base_error.rb +23 -0
  17. data/lib/rubybear/block_api.rb +14 -0
  18. data/lib/rubybear/broadcast_operations.json +500 -0
  19. data/lib/rubybear/chain.rb +299 -0
  20. data/lib/rubybear/chain_config.rb +22 -0
  21. data/lib/rubybear/chain_stats_api.rb +15 -0
  22. data/lib/rubybear/condenser_api.rb +99 -0
  23. data/lib/rubybear/database_api.rb +5 -0
  24. data/lib/rubybear/error_parser.rb +228 -0
  25. data/lib/rubybear/follow_api.rb +7 -0
  26. data/lib/rubybear/logger.rb +20 -0
  27. data/lib/rubybear/market_history_api.rb +19 -0
  28. data/lib/rubybear/methods.json +498 -0
  29. data/lib/rubybear/mixins/acts_as_poster.rb +124 -0
  30. data/lib/rubybear/mixins/acts_as_voter.rb +50 -0
  31. data/lib/rubybear/mixins/acts_as_wallet.rb +67 -0
  32. data/lib/rubybear/network_broadcast_api.rb +7 -0
  33. data/lib/rubybear/operation.rb +101 -0
  34. data/lib/rubybear/operation_ids.rb +98 -0
  35. data/lib/rubybear/operation_types.rb +139 -0
  36. data/lib/rubybear/stream.rb +527 -0
  37. data/lib/rubybear/tag_api.rb +33 -0
  38. data/lib/rubybear/transaction.rb +306 -0
  39. data/lib/rubybear/type/amount.rb +57 -0
  40. data/lib/rubybear/type/array.rb +17 -0
  41. data/lib/rubybear/type/beneficiaries.rb +29 -0
  42. data/lib/rubybear/type/future.rb +18 -0
  43. data/lib/rubybear/type/hash.rb +17 -0
  44. data/lib/rubybear/type/permission.rb +19 -0
  45. data/lib/rubybear/type/point_in_time.rb +19 -0
  46. data/lib/rubybear/type/price.rb +25 -0
  47. data/lib/rubybear/type/public_key.rb +18 -0
  48. data/lib/rubybear/type/serializer.rb +12 -0
  49. data/lib/rubybear/type/u_int16.rb +19 -0
  50. data/lib/rubybear/type/u_int32.rb +19 -0
  51. data/lib/rubybear/utils.rb +170 -0
  52. data/lib/rubybear/version.rb +4 -0
  53. data/rubybear.gemspec +40 -0
  54. metadata +412 -0
@@ -0,0 +1,299 @@
1
+ module Rubybear
2
+ # Examples ...
3
+ #
4
+ # To vote on a post/comment:
5
+ #
6
+ # bears = Rubybear::Chain.new(chain: :bears, account_name: 'your account name', wif: 'your wif')
7
+ # bears.vote!(10000, 'author', 'post-or-comment-permlink')
8
+ #
9
+ # To post and vote in the same transaction:
10
+ #
11
+ # bears = Rubybear::Chain.new(chain: :bears, account_name: 'your account name', wif: 'your wif')
12
+ # bears.post!(title: 'title of my post', body: 'body of my post', tags: ['tag'], self_upvote: 10000)
13
+ #
14
+ # To post and vote with declined payout:
15
+ #
16
+ # bears = Rubybear::Chain.new(chain: :bears, account_name: 'your account name', wif: 'your wif')
17
+ #
18
+ # options = {
19
+ # title: 'title of my post',
20
+ # body: 'body of my post',
21
+ # tags: ['tag'],
22
+ # self_upvote: 10000,
23
+ # percent_bears_dollars: 0
24
+ # }
25
+ #
26
+ # bears.post!(options)
27
+ #
28
+ class Chain
29
+ include Mixins::ActsAsPoster
30
+ include Mixins::ActsAsVoter
31
+ include Mixins::ActsAsWallet
32
+
33
+ VALID_OPTIONS = %w(
34
+ chain account_name wif url failover_urls
35
+ ).map(&:to_sym)
36
+ VALID_OPTIONS.each { |option| attr_accessor option }
37
+
38
+ def self.parse_slug(*args)
39
+ args = [args].flatten
40
+
41
+ if args.size == 1
42
+ case args[0]
43
+ when String then split_slug(args[0])
44
+ when ::Hash then [args[0]['author'], args[0]['permlink']]
45
+ end
46
+ else
47
+ args
48
+ end
49
+ end
50
+
51
+ def initialize(options = {})
52
+ options = options.dup
53
+ options.each do |k, v|
54
+ k = k.to_sym
55
+ if VALID_OPTIONS.include?(k.to_sym)
56
+ options.delete(k)
57
+ send("#{k}=", v)
58
+ end
59
+ end
60
+
61
+ @account_name ||= ENV['ACCOUNT_NAME']
62
+ @wif ||= ENV['WIF']
63
+
64
+ reset
65
+ end
66
+
67
+ # Find a specific block by block number.
68
+ #
69
+ # Example:
70
+ #
71
+ # bears = Rubybear::Chain.new(chain: :bears)
72
+ # block = bears.find_block(12345678)
73
+ # transactions = block.transactions
74
+ #
75
+ # @param block_number [Fixnum]
76
+ # @return [::Hash]
77
+ def find_block(block_number)
78
+ api.get_blocks(block_number).first
79
+ end
80
+
81
+ # Find a specific account by name.
82
+ #
83
+ # Example:
84
+ #
85
+ # bears = Rubybear::Chain.new(chain: :bears)
86
+ # ned = bears.find_account('ned')
87
+ # coining_shares = ned.coining_shares
88
+ #
89
+ # @param account_name [String] Name of the account to find.
90
+ # @return [::Hash]
91
+ def find_account(account_name)
92
+ api.get_accounts([account_name]) do |accounts, err|
93
+ raise ChainError, ErrorParser.new(err) if !!err
94
+
95
+ accounts[0]
96
+ end
97
+ end
98
+
99
+ # Find a specific comment by author and permlink or slug.
100
+ #
101
+ # Example:
102
+ #
103
+ # bears = Rubybear::Chain.new(chain: :bears)
104
+ # comment = bears.find_comment('inertia', 'kinda-spooky') # by account, permlink
105
+ # active_votes = comment.active_votes
106
+ #
107
+ # ... or ...
108
+ #
109
+ # comment = bears.find_comment('@inertia/kinda-spooky') # by slug
110
+ #
111
+ # @param args [String || ::Array<String>] Slug or author, permlink of comment.
112
+ # @return [::Hash]
113
+ def find_comment(*args)
114
+ author, permlink = Chain.parse_slug(args)
115
+
116
+ api.get_content(author, permlink) do |comment, err|
117
+ raise ChainError, ErrorParser.new(err) if !!err
118
+
119
+ comment unless comment.id == 0
120
+ end
121
+ end
122
+
123
+ # Current dynamic global properties, cached for 3 seconds. This is useful
124
+ # for reading properties without worrying about actually fetching it over
125
+ # rpc more than needed.
126
+ def properties
127
+ @properties ||= nil
128
+
129
+ if !!@properties && Time.now.utc - Time.parse(@properties.time + 'Z') > 3
130
+ @properties = nil
131
+ end
132
+
133
+ return @properties if !!@properties
134
+
135
+ api.get_dynamic_global_properties do |properties|
136
+ @properties = properties
137
+ end
138
+ end
139
+
140
+ def block_time
141
+ Time.parse(properties.time + 'Z')
142
+ end
143
+
144
+ # Returns the current base (e.g. BEARS) price in the coin asset (e.g.
145
+ # COINS).
146
+ #
147
+ def base_per_mcoin
148
+ total_coining_fund_bears = properties.total_coining_fund_bears.to_f
149
+ total_coining_shares_mcoin = properties.total_coining_shares.to_f / 1e6
150
+
151
+ total_coining_fund_bears / total_coining_shares_mcoin
152
+ end
153
+
154
+ # Returns the current base (e.g. BEARS) price in the debt asset (e.g BSD).
155
+ #
156
+ def base_per_debt
157
+ api.get_feed_history do |feed_history|
158
+ current_median_history = feed_history.current_median_history
159
+ base = current_median_history.base
160
+ base = base.split(' ').first.to_f
161
+ quote = current_median_history.quote
162
+ quote = quote.split(' ').first.to_f
163
+
164
+ (base / quote) * base_per_mcoin
165
+ end
166
+ end
167
+
168
+ # List of accounts followed by account.
169
+ #
170
+ # @param account_name String Name of the account.
171
+ # @return [::Array<String>]
172
+ def followed_by(account_name)
173
+ return [] if account_name.nil?
174
+
175
+ followers = []
176
+ count = -1
177
+
178
+ until count == followers.size
179
+ count = followers.size
180
+ follow_api.get_followers(account: account_name, start: followers.last, type: 'blog', limit: 1000) do |follows, err|
181
+ raise ChainError, ErrorParser.new(err) if !!err
182
+
183
+ followers += follows.map(&:follower)
184
+ followers = followers.uniq
185
+ end
186
+ end
187
+
188
+ followers
189
+ end
190
+
191
+ # List of accounts following account.
192
+ #
193
+ # @param account_name String Name of the account.
194
+ # @return [::Array<String>]
195
+ def following(account_name)
196
+ return [] if account_name.nil?
197
+
198
+ following = []
199
+ count = -1
200
+
201
+ until count == following.size
202
+ count = following.size
203
+ follow_api.get_following(account: account_name, start: following.last, type: 'blog', limit: 100) do |follows, err|
204
+ raise ChainError, ErrorParser.new(err) if !!err
205
+
206
+ following += follows.map(&:following)
207
+ following = following.uniq
208
+ end
209
+ end
210
+
211
+ following
212
+ end
213
+
214
+ # Clears out queued properties.
215
+ def reset_properties
216
+ @properties = nil
217
+ end
218
+
219
+ # Clears out queued operations.
220
+ def reset_operations
221
+ @operations = []
222
+ end
223
+
224
+ # Clears out all properties and operations.
225
+ def reset
226
+ reset_properties
227
+ reset_operations
228
+
229
+ @api = @block_api = @follow_api = nil
230
+ end
231
+
232
+ # Broadcast queued operations.
233
+ #
234
+ # @param auto_reset [boolean] clears operations no matter what, even if there's an error.
235
+ def broadcast!(auto_reset = false)
236
+ raise ChainError, "Required option: chain" if @chain.nil?
237
+ raise ChainError, "Required option: account_name, wif" if @account_name.nil? || @wif.nil?
238
+
239
+ begin
240
+ transaction = Rubybear::Transaction.new(build_options)
241
+ transaction.operations = @operations
242
+ response = transaction.process(true)
243
+ rescue => e
244
+ reset if auto_reset
245
+ raise e
246
+ end
247
+
248
+ if !!response.result
249
+ reset
250
+ response
251
+ else
252
+ reset if auto_reset
253
+ ErrorParser.new(response)
254
+ end
255
+ end
256
+ private
257
+ def self.split_slug(slug)
258
+ slug = slug.split('@').last
259
+ author = slug.split('/')[0]
260
+ permlink = slug.split('/')[1..-1].join('/')
261
+ permlink = permlink.split('#')[0]
262
+
263
+ [author, permlink]
264
+ end
265
+
266
+ def build_options
267
+ {
268
+ chain: chain,
269
+ wif: wif,
270
+ url: url,
271
+ failover_urls: failover_urls
272
+ }
273
+ end
274
+
275
+ def api
276
+ @api ||= Api.new(build_options)
277
+ end
278
+
279
+ def block_api
280
+ @block_api ||= BlockApi.new(build_options)
281
+ end
282
+
283
+ def follow_api
284
+ @follow_api ||= FollowApi.new(build_options)
285
+ end
286
+
287
+ def default_max_acepted_payout
288
+ "1000000.000 #{default_debt_asset}"
289
+ end
290
+
291
+ def default_debt_asset
292
+ case chain
293
+ when :bears then ChainConfig::NETWORKS_BEARS_DEBT_ASSET
294
+ when :test then ChainConfig::NETWORKS_TEST_DEBT_ASSET
295
+ else; raise ChainError, "Unknown chain: #{chain}"
296
+ end
297
+ end
298
+ end
299
+ end
@@ -0,0 +1,22 @@
1
+ module Rubybear
2
+ module ChainConfig
3
+ EXPIRE_IN_SECS = 600
4
+ EXPIRE_IN_SECS_PROPOSAL = 24 * 60 * 60
5
+
6
+ NETWORKS_BEARS_CHAIN_ID = 'b510834141c312c2aa8837040734605f2333f1ecc4f634576372f9c12dc7e8b2'
7
+ NETWORKS_BEARS_ADDRESS_PREFIX = 'SHR'
8
+ NETWORKS_BEARS_CORE_ASSET = 'BEARS'
9
+ NETWORKS_BEARS_DEBT_ASSET = 'BSD'
10
+ NETWORKS_BEARS_COIN_ASSET = 'COINS'
11
+ NETWORKS_BEARS_DEFAULT_NODE = 'https://api.bearshares.com'
12
+
13
+ NETWORKS_TEST_CHAIN_ID = '18dcf0a285365fc58b71f18b3d3fec954aa0c141c44e4e5cb4cf777b9eab274e'
14
+ NETWORKS_TEST_ADDRESS_PREFIX = 'TST'
15
+ NETWORKS_TEST_CORE_ASSET = 'CORE'
16
+ NETWORKS_TEST_DEBT_ASSET = 'TEST'
17
+ NETWORKS_TEST_COIN_ASSET = 'CESTS'
18
+ NETWORKS_TEST_DEFAULT_NODE = 'https://test.bears.ws'
19
+
20
+ NETWORK_CHAIN_IDS = [NETWORKS_BEARS_CHAIN_ID, NETWORKS_TEST_CHAIN_ID]
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ module Rubybear
2
+ class ChainStatsApi < Api
3
+ def method_names
4
+ @method_names ||= [
5
+ :get_stats_for_time,
6
+ :get_stats_for_interval,
7
+ :get_lifetime_stats
8
+ ].freeze
9
+ end
10
+
11
+ def api_name
12
+ :chain_stats_api
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,99 @@
1
+ module Rubybear
2
+ class CondenserApi < Api
3
+ METHOD_NAMES = [
4
+ :broadcast_block,
5
+ :broadcast_transaction,
6
+ :broadcast_transaction_synchronous,
7
+ :get_account_bandwidth,
8
+ :get_account_count,
9
+ :get_account_history,
10
+ :get_account_references,
11
+ :get_account_reputations,
12
+ :get_account_votes,
13
+ :get_accounts,
14
+ :get_active_votes,
15
+ :get_active_witnesses,
16
+ :get_block,
17
+ :get_block_header,
18
+ :get_blog,
19
+ :get_blog_authors,
20
+ :get_blog_entries,
21
+ :get_chain_properties,
22
+ :get_comment_discussions_by_payout,
23
+ :get_config,
24
+ :get_content,
25
+ :get_content_replies,
26
+ :get_conversion_requests,
27
+ :get_current_median_history_price,
28
+ :get_discussions_by_active,
29
+ :get_discussions_by_author_before_date,
30
+ :get_discussions_by_blog,
31
+ :get_discussions_by_cashout,
32
+ :get_discussions_by_children,
33
+ :get_discussions_by_comments,
34
+ :get_discussions_by_created,
35
+ :get_discussions_by_feed,
36
+ :get_discussions_by_hot,
37
+ :get_discussions_by_promoted,
38
+ :get_discussions_by_trending,
39
+ :get_discussions_by_votes,
40
+ :get_dynamic_global_properties,
41
+ :get_escrow,
42
+ :get_expiring_coining_delegations,
43
+ :get_feed,
44
+ :get_feed_entries,
45
+ :get_feed_history,
46
+ :get_follow_count,
47
+ :get_followers,
48
+ :get_following,
49
+ :get_hardfork_version,
50
+ :get_key_references,
51
+ :get_market_history,
52
+ :get_market_history_buckets,
53
+ :get_next_scheduled_hardfork,
54
+ :get_open_orders,
55
+ :get_ops_in_block,
56
+ :get_order_book,
57
+ :get_owner_history,
58
+ :get_post_discussions_by_payout,
59
+ :get_potential_signatures,
60
+ :get_reblogged_by,
61
+ :get_recent_trades,
62
+ :get_recovery_request,
63
+ :get_replies_by_last_update,
64
+ :get_required_signatures,
65
+ :get_reward_fund,
66
+ :get_savings_withdraw_from,
67
+ :get_savings_withdraw_to,
68
+ :get_state,
69
+ :get_tags_used_by_author,
70
+ :get_ticker,
71
+ :get_trade_history,
72
+ :get_transaction,
73
+ :get_transaction_hex,
74
+ :get_trending_tags,
75
+ :get_version,
76
+ :get_coining_delegations,
77
+ :get_volume,
78
+ :get_withdraw_routes,
79
+ :get_witness_by_account,
80
+ :get_witness_count,
81
+ :get_witness_schedule,
82
+ :get_witnesses,
83
+ :get_witnesses_by_vote,
84
+ :lookup_account_names,
85
+ :lookup_accounts,
86
+ :lookup_witness_accounts,
87
+ :verify_account_authority,
88
+ :verify_authority
89
+ ].freeze
90
+
91
+ def method_names
92
+ METHOD_NAMES
93
+ end
94
+
95
+ def api_name
96
+ :condenser_api
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,5 @@
1
+ module Rubybear
2
+ # @see Api
3
+ class DatabaseApi < Api
4
+ end
5
+ end
@@ -0,0 +1,228 @@
1
+ module Rubybear
2
+ class ErrorParser
3
+ include Utils
4
+
5
+ attr_reader :response, :error, :error_code, :error_message,
6
+ :api_name, :api_method, :api_params,
7
+ :expiry, :can_retry, :can_reprepare, :node_degraded, :trx_id, :debug
8
+
9
+ alias expiry? expiry
10
+ alias can_retry? can_retry
11
+ alias can_reprepare? can_reprepare
12
+ alias node_degraded? node_degraded
13
+
14
+ REPREPARE_WHITELIST = [
15
+ 'is_canonical( c ): signature is not canonical',
16
+ 'now < trx.expiration: '
17
+ ]
18
+
19
+ DUPECHECK = '(skip & skip_transaction_dupe_check) || trx_idx.indices().get<by_trx_id>().find(trx_id) == trx_idx.indices().get<by_trx_id>().end(): Duplicate transaction check failed'
20
+
21
+ REPREPARE_BLACKLIST = [DUPECHECK]
22
+
23
+ def initialize(response)
24
+ @response = response
25
+
26
+ @error = nil
27
+ @error_code = nil
28
+ @error_message = nil
29
+ @api_name = nil
30
+ @api_method = nil
31
+ @api_params = nil
32
+
33
+ @expiry = nil
34
+ @can_retry = nil
35
+ @can_reprepare = nil
36
+ @trx_id = nil
37
+ @debug = nil
38
+
39
+ parse_error_response
40
+ end
41
+
42
+ def parse_error_response
43
+ if response.nil?
44
+ @expiry = false
45
+ @can_retry = false
46
+ @can_reprepare = false
47
+
48
+ return
49
+ end
50
+
51
+ @response = JSON[response] if response.class == String
52
+
53
+ @error = if !!@response['error']
54
+ response['error']
55
+ else
56
+ response
57
+ end
58
+
59
+ begin
60
+ if !!@error['data']
61
+ # These are, by far, the more interesting errors, so we try to pull
62
+ # them out first, if possible.
63
+
64
+ @error_code = @error['data']['code']
65
+ stacks = @error['data']['stack']
66
+ stack_formats = nil
67
+
68
+ @error_message = if !!stacks
69
+ stack_formats = stacks.map { |s| s['format'] }
70
+ stack_datum = stacks.map { |s| s['data'] }
71
+ data_call_method = stack_datum.find { |data| data['call.method'] == 'call' }
72
+ data_name = stack_datum.find { |data| !!data['name'] }
73
+
74
+ # See if we can recover a transaction id out of this hot mess.
75
+ data_trx_ix = stack_datum.find { |data| !!data['trx_ix'] }
76
+ @trx_id = data_trx_ix['trx_ix'] if !!data_trx_ix
77
+
78
+ stack_formats.reject(&:empty?).join('; ')
79
+ else
80
+ @error_code ||= @error['code']
81
+ @error['message']
82
+ end
83
+
84
+ @api_name, @api_method, @api_params = if !!data_call_method
85
+ @api_name = data_call_method['call.params']
86
+ end
87
+ else
88
+ @error_code = @error['code']
89
+ @error_message = @error['message']
90
+ @expiry = false
91
+ @can_retry = false
92
+ @can_reprepare = false
93
+ end
94
+
95
+ case @error_code
96
+ when -32603
97
+ if error_match?('Internal Error')
98
+ @expiry = false
99
+ @can_retry = true
100
+ @can_reprepare = true
101
+ end
102
+ when -32003
103
+ if error_match?('Unable to acquire database lock')
104
+ @expiry = false
105
+ @can_retry = true
106
+ @can_reprepare = true
107
+ end
108
+ when -32000
109
+ @expiry = false
110
+ @can_retry = coerce_backtrace
111
+ @can_reprepare = if @api_name == 'network_broadcast_api'
112
+ error_match(REPREPARE_WHITELIST)
113
+ else
114
+ false
115
+ end
116
+ when 10
117
+ @expiry = false
118
+ @can_retry = coerce_backtrace
119
+ @can_reprepare = !!stack_formats && (stack_formats & REPREPARE_WHITELIST).any?
120
+ when 13
121
+ @error_message = @error['data']['message']
122
+ @expiry = false
123
+ @can_retry = false
124
+ @can_reprepare = false
125
+ when 3030000
126
+ @error_message = @error['data']['message']
127
+ @expiry = false
128
+ @can_retry = false
129
+ @can_reprepare = false
130
+ when 4030100
131
+ # Code 4030100 is "transaction_expiration_exception: transaction
132
+ # expiration exception". If we assume the expiration was valid, the
133
+ # node might be bad and needs to be dropped.
134
+
135
+ @expiry = true
136
+ @can_retry = true
137
+ @can_reprepare = false
138
+ when 4030200
139
+ # Code 4030200 is "transaction tapos exception". They are recoverable
140
+ # if the transaction hasn't expired yet. A tapos exception can be
141
+ # retried in situations where the node is behind and the tapos is
142
+ # based on a block the node doesn't know about yet.
143
+
144
+ @expiry = false
145
+ @can_retry = true
146
+
147
+ # Allow fall back to reprepare if retry fails.
148
+ @can_reprepare = true
149
+ else
150
+ @expiry = false
151
+ @can_retry = false
152
+ @can_reprepare = false
153
+ end
154
+ rescue => e
155
+ if defined? ap
156
+ if ENV['DEBUG'] == 'true'
157
+ ap error_parser_exception: e, original_response: response, backtrace: e.backtrace
158
+ else
159
+ ap error_parser_exception: e, original_response: response
160
+ end
161
+ end
162
+
163
+ @expiry = false
164
+ @can_retry = false
165
+ @can_reprepare = false
166
+ end
167
+ end
168
+
169
+ def coerce_backtrace
170
+ can_retry = false
171
+
172
+ case @error['code']
173
+ when -32003
174
+ any_of = [
175
+ 'Internal Error"',
176
+ '_api_plugin not enabled.'
177
+ ]
178
+
179
+ can_retry = error_match?('Unable to acquire database lock')
180
+
181
+ if !can_retry && error_match?(any_of)
182
+ can_retry = true
183
+ @node_degraded = true
184
+ else
185
+ @node_degraded = false
186
+ end
187
+ when -32002
188
+ can_retry = @node_degraded = error_match?('Could not find API')
189
+ when 1
190
+ can_retry = @node_degraded = error_match?('no method with name \'condenser_api')
191
+ end
192
+
193
+ can_retry
194
+ end
195
+
196
+ def error_match?(matches)
197
+ matches = [matches].flatten
198
+
199
+ any = matches.map do |match|
200
+ case match
201
+ when String
202
+ @error['message'] && @error['message'].include?(match)
203
+ when ::Array
204
+ if @error['message']
205
+ match.map { |m| m.include?(match) }.include? true
206
+ else
207
+ false
208
+ end
209
+ else; false
210
+ end
211
+ end
212
+
213
+ any.include?(true)
214
+ end
215
+
216
+ def to_s
217
+ if !!error_message && !error_message.empty?
218
+ "#{error_code}: #{error_message}"
219
+ else
220
+ error_code.to_s
221
+ end
222
+ end
223
+
224
+ def inspect
225
+ "#<#{self.class.name} [#{to_s}]>"
226
+ end
227
+ end
228
+ end