Priority: P1
Describe the issue
Wreq::Headers has good native primitives—case-insensitive get, duplicate-preserving get_all, set, append, remove, clear, length, and block iteration—but it does not implement the Ruby collection API documented by the gem.
The v1.2.4 Ruby documentation shows headers[name], headers[name] = value, headers.to_h, and blockless headers.each. At runtime those first three methods are missing and blockless each raises LocalJumpError.
Reproduction
require "wreq"
headers = Wreq::Headers.new
headers.append("Set-Cookie", "a=1")
headers.append("Set-Cookie", "b=2")
headers.get_all("set-cookie") # => ["a=1", "b=2"]
headers.each # raises LocalJumpError: no block given
headers.respond_to?(:[]) # => false
headers.respond_to?(:[]=) # => false
headers.respond_to?(:fetch) # => false
headers.respond_to?(:delete) # => false
headers.respond_to?(:size) # => false
headers.respond_to?(:to_a) # => false
headers.respond_to?(:to_h) # => false
Wreq::Headers.new("Accept" => "text/html")
# raises ArgumentError: wrong number of arguments
Proposed Ruby API
Preserve the existing methods and add familiar aliases/protocols:
class Wreq::Headers
include Enumerable
# []= delegates to set; delete delegates to remove;
# size delegates to length.
end
headers = Wreq::Headers.new(
accept: "text/html",
"Set-Cookie" => ["a=1", "b=2"]
)
Expected behavior:
headers["content-type"]
headers["set-cookie"] # => ["a=1", "b=2"]
headers["authorization"] = "Bearer token"
headers[:content_type] = "application/json"
headers.fetch("x-request-id")
headers.delete("authorization")
headers.size
headers.each # => Enumerator
headers.each.to_a # preserves every field-value occurrence
headers.select { |name, _| name.start_with?("x-") }
Duplicate-safe conversion
HTTP permits repeated fields, and Set-Cookie must not be comma-folded. Conversion must not silently discard duplicate values.
Use the shape established by HTTP::Headers: [], fetch, and to_h return nil for a missing field, a String for one value, and an Array for repeated values. Keep the existing get(name) behavior (first value or nil) and get_all(name) behavior (always an Array) for callers that need a fixed return type.
For example:
headers.to_h
# => {
# "content-type" => "text/html",
# "set-cookie" => ["a=1", "b=2"]
# }
Keep get_all(name) as the unambiguous way to retrieve every value. Document whether each yields every occurrence or one pair per unique name; yielding every occurrence matches current native behavior and avoids data loss.
length/size should count field-value occurrences, matching each and the current native length. keys.length remains the way to count unique field names.
String names should retain their spelling/casing behavior. Symbol names should follow the established Ruby HTTP convention of turning underscores/dashes into a canonical field name (:content_type to Content-Type). Constructor and setter array values should append one field occurrence per value rather than joining Set-Cookie unsafely.
Collection return values should also be deliberate: block iteration and clear should return self, while each without a block returns an Enumerator. []= naturally evaluates to the assigned value in Ruby; document the return values of set, append, and delete rather than inheriting native nil accidentally.
Response-header semantics
Document whether response headers are a mutable snapshot or a read-only view. Object identity is not part of the requested contract: callers should not need response.headers.equal?(response.headers) to be true, and an implementation may return a fresh snapshot when that is the safest native ownership model.
Acceptance criteria
Wreq::Headers includes Enumerable.
Wreq::Headers.new accepts a Hash, another header collection, or an enumerable of name/value pairs; the zero-argument form remains valid.
each returns an Enumerator without a block and returns self with a block.
[], []=, fetch, delete, size, to_a, to_h, and to_hash are implemented and documented.
[], fetch, and to_h use the documented nil/String/Array shape; get and get_all retain their existing fixed shapes.
- Lookups remain case-insensitive.
- String and Symbol header names are accepted with documented wire-name normalization.
- Array values preserve repeated field occurrences.
- Repeated fields survive iteration and conversion without silent loss or unsafe comma folding.
- Existing
get, get_all, set, append, remove, clear, and length remain available. Their value behavior remains compatible except for the intentional Ruby-collection normalization that clear returns self instead of its current nil.
- Response-header mutability and snapshot/view semantics are documented; no wrapper-identity guarantee is required.
- Tests cover construction, casing, missing keys,
fetch defaults/blocks, duplicate Set-Cookie, clear and its return value, enumerator chaining, and documented response snapshot/view semantics.
Ruby ecosystem precedent
Net::HTTPHeader provides [], []=, fetch, delete, size, blockless enumeration, get_fields, and duplicate-preserving to_hash values.
HTTP::Headers includes Enumerable, returns an enumerator without a block, and uses nil/String/Array lookup and conversion semantics for repeated fields.
- HTTPX response headers support case-insensitive
[], to_h, and to_a.
Faraday::Utils::Headers subclasses Hash and preserves case-insensitive lookup.
wreq-python's HeaderMap provides duplicate-aware mapping and iteration behavior while allowing response header wrappers to be copied snapshots.
Relevant wreq-ruby source
Priority: P1
Describe the issue
Wreq::Headershas good native primitives—case-insensitiveget, duplicate-preservingget_all,set,append,remove,clear,length, and block iteration—but it does not implement the Ruby collection API documented by the gem.The v1.2.4 Ruby documentation shows
headers[name],headers[name] = value,headers.to_h, and blocklessheaders.each. At runtime those first three methods are missing and blocklesseachraisesLocalJumpError.Reproduction
Proposed Ruby API
Preserve the existing methods and add familiar aliases/protocols:
Expected behavior:
Duplicate-safe conversion
HTTP permits repeated fields, and
Set-Cookiemust not be comma-folded. Conversion must not silently discard duplicate values.Use the shape established by
HTTP::Headers:[],fetch, andto_hreturnnilfor a missing field, a String for one value, and an Array for repeated values. Keep the existingget(name)behavior (first value ornil) andget_all(name)behavior (always an Array) for callers that need a fixed return type.For example:
Keep
get_all(name)as the unambiguous way to retrieve every value. Document whethereachyields every occurrence or one pair per unique name; yielding every occurrence matches current native behavior and avoids data loss.length/sizeshould count field-value occurrences, matchingeachand the current nativelength.keys.lengthremains the way to count unique field names.String names should retain their spelling/casing behavior. Symbol names should follow the established Ruby HTTP convention of turning underscores/dashes into a canonical field name (
:content_typetoContent-Type). Constructor and setter array values should append one field occurrence per value rather than joiningSet-Cookieunsafely.Collection return values should also be deliberate: block iteration and
clearshould returnself, whileeachwithout a block returns an Enumerator.[]=naturally evaluates to the assigned value in Ruby; document the return values ofset,append, anddeleterather than inheriting nativenilaccidentally.Response-header semantics
Document whether response headers are a mutable snapshot or a read-only view. Object identity is not part of the requested contract: callers should not need
response.headers.equal?(response.headers)to be true, and an implementation may return a fresh snapshot when that is the safest native ownership model.Acceptance criteria
Wreq::HeadersincludesEnumerable.Wreq::Headers.newaccepts a Hash, another header collection, or an enumerable of name/value pairs; the zero-argument form remains valid.eachreturns anEnumeratorwithout a block and returnsselfwith a block.[],[]=,fetch,delete,size,to_a,to_h, andto_hashare implemented and documented.[],fetch, andto_huse the documented nil/String/Array shape;getandget_allretain their existing fixed shapes.get,get_all,set,append,remove,clear, andlengthremain available. Their value behavior remains compatible except for the intentional Ruby-collection normalization thatclearreturnsselfinstead of its currentnil.fetchdefaults/blocks, duplicateSet-Cookie,clearand its return value, enumerator chaining, and documented response snapshot/view semantics.Ruby ecosystem precedent
Net::HTTPHeaderprovides[],[]=,fetch,delete,size, blockless enumeration,get_fields, and duplicate-preservingto_hashvalues.HTTP::HeadersincludesEnumerable, returns an enumerator without a block, and uses nil/String/Array lookup and conversion semantics for repeated fields.[],to_h, andto_a.Faraday::Utils::HeaderssubclassesHashand preserves case-insensitive lookup.wreq-python'sHeaderMapprovides duplicate-aware mapping and iteration behavior while allowing response header wrappers to be copied snapshots.Relevant wreq-ruby source
src/header.rslib/wreq_ruby/header.rbsrc/client/resp.rs