-
Notifications
You must be signed in to change notification settings - Fork 1
feat(error): add error/exception handler #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
60e00a1
434685f
6a699b5
c7b35fe
2744fad
27ade4f
5e2c8af
6c5513f
c074ab7
dac8c9b
b2dbe03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| module Wreq | ||
| # Network connection errors | ||
|
|
||
| # Connection to the server failed. | ||
| # | ||
| # Raised when the client cannot establish a connection to the server. | ||
| # | ||
| # @example | ||
| # begin | ||
| # client.get("http://localhost:9999") | ||
| # rescue Wreq::ConnectionError => e | ||
| # puts "Connection failed: #{e.message}" | ||
| # retry_with_backoff | ||
| # end | ||
| unless const_defined?(:ConnectionError) | ||
| class ConnectionError < StandardError; end | ||
| end | ||
|
|
||
| # Connection was reset by the server. | ||
| # | ||
| # Raised when the server closes the connection unexpectedly. | ||
| # | ||
| # @example | ||
| # rescue Wreq::ConnectionResetError => e | ||
| # puts "Connection reset: #{e.message}" | ||
| # end | ||
| unless const_defined?(:ConnectionResetError) | ||
| class ConnectionResetError < StandardError; end | ||
| end | ||
|
|
||
| # TLS/SSL error occurred. | ||
| # | ||
| # Raised when there's an error with TLS/SSL, such as certificate | ||
| # verification failure or protocol mismatch. | ||
| # | ||
| # @example | ||
| # begin | ||
| # client.get("https://self-signed.badssl.com") | ||
| # rescue Wreq::TlsError => e | ||
| # puts "TLS error: #{e.message}" | ||
| # end | ||
| unless const_defined?(:TlsError) | ||
| class TlsError < StandardError; end | ||
| end | ||
|
|
||
| # HTTP protocol and request/response errors | ||
|
|
||
| # Request failed. | ||
| # | ||
| # Generic error for request failures that don't fit other categories. | ||
| # | ||
| # @example | ||
| # rescue Wreq::RequestError => e | ||
| # puts "Request failed: #{e.message}" | ||
| # end | ||
| unless const_defined?(:RequestError) | ||
| class RequestError < StandardError; end | ||
| end | ||
|
|
||
| # HTTP status code indicates an error. | ||
| # | ||
| # Raised when the server returns an error status code (4xx or 5xx). | ||
| # | ||
| # @example | ||
| # begin | ||
| # response = client.get("https://httpbin.org/status/404") | ||
| # rescue Wreq::StatusError => e | ||
| # puts "HTTP error: #{e.message}" | ||
| # # e.response contains the full response | ||
| # end | ||
| unless const_defined?(:StatusError) | ||
| class StatusError < StandardError; end | ||
| end | ||
|
|
||
| # Redirect handling failed. | ||
| # | ||
| # Raised when too many redirects occur or redirect logic fails. | ||
| # | ||
| # @example | ||
| # begin | ||
| # client = Wreq::Client.new(max_redirects: 3) | ||
| # client.get("https://httpbin.org/redirect/10") | ||
| # rescue Wreq::RedirectError => e | ||
| # puts "Too many redirects: #{e.message}" | ||
| # end | ||
| unless const_defined?(:RedirectError) | ||
| class RedirectError < StandardError; end | ||
| end | ||
|
|
||
| # Request timed out. | ||
| # | ||
| # Raised when the request exceeds the configured timeout. | ||
| # | ||
| # @example | ||
| # begin | ||
| # client = Wreq::Client.new(timeout: 5) | ||
| # client.get("https://httpbin.org/delay/10") | ||
| # rescue Wreq::TimeoutError => e | ||
| # puts "Request timed out: #{e.message}" | ||
| # retry_with_longer_timeout | ||
| # end | ||
| unless const_defined?(:TimeoutError) | ||
| class TimeoutError < StandardError; end | ||
| end | ||
|
|
||
| # Data processing and encoding errors | ||
|
|
||
| # Response body processing failed. | ||
| # | ||
| # Raised when there's an error reading or processing the response body. | ||
| # | ||
| # @example | ||
| # rescue Wreq::BodyError => e | ||
| # puts "Body error: #{e.message}" | ||
| # end | ||
| unless const_defined?(:BodyError) | ||
| class BodyError < StandardError; end | ||
| end | ||
|
|
||
| # Decoding response failed. | ||
| # | ||
| # Raised when response content cannot be decoded (e.g., invalid UTF-8, | ||
| # malformed JSON, corrupted compression). | ||
| # | ||
| # @example | ||
| # begin | ||
| # response = client.get("https://example.com/invalid-utf8") | ||
| # response.text # May raise DecodingError | ||
| # rescue Wreq::DecodingError => e | ||
| # puts "Decoding error: #{e.message}" | ||
| # # Fall back to binary data | ||
| # data = response.body | ||
| # end | ||
| unless const_defined?(:DecodingError) | ||
| class DecodingError < StandardError; end | ||
| end | ||
|
|
||
| # Configuration and builder errors | ||
|
|
||
| # Client configuration is invalid. | ||
| # | ||
| # Raised when the client is configured with invalid options. | ||
| # | ||
| # @example | ||
| # begin | ||
| # client = Wreq::Client.new( | ||
| # proxy: "invalid://proxy", | ||
| # timeout: -1 | ||
| # ) | ||
| # rescue Wreq::BuilderError => e | ||
| # puts "Invalid configuration: #{e.message}" | ||
| # end | ||
| unless const_defined?(:BuilderError) | ||
| class BuilderError < StandardError; end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| use magnus::{ | ||
| Error as MagnusError, RModule, Ruby, exception::ExceptionClass, prelude::*, value::Lazy, | ||
| }; | ||
|
|
||
| static WREQ: Lazy<RModule> = Lazy::new(|ruby| ruby.define_module(crate::RUBY_MODULE_NAME).unwrap()); | ||
|
|
||
| macro_rules! define_exception { | ||
| ($name:ident, $ruby_name:literal, $parent_method:ident) => { | ||
| static $name: Lazy<ExceptionClass> = Lazy::new(|ruby| { | ||
| ruby.get_inner(&WREQ) | ||
| .define_error($ruby_name, ruby.$parent_method()) | ||
| .unwrap() | ||
| }); | ||
| }; | ||
| } | ||
|
|
||
| macro_rules! map_wreq_error { | ||
| ($ruby:expr, $err:expr, $msg:expr, $($check_method:ident => $exception:ident),* $(,)?) => { | ||
| { | ||
| $( | ||
| if $err.$check_method() { | ||
| return MagnusError::new($ruby.get_inner(&$exception), $msg); | ||
| } | ||
| )* | ||
| MagnusError::new($ruby.exception_runtime_error(), $msg) | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| // Network connection errors | ||
| define_exception!(CONNECTION_ERROR, "ConnectionError", exception_runtime_error); | ||
| define_exception!( | ||
| CONNECTION_RESET_ERROR, | ||
| "ConnectionResetError", | ||
| exception_runtime_error | ||
| ); | ||
| define_exception!(TLS_ERROR, "TlsError", exception_runtime_error); | ||
|
|
||
| // HTTP protocol and request/response errors | ||
| define_exception!(REQUEST_ERROR, "RequestError", exception_runtime_error); | ||
| define_exception!(STATUS_ERROR, "StatusError", exception_runtime_error); | ||
| define_exception!(REDIRECT_ERROR, "RedirectError", exception_runtime_error); | ||
| define_exception!(TIMEOUT_ERROR, "TimeoutError", exception_runtime_error); | ||
|
|
||
| // Data processing and encoding errors | ||
| define_exception!(BODY_ERROR, "BodyError", exception_runtime_error); | ||
| define_exception!(DECODING_ERROR, "DecodingError", exception_runtime_error); | ||
|
|
||
| // Configuration and builder errors | ||
| define_exception!(BUILDER_ERROR, "BuilderError", exception_runtime_error); | ||
|
|
||
| /// Map [`wreq::Error`] to corresponding [`magnus::Error`] | ||
| pub fn wreq_error_to_magnus(ruby: &Ruby, err: wreq::Error) -> MagnusError { | ||
| let error_msg = err.to_string(); | ||
| map_wreq_error!( | ||
| ruby, | ||
| err, | ||
| error_msg, | ||
| is_builder => BUILDER_ERROR, | ||
| is_body => BODY_ERROR, | ||
| is_tls => TLS_ERROR, | ||
| is_connection_reset => CONNECTION_RESET_ERROR, | ||
| is_connect => CONNECTION_ERROR, | ||
| is_decode => DECODING_ERROR, | ||
| is_redirect => REDIRECT_ERROR, | ||
| is_timeout => TIMEOUT_ERROR, | ||
| is_status => STATUS_ERROR, | ||
| is_request => REQUEST_ERROR, | ||
| ) | ||
| } | ||
|
|
||
| pub fn include(ruby: &Ruby) { | ||
| Lazy::force(&CONNECTION_ERROR, ruby); | ||
| Lazy::force(&CONNECTION_RESET_ERROR, ruby); | ||
| Lazy::force(&TLS_ERROR, ruby); | ||
| Lazy::force(&REQUEST_ERROR, ruby); | ||
| Lazy::force(&STATUS_ERROR, ruby); | ||
| Lazy::force(&REDIRECT_ERROR, ruby); | ||
| Lazy::force(&TIMEOUT_ERROR, ruby); | ||
| Lazy::force(&BODY_ERROR, ruby); | ||
| Lazy::force(&DECODING_ERROR, ruby); | ||
| Lazy::force(&BUILDER_ERROR, ruby); | ||
| } | ||
|
Comment on lines
+72
to
+83
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Potential issue: The
WREQmodule is being defined here withdefine_module, but the same module is already defined inlib.rs(line 43). Callingdefine_modulemultiple times should be safe in Ruby/Magnus (it returns the existing module if it already exists), but it's redundant. Consider passing the module as a parameter toinclude()or usingfind_classinstead ofdefine_moduleto avoid confusion.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot open a new pull request to apply changes based on this feedback