telegem 3.3.1 → 3.4.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.
@@ -0,0 +1,435 @@
1
+ # Error Handling
2
+
3
+ Comprehensive error handling is crucial for robust Telegram bots. Telegem provides multiple layers of error handling with recovery strategies.
4
+
5
+ ## Error Types
6
+
7
+ ### API Errors
8
+
9
+ Telegram API errors from invalid requests or server issues.
10
+
11
+ ```ruby
12
+ begin
13
+ bot.api.call('sendMessage', invalid_params)
14
+ rescue Telegem::API::APIError => e
15
+ puts "API Error: #{e.message}"
16
+ puts "Error code: #{e.code}" if e.code
17
+ end
18
+ ```
19
+
20
+ **Common API Error Codes:**
21
+ - `400` - Bad Request (invalid parameters)
22
+ - `401` - Unauthorized (invalid token)
23
+ - `403` - Forbidden (bot blocked/kicked)
24
+ - `404` - Not Found (chat/user not found)
25
+ - `429` - Too Many Requests (rate limited)
26
+
27
+ ### Network Errors
28
+
29
+ Connection issues, timeouts, DNS failures.
30
+
31
+ ```ruby
32
+ begin
33
+ result = bot.api.call('sendMessage', params)
34
+ rescue Telegem::API::NetworkError => e
35
+ puts "Network error: #{e.message}"
36
+ # Retry logic
37
+ end
38
+ ```
39
+
40
+ ### Handler Errors
41
+
42
+ Exceptions in message handlers.
43
+
44
+ ```ruby
45
+ bot.error do |error, ctx|
46
+ ctx.logger.error("Handler error: #{error.message}")
47
+ ctx.logger.error("User: #{ctx.from&.id}, Chat: #{ctx.chat&.id}")
48
+
49
+ # Send user-friendly message
50
+ ctx.reply("Sorry, something went wrong. Please try again.") if ctx.chat
51
+ end
52
+ ```
53
+
54
+ ## Global Error Handler
55
+
56
+ Catch all unhandled errors in handlers.
57
+
58
+ ```ruby
59
+ bot.error do |error, ctx|
60
+ case error
61
+ when Telegem::API::APIError
62
+ handle_api_error(error, ctx)
63
+ when StandardError
64
+ handle_generic_error(error, ctx)
65
+ end
66
+ end
67
+
68
+ def handle_api_error(error, ctx)
69
+ case error.code
70
+ when 403
71
+ ctx.reply("I don't have permission to do that.")
72
+ when 429
73
+ ctx.reply("Please wait a moment before trying again.")
74
+ else
75
+ ctx.reply("API error occurred.")
76
+ end
77
+ end
78
+
79
+ def handle_generic_error(error, ctx)
80
+ ctx.logger.error("Unexpected error: #{error.class}: #{error.message}")
81
+ ctx.logger.error(error.backtrace.join("\n"))
82
+ ctx.reply("An unexpected error occurred.")
83
+ end
84
+ ```
85
+
86
+ ## Handler-Level Error Handling
87
+
88
+ Handle errors within specific handlers.
89
+
90
+ ```ruby
91
+ bot.command('process') do |ctx|
92
+ begin
93
+ data = process_user_input(ctx.text)
94
+ result = perform_calculation(data)
95
+ ctx.reply("Result: #{result}")
96
+ rescue ArgumentError => e
97
+ ctx.reply("Invalid input: #{e.message}")
98
+ rescue ZeroDivisionError
99
+ ctx.reply("Cannot divide by zero")
100
+ rescue => e
101
+ ctx.logger.error("Processing error: #{e.message}")
102
+ ctx.reply("Processing failed. Please try again.")
103
+ end
104
+ end
105
+ ```
106
+
107
+ ## Middleware Error Handling
108
+
109
+ Handle errors in middleware chain.
110
+
111
+ ```ruby
112
+ class SafeMiddleware
113
+ def call(ctx, next_middleware)
114
+ begin
115
+ next_middleware.call(ctx)
116
+ rescue => e
117
+ ctx.logger.error("Middleware error: #{e.message}")
118
+ # Continue processing or re-raise
119
+ raise
120
+ end
121
+ end
122
+ end
123
+
124
+ # Use safe wrapper
125
+ bot.use SafeMiddleware.new
126
+ bot.use ProblematicMiddleware.new
127
+ ```
128
+
129
+ ## Async Error Handling
130
+
131
+ Handle errors in async operations.
132
+
133
+ ```ruby
134
+ bot.command('async_task') do |ctx|
135
+ ctx.reply("Processing...")
136
+
137
+ Async do
138
+ begin
139
+ result = perform_async_operation()
140
+ ctx.reply("Success: #{result}")
141
+ rescue => e
142
+ ctx.logger.error("Async error: #{e.message}")
143
+ ctx.reply("Operation failed")
144
+ end
145
+ end
146
+ end
147
+ ```
148
+
149
+ ## Network Resilience
150
+
151
+ Handle network issues with retries.
152
+
153
+ ```ruby
154
+ class RetryMiddleware
155
+ def initialize(max_retries: 3, backoff: 1)
156
+ @max_retries = max_retries
157
+ @backoff = backoff
158
+ end
159
+
160
+ def call(ctx, next_middleware)
161
+ retries = 0
162
+
163
+ begin
164
+ next_middleware.call(ctx)
165
+ rescue Telegem::API::NetworkError => e
166
+ retries += 1
167
+ if retries <= @max_retries
168
+ sleep(@backoff * retries)
169
+ retry
170
+ else
171
+ raise
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ bot.use RetryMiddleware.new(max_retries: 3)
178
+ ```
179
+
180
+ ## Rate Limiting Errors
181
+
182
+ Handle Telegram's rate limits.
183
+
184
+ ```ruby
185
+ class RateLimitHandler
186
+ def call(ctx, next_middleware)
187
+ begin
188
+ next_middleware.call(ctx)
189
+ rescue Telegem::API::APIError => e
190
+ if e.code == 429
191
+ # Rate limited
192
+ retry_after = parse_retry_after(e.message)
193
+ ctx.logger.warn("Rate limited, retry after #{retry_after}s")
194
+
195
+ if retry_after && retry_after < 60
196
+ sleep(retry_after)
197
+ retry
198
+ else
199
+ ctx.reply("Service temporarily unavailable")
200
+ end
201
+ else
202
+ raise
203
+ end
204
+ end
205
+ end
206
+
207
+ private
208
+
209
+ def parse_retry_after(message)
210
+ # Parse retry-after from error message
211
+ match = message.match(/retry after (\d+)/)
212
+ match ? match[1].to_i : nil
213
+ end
214
+ end
215
+ ```
216
+
217
+ ## File Operation Errors
218
+
219
+ Handle file upload/download errors.
220
+
221
+ ```ruby
222
+ bot.document do |ctx|
223
+ begin
224
+ file_id = ctx.message.document.file_id
225
+ content = ctx.download_file(file_id)
226
+
227
+ # Process content
228
+ result = process_file(content)
229
+ ctx.reply("File processed successfully")
230
+
231
+ rescue => e
232
+ ctx.logger.error("File processing error: #{e.message}")
233
+ ctx.reply("Failed to process file. Please try again.")
234
+ end
235
+ end
236
+ ```
237
+
238
+ ## Session Errors
239
+
240
+ Handle session storage failures.
241
+
242
+ ```ruby
243
+ class SessionErrorHandler
244
+ def call(ctx, next_middleware)
245
+ begin
246
+ next_middleware.call(ctx)
247
+ rescue => e
248
+ if e.message.include?('session')
249
+ ctx.logger.error("Session error: #{e.message}")
250
+ # Continue without session
251
+ ctx.instance_variable_set(:@session, {})
252
+ else
253
+ raise
254
+ end
255
+ end
256
+
257
+ next_middleware.call(ctx)
258
+ end
259
+ end
260
+ ```
261
+
262
+ ## Validation Errors
263
+
264
+ Validate user input before processing.
265
+
266
+ ```ruby
267
+ class ValidationError < StandardError; end
268
+
269
+ def validate_input(text)
270
+ raise ValidationError, "Text is required" if text.nil? || text.empty?
271
+ raise ValidationError, "Text too long" if text.length > 1000
272
+ raise ValidationError, "Invalid characters" unless text.match?(/\A[\w\s]+\z/)
273
+ end
274
+
275
+ bot.command('validate') do |ctx|
276
+ begin
277
+ validate_input(ctx.text)
278
+ ctx.reply("Input is valid")
279
+ rescue ValidationError => e
280
+ ctx.reply("Validation error: #{e.message}")
281
+ end
282
+ end
283
+ ```
284
+
285
+ ## Graceful Degradation
286
+
287
+ Continue operating when some features fail.
288
+
289
+ ```ruby
290
+ bot.command('complex') do |ctx|
291
+ result = {}
292
+
293
+ # Try optional features
294
+ begin
295
+ result[:feature1] = optional_feature1()
296
+ rescue => e
297
+ ctx.logger.warn("Feature 1 failed: #{e.message}")
298
+ end
299
+
300
+ begin
301
+ result[:feature2] = optional_feature2()
302
+ rescue => e
303
+ ctx.logger.warn("Feature 2 failed: #{e.message}")
304
+ end
305
+
306
+ # Core functionality
307
+ result[:core] = core_functionality()
308
+
309
+ ctx.reply("Result: #{result}")
310
+ end
311
+ ```
312
+
313
+ ## Error Monitoring
314
+
315
+ Log and monitor errors for debugging.
316
+
317
+ ```ruby
318
+ class ErrorMonitoringMiddleware
319
+ def initialize(error_tracker)
320
+ @error_tracker = error_tracker
321
+ end
322
+
323
+ def call(ctx, next_middleware)
324
+ begin
325
+ next_middleware.call(ctx)
326
+ rescue => e
327
+ # Log error with context
328
+ error_data = {
329
+ error: e.message,
330
+ class: e.class.name,
331
+ backtrace: e.backtrace.first(10),
332
+ user_id: ctx.from&.id,
333
+ chat_id: ctx.chat&.id,
334
+ update_type: ctx.update_type,
335
+ timestamp: Time.now.to_i
336
+ }
337
+
338
+ @error_tracker.report(error_data)
339
+
340
+ # Re-raise to let other handlers deal with it
341
+ raise
342
+ end
343
+ end
344
+ end
345
+ ```
346
+
347
+ ## User Communication
348
+
349
+ Provide helpful error messages to users.
350
+
351
+ ```ruby
352
+ ERROR_MESSAGES = {
353
+ 'network' => "Connection problem. Please try again.",
354
+ 'timeout' => "Request timed out. Please try again.",
355
+ 'invalid_input' => "Please check your input and try again.",
356
+ 'permission_denied' => "I don't have permission to do that.",
357
+ 'not_found' => "The requested item was not found.",
358
+ 'rate_limited' => "Too many requests. Please wait a moment.",
359
+ 'server_error' => "Server error. Please try again later."
360
+ }
361
+
362
+ def user_friendly_error(error_type, ctx)
363
+ message = ERROR_MESSAGES[error_type] || "An error occurred."
364
+ ctx.reply(message)
365
+ end
366
+
367
+ bot.error do |error, ctx|
368
+ error_type = classify_error(error)
369
+ user_friendly_error(error_type, ctx)
370
+ end
371
+
372
+ def classify_error(error)
373
+ case error
374
+ when Telegem::API::NetworkError
375
+ 'network'
376
+ when Timeout::Error
377
+ 'timeout'
378
+ when Telegem::API::APIError
379
+ case error.code
380
+ when 403 then 'permission_denied'
381
+ when 404 then 'not_found'
382
+ when 429 then 'rate_limited'
383
+ else 'server_error'
384
+ end
385
+ else
386
+ 'server_error'
387
+ end
388
+ end
389
+ ```
390
+
391
+ ## Testing Error Scenarios
392
+
393
+ ```ruby
394
+ # Test error handlers
395
+ def test_error_handling
396
+ # Simulate API error
397
+ allow(bot.api).to receive(:call).and_raise(Telegem::API::APIError.new("Test error"))
398
+
399
+ # Trigger handler
400
+ simulate_message(bot, '/test')
401
+
402
+ # Assert error handling
403
+ expect(last_response).to include("error occurred")
404
+ end
405
+
406
+ # Test network resilience
407
+ def test_network_retry
408
+ call_count = 0
409
+ allow(bot.api).to receive(:call) do
410
+ call_count += 1
411
+ raise Telegem::API::NetworkError.new("Connection failed") if call_count < 3
412
+ { ok: true }
413
+ end
414
+
415
+ simulate_message(bot, '/retry_test')
416
+
417
+ expect(call_count).to eq(3)
418
+ end
419
+ ```
420
+
421
+ ## Best Practices
422
+
423
+ 1. **Always implement global error handlers**
424
+ 2. **Use specific error types for different scenarios**
425
+ 3. **Provide user-friendly error messages**
426
+ 4. **Log errors with sufficient context**
427
+ 5. **Implement retry logic for transient errors**
428
+ 6. **Use circuit breakers for external services**
429
+ 7. **Monitor error rates and patterns**
430
+ 8. **Test error scenarios thoroughly**
431
+ 9. **Gracefully degrade when features fail**
432
+ 10. **Validate input before processing**
433
+
434
+ Proper error handling ensures your bot remains stable and provides a good user experience even when things go wrong.</content>
435
+ <parameter name="filePath">/home/slick/telegem/docs/error_handling.md