Priority: P0
Related API contract: Provide a stable error hierarchy and safe, class-specific metadata
Describe the issue
Wreq::Client currently ignores unknown keys in client and request option hashes. A misspelling can therefore disable a timeout, proxy, redirect, TLS, or other transport control without any feedback.
Ruby callers generally expect keyword-like configuration to fail fast. Accepting extension keys is useful for middleware-oriented libraries, but wreq-ruby owns a finite native option surface and can validate it precisely.
Reproduction
Tested with wreq 1.2.4:
require "wreq"
client = Wreq::Client.new(timout: 1) # accepted; `timout` is ignored
client.get(
"http://127.0.0.1:1",
history: true # accepted; request proceeds to the transport
)
Known options can also accept values that are never applied. For example, Wreq::Client.new(proxy: Object.new) currently constructs a client instead of rejecting the unsupported value.
Constructor arity and mutually exclusive options are also not validated:
Wreq::Client.new("ignored") # creates a default client
Wreq::Client.new({}, {}) # extra positional argument is ignored
client.post(url, json: {a: 1}, body: "raw")
# sends body `raw` with Content-Type: application/json
client.post(url, form: {a: 1}, json: {b: 2})
# sends JSON bytes with Content-Type: application/x-www-form-urlencoded
client.post(url, bearer_auth: "token", basic_auth: ["user", "password"])
# emits two Authorization header fields
These combinations produce a valid-looking request object but an internally inconsistent wire request.
No-argument constructor safety
The binding's zero-argument branch calls Rust wreq::Client::new(), while the configured branch calls the fallible builder and maps its error to Ruby. The underlying Client::new() documentation states that it can panic when TLS or resolver initialization fails.
A Ruby constructor should use the fallible builder on every path. An environmental initialization failure must raise a normal Wreq::BuilderError/Wreq::TlsError; it must not unwind or abort through the native boundary.
Proposed Ruby behavior
- Validate keys independently for client options and request options.
- Raise
ArgumentError for unknown keys and include every invalid key in the message.
- Raise
TypeError or ArgumentError for invalid values of known options.
- Enforce constructor arity and reject non-Hash option arguments.
- Reject mutually exclusive body options (
body, form, and json) and authentication options (auth, bearer_auth, and basic_auth).
- Reject contradictory protocol controls such as enabling both HTTP/1-only and HTTP/2-only modes.
- Reject dependent options that cannot take effect, such as
max_redirects when redirects are disabled or not enabled.
- On platforms where an option is compiled out, raise a documented
Wreq::BuilderError or a specific Wreq::UnsupportedOptionError < Wreq::BuilderError rather than accepting and ignoring it. Do not use NotImplementedError: it inherits from ScriptError, so normal rescue StandardError and the proposed Wreq::Error transport rescue would not catch it.
- Preserve normal Ruby keyword behavior whether the caller passes keywords directly or expands a hash with
**options.
- If practical, suggest a close match for a typo:
Wreq::Client.new(timout: 1)
# ArgumentError: unknown option: :timout; did you mean :timeout?
This validation should apply consistently to Wreq::Client.new, Client#request, convenience verbs such as #get, and other native option entry points. APIs that intentionally accept arbitrary keys should document that exception explicitly.
Acceptance criteria
- Unknown client option keys raise before a native client or connection pool is created.
- Unknown request option keys raise before DNS or network I/O begins.
- Invalid values for known options raise and name the option.
- Conflicting body/authentication/protocol options raise before request construction.
- Unsupported platform-specific and ineffective dependent options fail explicitly.
- Compiled-out option failures remain catchable through
StandardError and, once available, the shared Wreq::Error hierarchy.
- Client and value-object constructors reject extra positional arguments instead of ignoring them.
- Zero-option and configured client construction both use a fallible native build path; initialization failure raises a Ruby exception rather than panicking.
- Error messages do not include secrets from unrelated option values.
- Tests cover a typo, multiple unknown keys, a wrong value type, constructor arity, each mutually exclusive group,
**hash expansion, and valid options.
- Public API documentation lists the accepted keys for each entry point.
Ruby ecosystem precedent
- http.rb raises
ArgumentError for unknown timeout options in its per-operation timeout implementation.
- Faraday stores request configuration in a defined
RequestOptions structure, so an unknown writer fails instead of disappearing silently.
- Ruby methods with explicit keywords raise
ArgumentError for unknown keywords by default.
wreq-python's builder extracts recognized values from **kwargs without rejecting leftovers. That is a useful warning about thin-binding extractors, not a reason for Ruby transport controls to ignore misspellings.
Relevant wreq-ruby source
Priority: P0
Related API contract: Provide a stable error hierarchy and safe, class-specific metadata
Describe the issue
Wreq::Clientcurrently ignores unknown keys in client and request option hashes. A misspelling can therefore disable a timeout, proxy, redirect, TLS, or other transport control without any feedback.Ruby callers generally expect keyword-like configuration to fail fast. Accepting extension keys is useful for middleware-oriented libraries, but wreq-ruby owns a finite native option surface and can validate it precisely.
Reproduction
Tested with wreq 1.2.4:
Known options can also accept values that are never applied. For example,
Wreq::Client.new(proxy: Object.new)currently constructs a client instead of rejecting the unsupported value.Constructor arity and mutually exclusive options are also not validated:
These combinations produce a valid-looking request object but an internally inconsistent wire request.
No-argument constructor safety
The binding's zero-argument branch calls Rust
wreq::Client::new(), while the configured branch calls the fallible builder and maps its error to Ruby. The underlyingClient::new()documentation states that it can panic when TLS or resolver initialization fails.A Ruby constructor should use the fallible builder on every path. An environmental initialization failure must raise a normal
Wreq::BuilderError/Wreq::TlsError; it must not unwind or abort through the native boundary.Proposed Ruby behavior
ArgumentErrorfor unknown keys and include every invalid key in the message.TypeErrororArgumentErrorfor invalid values of known options.body,form, andjson) and authentication options (auth,bearer_auth, andbasic_auth).max_redirectswhen redirects are disabled or not enabled.Wreq::BuilderErroror a specificWreq::UnsupportedOptionError < Wreq::BuilderErrorrather than accepting and ignoring it. Do not useNotImplementedError: it inherits fromScriptError, so normalrescue StandardErrorand the proposedWreq::Errortransport rescue would not catch it.**options.This validation should apply consistently to
Wreq::Client.new,Client#request, convenience verbs such as#get, and other native option entry points. APIs that intentionally accept arbitrary keys should document that exception explicitly.Acceptance criteria
StandardErrorand, once available, the sharedWreq::Errorhierarchy.**hashexpansion, and valid options.Ruby ecosystem precedent
ArgumentErrorfor unknown timeout options in its per-operation timeout implementation.RequestOptionsstructure, so an unknown writer fails instead of disappearing silently.ArgumentErrorfor unknown keywords by default.wreq-python's builder extracts recognized values from**kwargswithout rejecting leftovers. That is a useful warning about thin-binding extractors, not a reason for Ruby transport controls to ignore misspellings.Relevant wreq-ruby source
src/client.rssrc/client/req.rssrc/extractor.rswreq::Client::newpanic contract