From 60e00a1428b4466cce6cddd0a47e0a8129eb6437 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Tue, 18 Nov 2025 22:31:16 +0800 Subject: [PATCH 01/11] feat(error): add error/exception handler --- lib/wreq_ruby/error.rb | 210 +++++++++++++++++++++++++++++++++++++++++ src/client.rs | 16 ++-- src/error.rs | 100 ++++++++++++++++++++ src/lib.rs | 10 +- src/macros.rs | 7 -- 5 files changed, 321 insertions(+), 22 deletions(-) create mode 100644 lib/wreq_ruby/error.rb create mode 100644 src/error.rs diff --git a/lib/wreq_ruby/error.rb b/lib/wreq_ruby/error.rb new file mode 100644 index 0000000..fd69fc7 --- /dev/null +++ b/lib/wreq_ruby/error.rb @@ -0,0 +1,210 @@ +# frozen_string_literal: true + +module Wreq + # Base error class for all Wreq exceptions. + # This is only defined if the native extension hasn't loaded yet. + unless const_defined?(:Error) + class Error < StandardError; end + end + + # System-level and runtime errors + + # DNS resolution failed. + # + # Raised when the DNS resolver cannot resolve the hostname. + # + # @example + # begin + # client.get("http://nonexistent.domain.invalid") + # rescue Wreq::DNSResolverError => e + # puts "DNS resolution failed: #{e.message}" + # end + unless const_defined?(:DNSResolverError) + class DNSResolverError < Error; end + end + + # Rust panic occurred. + # + # Raised when an unexpected panic occurs in the Rust code. + # This usually indicates a bug in the library. + # + # @example + # rescue Wreq::RustPanic => e + # puts "Internal error: #{e.message}" + # # Report this as a bug + # end + unless const_defined?(:RustPanic) + class RustPanic < Error; end + end + + # 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 < Error; 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 < ConnectionError; 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 < Error; 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 < Error; 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 < Error; 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 < Error; 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 < Error; 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 < Error; 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 < Error; 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 < Error; end + end + + # Input validation and parsing errors + + # URL parsing failed. + # + # Raised when a provided URL is malformed or invalid. + # + # @example + # begin + # client.get("not a valid url") + # rescue Wreq::URLParseError => e + # puts "Invalid URL: #{e.message}" + # end + unless const_defined?(:URLParseError) + class URLParseError < Error; end + end +end \ No newline at end of file diff --git a/src/client.rs b/src/client.rs index 0e67168..4c1fcbc 100644 --- a/src/client.rs +++ b/src/client.rs @@ -10,7 +10,7 @@ use wreq::{ header::{HeaderMap, HeaderName, HeaderValue, OrigHeaderMap}, }; -use crate::{format_magnus_error, nogvl}; +use crate::nogvl; /// A builder for `Client`. #[derive(Debug, Default, Deserialize)] @@ -84,7 +84,7 @@ struct Builder { no_proxy: Option, /// The proxy to use for the client. #[serde(skip)] - proxy: Option, + proxy: Option, // ========= Compression options ========= /// Sets gzip as an accepted encoding. @@ -165,12 +165,8 @@ impl Builder { .get(ruby.to_symbol("proxy")) .and_then(RString::from_value) { - let uri = Uri::from_maybe_shared(proxy.to_bytes()).map_err(|e| { - magnus::Error::new( - ruby.exception_arg_error(), - format!("invalid proxy URI '{proxy}': {e}"), - ) - })?; + let uri = Proxy::all(proxy.to_bytes().as_ref()) + .map_err(|err| crate::error::wreq_error_to_magnus(ruby, err))?; builder.proxy = Some(uri); } @@ -297,7 +293,7 @@ impl Client { apply_option!(set_if_some, builder, params.verify, cert_verification); // Network options. - apply_option!(set_if_some_map_ok, builder, params.proxy, proxy, Proxy::all); + apply_option!(set_if_some, builder, params.proxy, proxy); apply_option!(set_if_true, builder, params.no_proxy, no_proxy, false); // Compression options. @@ -309,7 +305,7 @@ impl Client { builder .build() .map(Client) - .map_err(|e| format_magnus_error(ruby, e)) + .map_err(|err| crate::error::wreq_error_to_magnus(ruby, err)) }) } else { nogvl::nogvl(|| Ok(Self(wreq::Client::new()))) diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..1d90323 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,100 @@ +use magnus::{ + Error as MagnusError, RModule, Ruby, exception::ExceptionClass, prelude::*, value::Lazy, +}; + +static WREQ: Lazy = Lazy::new(|ruby| ruby.define_module(crate::RUBY_MODULE_NAME).unwrap()); + +macro_rules! define_exception { + ($name:ident, $ruby_name:literal, $parent:expr) => { + static $name: Lazy = Lazy::new(|ruby| { + ruby.get_inner(&WREQ) + .define_error($ruby_name, $parent(ruby)) + .unwrap() + }); + }; +} + +// System-level and runtime errors +define_exception!(DNS_RESOLVER_ERROR, "DNSResolverError", |ruby: &Ruby| ruby + .exception_runtime_error()); +define_exception!(RUST_PANIC, "RustPanic", |ruby: &Ruby| ruby + .exception_runtime_error()); + +// Network connection errors +define_exception!(CONNECTION_ERROR, "ConnectionError", |ruby: &Ruby| ruby + .exception_runtime_error()); +define_exception!( + CONNECTION_RESET_ERROR, + "ConnectionResetError", + |ruby: &Ruby| ruby.exception_runtime_error() +); +define_exception!(TLS_ERROR, "TlsError", |ruby: &Ruby| ruby + .exception_runtime_error()); + +// HTTP protocol and request/response errors +define_exception!(REQUEST_ERROR, "RequestError", |ruby: &Ruby| ruby + .exception_runtime_error()); +define_exception!(STATUS_ERROR, "StatusError", |ruby: &Ruby| ruby + .exception_runtime_error()); +define_exception!(REDIRECT_ERROR, "RedirectError", |ruby: &Ruby| ruby + .exception_runtime_error()); +define_exception!(TIMEOUT_ERROR, "TimeoutError", |ruby: &Ruby| ruby + .exception_runtime_error()); + +// Data processing and encoding errors +define_exception!(BODY_ERROR, "BodyError", |ruby: &Ruby| ruby + .exception_runtime_error()); +define_exception!(DECODING_ERROR, "DecodingError", |ruby: &Ruby| ruby + .exception_runtime_error()); + +// Configuration and builder errors +define_exception!(BUILDER_ERROR, "BuilderError", |ruby: &Ruby| ruby + .exception_runtime_error()); + +// Input validation and parsing errors +define_exception!(URL_PARSE_ERROR, "URLParseError", |ruby: &Ruby| ruby + .exception_runtime_error()); + +pub fn wreq_error_to_magnus(ruby: &Ruby, err: wreq::Error) -> MagnusError { + let error_msg = format!("{}", err); + + if err.is_builder() { + MagnusError::new(ruby.get_inner(&BUILDER_ERROR), error_msg) + } else if err.is_body() { + MagnusError::new(ruby.get_inner(&BODY_ERROR), error_msg) + } else if err.is_tls() { + MagnusError::new(ruby.get_inner(&TLS_ERROR), error_msg) + } else if err.is_connection_reset() { + MagnusError::new(ruby.get_inner(&CONNECTION_RESET_ERROR), error_msg) + } else if err.is_connect() { + MagnusError::new(ruby.get_inner(&CONNECTION_ERROR), error_msg) + } else if err.is_decode() { + MagnusError::new(ruby.get_inner(&DECODING_ERROR), error_msg) + } else if err.is_redirect() { + MagnusError::new(ruby.get_inner(&REDIRECT_ERROR), error_msg) + } else if err.is_timeout() { + MagnusError::new(ruby.get_inner(&TIMEOUT_ERROR), error_msg) + } else if err.is_status() { + MagnusError::new(ruby.get_inner(&STATUS_ERROR), error_msg) + } else if err.is_request() { + MagnusError::new(ruby.get_inner(&REQUEST_ERROR), error_msg) + } else { + MagnusError::new(ruby.exception_runtime_error(), error_msg) + } +} + +pub fn include(ruby: &Ruby) { + Lazy::force(&DNS_RESOLVER_ERROR, ruby); + Lazy::force(&RUST_PANIC, 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); + Lazy::force(&URL_PARSE_ERROR, ruby); +} diff --git a/src/lib.rs b/src/lib.rs index 8b6faee..ea1fc08 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,12 +20,13 @@ #[macro_use] mod macros; mod client; +mod error; mod http; mod nogvl; use std::sync::LazyLock; -use magnus::{Error, Ruby}; +use magnus::{Error, RModule, Ruby, value::Lazy}; static RUNTIME: LazyLock = LazyLock::new(|| { tokio::runtime::Builder::new_multi_thread() @@ -34,15 +35,14 @@ static RUNTIME: LazyLock = LazyLock::new(|| { .expect("Failed to initialize Tokio runtime") }); -fn format_magnus_error(ruby: &Ruby, err: wreq::Error) -> Error { - Error::new(ruby.exception_runtime_error(), err.to_string()) -} +const RUBY_MODULE_NAME: &str = "Wreq"; /// wreq ruby binding #[magnus::init] fn init(ruby: &Ruby) -> Result<(), Error> { - let gem_module = ruby.define_module("Wreq")?; + let gem_module = ruby.define_module(RUBY_MODULE_NAME)?; http::include(ruby, &gem_module)?; client::include(ruby, &gem_module)?; + error::include(ruby); Ok(()) } diff --git a/src/macros.rs b/src/macros.rs index 74f6b82..e2dbd40 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -24,13 +24,6 @@ macro_rules! apply_option { $builder = $builder.$method($transform(&value)); } }; - (set_if_some_map_ok, $builder:expr, $option:expr, $method:ident, $transform:expr) => { - if let Some(value) = $option.take() { - if let Ok(transformed) = $transform(value) { - $builder = $builder.$method(transformed); - } - } - }; (set_if_true, $builder:expr, $option:expr, $method:ident, $default:expr) => { if $option.unwrap_or($default) { $builder = $builder.$method(); From 434685f62b0bb5c18317278a4009435a1bbb18d2 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Tue, 18 Nov 2025 22:35:36 +0800 Subject: [PATCH 02/11] update --- lib/wreq_ruby/error.rb | 52 ------------------------------------------ src/error.rs | 13 ----------- 2 files changed, 65 deletions(-) diff --git a/lib/wreq_ruby/error.rb b/lib/wreq_ruby/error.rb index fd69fc7..fe327a5 100644 --- a/lib/wreq_ruby/error.rb +++ b/lib/wreq_ruby/error.rb @@ -1,42 +1,6 @@ # frozen_string_literal: true module Wreq - # Base error class for all Wreq exceptions. - # This is only defined if the native extension hasn't loaded yet. - unless const_defined?(:Error) - class Error < StandardError; end - end - - # System-level and runtime errors - - # DNS resolution failed. - # - # Raised when the DNS resolver cannot resolve the hostname. - # - # @example - # begin - # client.get("http://nonexistent.domain.invalid") - # rescue Wreq::DNSResolverError => e - # puts "DNS resolution failed: #{e.message}" - # end - unless const_defined?(:DNSResolverError) - class DNSResolverError < Error; end - end - - # Rust panic occurred. - # - # Raised when an unexpected panic occurs in the Rust code. - # This usually indicates a bug in the library. - # - # @example - # rescue Wreq::RustPanic => e - # puts "Internal error: #{e.message}" - # # Report this as a bug - # end - unless const_defined?(:RustPanic) - class RustPanic < Error; end - end - # Network connection errors # Connection to the server failed. @@ -191,20 +155,4 @@ class DecodingError < Error; end unless const_defined?(:BuilderError) class BuilderError < Error; end end - - # Input validation and parsing errors - - # URL parsing failed. - # - # Raised when a provided URL is malformed or invalid. - # - # @example - # begin - # client.get("not a valid url") - # rescue Wreq::URLParseError => e - # puts "Invalid URL: #{e.message}" - # end - unless const_defined?(:URLParseError) - class URLParseError < Error; end - end end \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index 1d90323..a587852 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,12 +14,6 @@ macro_rules! define_exception { }; } -// System-level and runtime errors -define_exception!(DNS_RESOLVER_ERROR, "DNSResolverError", |ruby: &Ruby| ruby - .exception_runtime_error()); -define_exception!(RUST_PANIC, "RustPanic", |ruby: &Ruby| ruby - .exception_runtime_error()); - // Network connection errors define_exception!(CONNECTION_ERROR, "ConnectionError", |ruby: &Ruby| ruby .exception_runtime_error()); @@ -51,10 +45,6 @@ define_exception!(DECODING_ERROR, "DecodingError", |ruby: &Ruby| ruby define_exception!(BUILDER_ERROR, "BuilderError", |ruby: &Ruby| ruby .exception_runtime_error()); -// Input validation and parsing errors -define_exception!(URL_PARSE_ERROR, "URLParseError", |ruby: &Ruby| ruby - .exception_runtime_error()); - pub fn wreq_error_to_magnus(ruby: &Ruby, err: wreq::Error) -> MagnusError { let error_msg = format!("{}", err); @@ -84,8 +74,6 @@ pub fn wreq_error_to_magnus(ruby: &Ruby, err: wreq::Error) -> MagnusError { } pub fn include(ruby: &Ruby) { - Lazy::force(&DNS_RESOLVER_ERROR, ruby); - Lazy::force(&RUST_PANIC, ruby); Lazy::force(&CONNECTION_ERROR, ruby); Lazy::force(&CONNECTION_RESET_ERROR, ruby); Lazy::force(&TLS_ERROR, ruby); @@ -96,5 +84,4 @@ pub fn include(ruby: &Ruby) { Lazy::force(&BODY_ERROR, ruby); Lazy::force(&DECODING_ERROR, ruby); Lazy::force(&BUILDER_ERROR, ruby); - Lazy::force(&URL_PARSE_ERROR, ruby); } From 6a699b5cd516f3c1c7d4cda8bdbbd14322890119 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Tue, 18 Nov 2025 22:35:48 +0800 Subject: [PATCH 03/11] fmt --- lib/wreq_ruby/error.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/wreq_ruby/error.rb b/lib/wreq_ruby/error.rb index fe327a5..ea6f95f 100644 --- a/lib/wreq_ruby/error.rb +++ b/lib/wreq_ruby/error.rb @@ -155,4 +155,4 @@ class DecodingError < Error; end unless const_defined?(:BuilderError) class BuilderError < Error; end end -end \ No newline at end of file +end From c7b35fed3a55d362f3c0605295da6dce79a730e4 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Tue, 18 Nov 2025 22:45:47 +0800 Subject: [PATCH 04/11] update --- lib/wreq_ruby/error.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/wreq_ruby/error.rb b/lib/wreq_ruby/error.rb index ea6f95f..8c1d574 100644 --- a/lib/wreq_ruby/error.rb +++ b/lib/wreq_ruby/error.rb @@ -15,7 +15,7 @@ module Wreq # retry_with_backoff # end unless const_defined?(:ConnectionError) - class ConnectionError < Error; end + class ConnectionError < StandardError; end end # Connection was reset by the server. @@ -27,7 +27,7 @@ class ConnectionError < Error; end # puts "Connection reset: #{e.message}" # end unless const_defined?(:ConnectionResetError) - class ConnectionResetError < ConnectionError; end + class ConnectionResetError < StandardError; end end # TLS/SSL error occurred. @@ -42,7 +42,7 @@ class ConnectionResetError < ConnectionError; end # puts "TLS error: #{e.message}" # end unless const_defined?(:TlsError) - class TlsError < Error; end + class TlsError < StandardError; end end # HTTP protocol and request/response errors @@ -56,7 +56,7 @@ class TlsError < Error; end # puts "Request failed: #{e.message}" # end unless const_defined?(:RequestError) - class RequestError < Error; end + class RequestError < StandardError; end end # HTTP status code indicates an error. @@ -71,7 +71,7 @@ class RequestError < Error; end # # e.response contains the full response # end unless const_defined?(:StatusError) - class StatusError < Error; end + class StatusError < StandardError; end end # Redirect handling failed. @@ -86,7 +86,7 @@ class StatusError < Error; end # puts "Too many redirects: #{e.message}" # end unless const_defined?(:RedirectError) - class RedirectError < Error; end + class RedirectError < StandardError; end end # Request timed out. @@ -102,7 +102,7 @@ class RedirectError < Error; end # retry_with_longer_timeout # end unless const_defined?(:TimeoutError) - class TimeoutError < Error; end + class TimeoutError < StandardError; end end # Data processing and encoding errors @@ -116,7 +116,7 @@ class TimeoutError < Error; end # puts "Body error: #{e.message}" # end unless const_defined?(:BodyError) - class BodyError < Error; end + class BodyError < StandardError; end end # Decoding response failed. @@ -134,7 +134,7 @@ class BodyError < Error; end # data = response.body # end unless const_defined?(:DecodingError) - class DecodingError < Error; end + class DecodingError < StandardError; end end # Configuration and builder errors @@ -153,6 +153,6 @@ class DecodingError < Error; end # puts "Invalid configuration: #{e.message}" # end unless const_defined?(:BuilderError) - class BuilderError < Error; end + class BuilderError < StandardError; end end end From 2744fadce36619ceb684d26304297fa358ab1569 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Tue, 18 Nov 2025 22:47:17 +0800 Subject: [PATCH 05/11] Update src/error.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index a587852..2042420 100644 --- a/src/error.rs +++ b/src/error.rs @@ -46,7 +46,7 @@ define_exception!(BUILDER_ERROR, "BuilderError", |ruby: &Ruby| ruby .exception_runtime_error()); pub fn wreq_error_to_magnus(ruby: &Ruby, err: wreq::Error) -> MagnusError { - let error_msg = format!("{}", err); + let error_msg = err.to_string(); if err.is_builder() { MagnusError::new(ruby.get_inner(&BUILDER_ERROR), error_msg) From 27ade4fd4552d230cb90dbc8374c97f1d4a37c52 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Tue, 18 Nov 2025 22:47:56 +0800 Subject: [PATCH 06/11] Update src/error.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index 2042420..2ca6f67 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,7 +20,7 @@ define_exception!(CONNECTION_ERROR, "ConnectionError", |ruby: &Ruby| ruby define_exception!( CONNECTION_RESET_ERROR, "ConnectionResetError", - |ruby: &Ruby| ruby.exception_runtime_error() + |ruby: &Ruby| ruby.get_inner(&CONNECTION_ERROR) ); define_exception!(TLS_ERROR, "TlsError", |ruby: &Ruby| ruby .exception_runtime_error()); From 5e2c8af7702669e460545fcb7c73fca3063cfe67 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Tue, 18 Nov 2025 22:48:08 +0800 Subject: [PATCH 07/11] Update src/error.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/error.rs | 65 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/src/error.rs b/src/error.rs index 2ca6f67..95ec4ee 100644 --- a/src/error.rs +++ b/src/error.rs @@ -15,35 +15,62 @@ macro_rules! define_exception { } // Network connection errors -define_exception!(CONNECTION_ERROR, "ConnectionError", |ruby: &Ruby| ruby - .exception_runtime_error()); +define_exception!( + CONNECTION_ERROR, + "ConnectionError", + |ruby: &Ruby| ruby.exception_runtime_error() +); define_exception!( CONNECTION_RESET_ERROR, "ConnectionResetError", - |ruby: &Ruby| ruby.get_inner(&CONNECTION_ERROR) + |ruby: &Ruby| ruby.exception_runtime_error() +); +define_exception!( + TLS_ERROR, + "TlsError", + |ruby: &Ruby| ruby.exception_runtime_error() ); -define_exception!(TLS_ERROR, "TlsError", |ruby: &Ruby| ruby - .exception_runtime_error()); // HTTP protocol and request/response errors -define_exception!(REQUEST_ERROR, "RequestError", |ruby: &Ruby| ruby - .exception_runtime_error()); -define_exception!(STATUS_ERROR, "StatusError", |ruby: &Ruby| ruby - .exception_runtime_error()); -define_exception!(REDIRECT_ERROR, "RedirectError", |ruby: &Ruby| ruby - .exception_runtime_error()); -define_exception!(TIMEOUT_ERROR, "TimeoutError", |ruby: &Ruby| ruby - .exception_runtime_error()); +define_exception!( + REQUEST_ERROR, + "RequestError", + |ruby: &Ruby| ruby.exception_runtime_error() +); +define_exception!( + STATUS_ERROR, + "StatusError", + |ruby: &Ruby| ruby.exception_runtime_error() +); +define_exception!( + REDIRECT_ERROR, + "RedirectError", + |ruby: &Ruby| ruby.exception_runtime_error() +); +define_exception!( + TIMEOUT_ERROR, + "TimeoutError", + |ruby: &Ruby| ruby.exception_runtime_error() +); // Data processing and encoding errors -define_exception!(BODY_ERROR, "BodyError", |ruby: &Ruby| ruby - .exception_runtime_error()); -define_exception!(DECODING_ERROR, "DecodingError", |ruby: &Ruby| ruby - .exception_runtime_error()); +define_exception!( + BODY_ERROR, + "BodyError", + |ruby: &Ruby| ruby.exception_runtime_error() +); +define_exception!( + DECODING_ERROR, + "DecodingError", + |ruby: &Ruby| ruby.exception_runtime_error() +); // Configuration and builder errors -define_exception!(BUILDER_ERROR, "BuilderError", |ruby: &Ruby| ruby - .exception_runtime_error()); +define_exception!( + BUILDER_ERROR, + "BuilderError", + |ruby: &Ruby| ruby.exception_runtime_error() +); pub fn wreq_error_to_magnus(ruby: &Ruby, err: wreq::Error) -> MagnusError { let error_msg = err.to_string(); From 6c5513ff8f813ada951358d874f06ba5049e3bc9 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:51:02 +0800 Subject: [PATCH 08/11] Initial plan (#5) From c074ab7d1b125b2727d31ada0c5d30db3ea94a92 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:51:24 +0800 Subject: [PATCH 09/11] Initial plan (#6) From dac8c9b10267de717d02546f50559ca5bbfc350d Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Tue, 18 Nov 2025 23:00:03 +0800 Subject: [PATCH 10/11] fmt --- src/error.rs | 60 +++++++++++----------------------------------------- 1 file changed, 12 insertions(+), 48 deletions(-) diff --git a/src/error.rs b/src/error.rs index 95ec4ee..7cb9b8c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,72 +5,36 @@ use magnus::{ static WREQ: Lazy = Lazy::new(|ruby| ruby.define_module(crate::RUBY_MODULE_NAME).unwrap()); macro_rules! define_exception { - ($name:ident, $ruby_name:literal, $parent:expr) => { + ($name:ident, $ruby_name:literal, $parent_method:ident) => { static $name: Lazy = Lazy::new(|ruby| { ruby.get_inner(&WREQ) - .define_error($ruby_name, $parent(ruby)) + .define_error($ruby_name, ruby.$parent_method()) .unwrap() }); }; } // Network connection errors -define_exception!( - CONNECTION_ERROR, - "ConnectionError", - |ruby: &Ruby| ruby.exception_runtime_error() -); +define_exception!(CONNECTION_ERROR, "ConnectionError", exception_runtime_error); define_exception!( CONNECTION_RESET_ERROR, "ConnectionResetError", - |ruby: &Ruby| ruby.exception_runtime_error() -); -define_exception!( - TLS_ERROR, - "TlsError", - |ruby: &Ruby| ruby.exception_runtime_error() + exception_runtime_error ); +define_exception!(TLS_ERROR, "TlsError", exception_runtime_error); // HTTP protocol and request/response errors -define_exception!( - REQUEST_ERROR, - "RequestError", - |ruby: &Ruby| ruby.exception_runtime_error() -); -define_exception!( - STATUS_ERROR, - "StatusError", - |ruby: &Ruby| ruby.exception_runtime_error() -); -define_exception!( - REDIRECT_ERROR, - "RedirectError", - |ruby: &Ruby| ruby.exception_runtime_error() -); -define_exception!( - TIMEOUT_ERROR, - "TimeoutError", - |ruby: &Ruby| ruby.exception_runtime_error() -); +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", - |ruby: &Ruby| ruby.exception_runtime_error() -); -define_exception!( - DECODING_ERROR, - "DecodingError", - |ruby: &Ruby| ruby.exception_runtime_error() -); +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", - |ruby: &Ruby| ruby.exception_runtime_error() -); +define_exception!(BUILDER_ERROR, "BuilderError", exception_runtime_error); pub fn wreq_error_to_magnus(ruby: &Ruby, err: wreq::Error) -> MagnusError { let error_msg = err.to_string(); From b2dbe03f4c7e68670a565efc6b627da3f89c4331 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Tue, 18 Nov 2025 23:02:29 +0800 Subject: [PATCH 11/11] fmt --- src/error.rs | 53 ++++++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/error.rs b/src/error.rs index 7cb9b8c..43ec4c3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,6 +14,19 @@ macro_rules! define_exception { }; } +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!( @@ -36,32 +49,24 @@ 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(); - - if err.is_builder() { - MagnusError::new(ruby.get_inner(&BUILDER_ERROR), error_msg) - } else if err.is_body() { - MagnusError::new(ruby.get_inner(&BODY_ERROR), error_msg) - } else if err.is_tls() { - MagnusError::new(ruby.get_inner(&TLS_ERROR), error_msg) - } else if err.is_connection_reset() { - MagnusError::new(ruby.get_inner(&CONNECTION_RESET_ERROR), error_msg) - } else if err.is_connect() { - MagnusError::new(ruby.get_inner(&CONNECTION_ERROR), error_msg) - } else if err.is_decode() { - MagnusError::new(ruby.get_inner(&DECODING_ERROR), error_msg) - } else if err.is_redirect() { - MagnusError::new(ruby.get_inner(&REDIRECT_ERROR), error_msg) - } else if err.is_timeout() { - MagnusError::new(ruby.get_inner(&TIMEOUT_ERROR), error_msg) - } else if err.is_status() { - MagnusError::new(ruby.get_inner(&STATUS_ERROR), error_msg) - } else if err.is_request() { - MagnusError::new(ruby.get_inner(&REQUEST_ERROR), error_msg) - } else { - MagnusError::new(ruby.exception_runtime_error(), error_msg) - } + 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) {