Priority: P1
Describe the issue
wreq-ruby exposes useful specific exception classes, but each normal transport error directly subclasses RuntimeError. There is no shared Wreq::Error, and useful native details such as an HTTP status are reduced to a formatted message.
That makes robust rescue logic difficult: callers must enumerate every current exception class and parse human-readable strings. A stable root exception would make the binding easier to integrate and safer to upgrade.
Reproduction
Tested with wreq 1.2.4:
require "wreq"
Wreq::ConnectionError.superclass # => RuntimeError
Wreq::TimeoutError.superclass # => RuntimeError
Wreq::BodyError.superclass # => RuntimeError
begin
Wreq.get("http://127.0.0.1:1/?token=example-secret")
rescue Wreq::ConnectionError => error
error.message # includes the complete URI query
end
Proposed Ruby API
module Wreq
# Keep RuntimeError ancestry for 1.x compatibility. RuntimeError itself
# inherits from StandardError.
class Error < RuntimeError; end
class ConnectionError < Error; end
class TimeoutError < Error; end
class BodyError < Error; end
# Existing regular transport errors also inherit from Error.
# Keep interruption outside StandardError so a broad transport rescue
# never swallows a Ruby interrupt.
class InterruptError < Interrupt; end
end
Making Wreq::Error a direct child of StandardError would be a reasonable clean-slate design, but it would stop existing rescue RuntimeError handlers from catching these exceptions. Inserting the root below RuntimeError preserves that public behavior while still providing the library-specific rescue point. A direct-superclass change can be considered only in a major release.
The exception subclass should identify the error category. A second generic kind symbol would duplicate that hierarchy and is not a common Ruby HTTP-client convention. Likewise, retryable? is usually caller policy: retry safety depends on the request method, whether its body is replayable, and whether the server might already have processed it.
Add metadata only to the exception class where it has clear semantics. For example:
class Wreq::StatusError < Wreq::Error
attr_reader :status
end
The underlying Rust error exposes an optional URI and status. StatusError#status is useful and unambiguous. A URI reader is less clearly safe because it can contain credentials or query secrets; if exposed, document its type and sensitivity, and do not include sensitive components in default string representations.
Preserve Ruby's standard exception chaining through Exception#cause where an underlying Ruby exception exists. There is no need for a parallel wrapped_exception API unless native exception chaining cannot represent the source.
Opt-in HTTP status errors
The response API should also expose the upstream status check without changing the default scraping-friendly behavior:
response = client.get("https://example.com/missing")
response.raise_for_status!
# raises Wreq::StatusError with status == 404
Response#raise_for_status! should return self for a successful response. It should remain opt-in because a 4xx or 5xx response is still a valid HTTP response, and callers commonly need its headers and body. A client-level raise_for_status: true convenience can be considered separately, but is not required for the root hierarchy.
Sensitive data handling
Exception messages and inspect output should not include URL userinfo, query values, fragments, proxy credentials, authorization headers, or cookies by default. The Rust API explicitly provides without_uri for this concern, but the Ruby binding currently formats the complete Rust error before raising it.
Acceptance criteria
- Every regular wreq-ruby transport exception inherits from
Wreq::Error, which remains both a RuntimeError and a StandardError for 1.x compatibility.
- Existing specific exception constants remain available for backward compatibility.
Wreq::InterruptError remains an Interrupt, not a StandardError.
Wreq::StatusError#status returns an integer status without parsing #message.
Response#raise_for_status! returns the response for non-error statuses and raises Wreq::StatusError for 4xx/5xx statuses.
- Any additional metadata is class-specific, documented, and backed by data the native error actually retains.
- No generic
kind or retryable? API is required by this issue.
- Exception messages redact sensitive URI components by default.
- Tests cover rescuing the root class, rescuing a specific subclass, status metadata, chaining, redaction, and interrupt behavior.
Ruby ecosystem precedent
- HTTPX defines
HTTPX::Error < StandardError with timeout subclasses.
- http.rb defines
HTTP::Error as the root for client errors.
- HTTPX and http.rb put response/status metadata on HTTP-status-specific errors rather than defining generic
kind, URI, and retry-policy readers on every error.
- Faraday's
Faraday::Error exposes response metadata and preserves a wrapped exception.
- Excon documents an
Excon::Error family for transport failures.
- HTTPX supports redacting sensitive debug data.
- The underlying
wreq::Error exposes optional URI and status data and warns that URIs may contain secrets.
wreq-python exposes the same upstream status check as Response.raise_for_status() while leaving it disabled by default.
Relevant wreq-ruby source
Priority: P1
Describe the issue
wreq-ruby exposes useful specific exception classes, but each normal transport error directly subclasses
RuntimeError. There is no sharedWreq::Error, and useful native details such as an HTTP status are reduced to a formatted message.That makes robust rescue logic difficult: callers must enumerate every current exception class and parse human-readable strings. A stable root exception would make the binding easier to integrate and safer to upgrade.
Reproduction
Tested with wreq 1.2.4:
Proposed Ruby API
Making
Wreq::Errora direct child ofStandardErrorwould be a reasonable clean-slate design, but it would stop existingrescue RuntimeErrorhandlers from catching these exceptions. Inserting the root belowRuntimeErrorpreserves that public behavior while still providing the library-specific rescue point. A direct-superclass change can be considered only in a major release.The exception subclass should identify the error category. A second generic
kindsymbol would duplicate that hierarchy and is not a common Ruby HTTP-client convention. Likewise,retryable?is usually caller policy: retry safety depends on the request method, whether its body is replayable, and whether the server might already have processed it.Add metadata only to the exception class where it has clear semantics. For example:
The underlying Rust error exposes an optional URI and status.
StatusError#statusis useful and unambiguous. A URI reader is less clearly safe because it can contain credentials or query secrets; if exposed, document its type and sensitivity, and do not include sensitive components in default string representations.Preserve Ruby's standard exception chaining through
Exception#causewhere an underlying Ruby exception exists. There is no need for a parallelwrapped_exceptionAPI unless native exception chaining cannot represent the source.Opt-in HTTP status errors
The response API should also expose the upstream status check without changing the default scraping-friendly behavior:
Response#raise_for_status!should returnselffor a successful response. It should remain opt-in because a4xxor5xxresponse is still a valid HTTP response, and callers commonly need its headers and body. A client-levelraise_for_status: trueconvenience can be considered separately, but is not required for the root hierarchy.Sensitive data handling
Exception messages and
inspectoutput should not include URL userinfo, query values, fragments, proxy credentials, authorization headers, or cookies by default. The Rust API explicitly provideswithout_urifor this concern, but the Ruby binding currently formats the complete Rust error before raising it.Acceptance criteria
Wreq::Error, which remains both aRuntimeErrorand aStandardErrorfor 1.x compatibility.Wreq::InterruptErrorremains anInterrupt, not aStandardError.Wreq::StatusError#statusreturns an integer status without parsing#message.Response#raise_for_status!returns the response for non-error statuses and raisesWreq::StatusErrorfor4xx/5xxstatuses.kindorretryable?API is required by this issue.Ruby ecosystem precedent
HTTPX::Error < StandardErrorwith timeout subclasses.HTTP::Erroras the root for client errors.kind, URI, and retry-policy readers on every error.Faraday::Errorexposes response metadata and preserves a wrapped exception.Excon::Errorfamily for transport failures.wreq::Errorexposes optional URI and status data and warns that URIs may contain secrets.wreq-pythonexposes the same upstream status check asResponse.raise_for_status()while leaving it disabled by default.Relevant wreq-ruby source
src/error.rslib/wreq_ruby/error.rb