wreq 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55fb3870a295fb5c880d3114c560170df733c64738bf07fae26bcbe7055a3d0e
4
- data.tar.gz: d40e5a5942c85fbfb80c3a6dc76ee1bdc4da80a2286f38d217bdf2fe18e7e924
3
+ metadata.gz: 15ad9f6cc6c41b23c065d2118942ce008260dd0b0ec0124e03d5d72379ed9dda
4
+ data.tar.gz: ef0c61872bffe6dc3e3e7e820d1d774f944d129a5e486e1c8fe50c53d06b4c5d
5
5
  SHA512:
6
- metadata.gz: 7eef95347359cbfbf15654d89425eee6ca329f3603d2c5467368f3f90118c7195b201fe2380026354901998a7bb73befccff227e5f71040d513b0953e09410ee
7
- data.tar.gz: d8b5c0a8416a36ef9f43721f824fb8f7201c8b31e709b2fd42c5b1ef61f5da8031fc529725013832efd4a1101f86d6e503c4e67e1d9f84c790d35897e2090fac
6
+ metadata.gz: a2c870dd5fedc1dfd0c72de4b1567b63f9a44ce361a7f6e47a57bfe751ddf1c083a4828542f778236ce1bc2bc529e7b85df19ef33c742580a695df33ac114cd2
7
+ data.tar.gz: b2616fcfc85af1ac603ea330ca6f2c697aa81049d36471df4619327ad0d549af974078f14d4d3f76aee0e581ac8f8fed6d711778d64c8b82fad8c038cec3f14b
data/Cargo.lock CHANGED
@@ -1559,7 +1559,7 @@ dependencies = [
1559
1559
 
1560
1560
  [[package]]
1561
1561
  name = "wreq-ruby"
1562
- version = "1.2.0"
1562
+ version = "1.2.1"
1563
1563
  dependencies = [
1564
1564
  "arc-swap",
1565
1565
  "bytes",
data/Cargo.toml CHANGED
@@ -6,7 +6,7 @@ homepage = "https://github.com/SearchApi/wreq-ruby"
6
6
  repository = "https://github.com/SearchApi/wreq-ruby"
7
7
  edition = "2024"
8
8
  rust-version = "1.85"
9
- version = "1.2.0"
9
+ version = "1.2.1"
10
10
 
11
11
  [lib]
12
12
  crate-type = ["cdylib"]
@@ -38,7 +38,7 @@ indexmap = { version = "2.14.0", features = ["serde"] }
38
38
  cookie = "0.18.1"
39
39
  bytes = "1.11.1"
40
40
  arc-swap = "1.9.1"
41
- http = "1.4.0"
41
+ http = "1.4.1"
42
42
  http-body-util = "0.1.3"
43
43
  futures-util = { version = "0.3.32", default-features = false }
44
44
 
data/lib/wreq.rb CHANGED
@@ -25,7 +25,7 @@ unless defined?(Wreq)
25
25
  # @param method [Wreq::Method] HTTP method to use
26
26
  # @param url [String] Target URL
27
27
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
28
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
28
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
29
29
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
30
30
  # @param query [Hash, nil] URL query parameters
31
31
  # @param auth [String, nil] Authorization header value
@@ -56,7 +56,7 @@ unless defined?(Wreq)
56
56
  #
57
57
  # @param url [String] Target URL
58
58
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
59
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
59
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
60
60
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
61
61
  # @param query [Hash, nil] URL query parameters
62
62
  # @param auth [String, nil] Authorization header value
@@ -87,7 +87,7 @@ unless defined?(Wreq)
87
87
  #
88
88
  # @param url [String] Target URL
89
89
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
90
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
90
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
91
91
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
92
92
  # @param query [Hash, nil] URL query parameters
93
93
  # @param auth [String, nil] Authorization header value
@@ -118,7 +118,7 @@ unless defined?(Wreq)
118
118
  #
119
119
  # @param url [String] Target URL
120
120
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
121
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
121
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
122
122
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
123
123
  # @param query [Hash, nil] URL query parameters
124
124
  # @param auth [String, nil] Authorization header value
@@ -149,7 +149,7 @@ unless defined?(Wreq)
149
149
  #
150
150
  # @param url [String] Target URL
151
151
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
152
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
152
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
153
153
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
154
154
  # @param query [Hash, nil] URL query parameters
155
155
  # @param auth [String, nil] Authorization header value
@@ -180,7 +180,7 @@ unless defined?(Wreq)
180
180
  #
181
181
  # @param url [String] Target URL
182
182
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
183
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
183
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
184
184
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
185
185
  # @param query [Hash, nil] URL query parameters
186
186
  # @param auth [String, nil] Authorization header value
@@ -211,7 +211,7 @@ unless defined?(Wreq)
211
211
  #
212
212
  # @param url [String] Target URL
213
213
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
214
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
214
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
215
215
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
216
216
  # @param query [Hash, nil] URL query parameters
217
217
  # @param auth [String, nil] Authorization header value
@@ -242,7 +242,7 @@ unless defined?(Wreq)
242
242
  #
243
243
  # @param url [String] Target URL
244
244
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
245
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
245
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
246
246
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
247
247
  # @param query [Hash, nil] URL query parameters
248
248
  # @param auth [String, nil] Authorization header value
@@ -273,7 +273,7 @@ unless defined?(Wreq)
273
273
  #
274
274
  # @param url [String] Target URL
275
275
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
276
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
276
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
277
277
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
278
278
  # @param query [Hash, nil] URL query parameters
279
279
  # @param auth [String, nil] Authorization header value
@@ -38,6 +38,8 @@ unless defined?(Wreq)
38
38
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Default headers to include
39
39
  # in every request. Header names are case-insensitive. These headers
40
40
  # can be overridden on a per-request basis.
41
+ # @param orig_headers [Array<String>, nil] Original header names used to
42
+ # preserve raw header order and HTTP/1 case-sensitive header handling.
41
43
  #
42
44
  # @param referer [Boolean, nil] Whether to automatically send Referer
43
45
  # headers when following redirects. When true, the previous URL will
@@ -237,7 +239,7 @@ unless defined?(Wreq)
237
239
  # @param method [Wreq::Method] HTTP method to use
238
240
  # @param url [String] Target URL
239
241
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
240
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
242
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
241
243
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
242
244
  # @param query [Hash, nil] URL query parameters
243
245
  # @param auth [String, nil] Authorization header value
@@ -268,7 +270,7 @@ unless defined?(Wreq)
268
270
  #
269
271
  # @param url [String] Target URL
270
272
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
271
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
273
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
272
274
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
273
275
  # @param query [Hash, nil] URL query parameters
274
276
  # @param auth [String, nil] Authorization header value
@@ -299,7 +301,7 @@ unless defined?(Wreq)
299
301
  #
300
302
  # @param url [String] Target URL
301
303
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
302
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
304
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
303
305
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
304
306
  # @param query [Hash, nil] URL query parameters
305
307
  # @param auth [String, nil] Authorization header value
@@ -330,7 +332,7 @@ unless defined?(Wreq)
330
332
  #
331
333
  # @param url [String] Target URL
332
334
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
333
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
335
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
334
336
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
335
337
  # @param query [Hash, nil] URL query parameters
336
338
  # @param auth [String, nil] Authorization header value
@@ -361,7 +363,7 @@ unless defined?(Wreq)
361
363
  #
362
364
  # @param url [String] Target URL
363
365
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
364
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
366
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
365
367
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
366
368
  # @param query [Hash, nil] URL query parameters
367
369
  # @param auth [String, nil] Authorization header value
@@ -392,7 +394,7 @@ unless defined?(Wreq)
392
394
  #
393
395
  # @param url [String] Target URL
394
396
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
395
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
397
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
396
398
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
397
399
  # @param query [Hash, nil] URL query parameters
398
400
  # @param auth [String, nil] Authorization header value
@@ -423,7 +425,7 @@ unless defined?(Wreq)
423
425
  #
424
426
  # @param url [String] Target URL
425
427
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
426
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
428
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
427
429
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
428
430
  # @param query [Hash, nil] URL query parameters
429
431
  # @param auth [String, nil] Authorization header value
@@ -454,7 +456,7 @@ unless defined?(Wreq)
454
456
  #
455
457
  # @param url [String] Target URL
456
458
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
457
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
459
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
458
460
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
459
461
  # @param query [Hash, nil] URL query parameters
460
462
  # @param auth [String, nil] Authorization header value
@@ -485,7 +487,7 @@ unless defined?(Wreq)
485
487
  #
486
488
  # @param url [String] Target URL
487
489
  # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request
488
- # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified)
490
+ # @param orig_headers [Array<String>, nil] Original header names used to preserve raw header order and HTTP/1 case-sensitive header handling
489
491
  # @param default_headers [Hash{String=>String}, nil] Default headers to merge
490
492
  # @param query [Hash, nil] URL query parameters
491
493
  # @param auth [String, nil] Authorization header value
@@ -49,6 +49,20 @@ module Wreq
49
49
  def to_s
50
50
  end
51
51
  end
52
+
53
+ # Compares HTTP versions by semantic value, not object identity.
54
+ #
55
+ # This method is implemented by the native extension.
56
+ # When comparing with non-{Wreq::Version} objects, it returns false.
57
+ #
58
+ # @param other [Object] object to compare against
59
+ # @return [Boolean] true when both represent the same HTTP version
60
+ # @example
61
+ # Wreq::Version::HTTP_11 == response.version
62
+ unless method_defined?(:==)
63
+ def ==(other)
64
+ end
65
+ end
52
66
  end
53
67
 
54
68
  # HTTP status code wrapper.
data/src/client/req.rs CHANGED
@@ -3,7 +3,7 @@ use std::{net::IpAddr, time::Duration};
3
3
  use http::header;
4
4
  use magnus::{RHash, TryConvert, typed_data::Obj, value::ReprValue};
5
5
  use serde::Deserialize;
6
- use wreq::{Client, Proxy, Version, header::OrigHeaderMap};
6
+ use wreq::{Client, Proxy};
7
7
 
8
8
  use super::body::{Body, Form, Json};
9
9
  use crate::{
@@ -12,8 +12,8 @@ use crate::{
12
12
  emulate::Emulation,
13
13
  error::wreq_error_to_magnus,
14
14
  extractor::Extractor,
15
- header::Headers,
16
- http::Method,
15
+ header::{Headers, OrigHeaders},
16
+ http::{Method, Version},
17
17
  rt,
18
18
  };
19
19
 
@@ -54,7 +54,7 @@ pub struct Request {
54
54
 
55
55
  /// The original headers to use for the request.
56
56
  #[serde(skip)]
57
- orig_headers: Option<OrigHeaderMap>,
57
+ orig_headers: Option<OrigHeaders>,
58
58
 
59
59
  /// The cookies to use for the request.
60
60
  #[serde(skip)]
@@ -112,10 +112,18 @@ impl Request {
112
112
  builder.emulation = Some((*obj).clone());
113
113
  }
114
114
 
115
+ if let Some(v) = hash.get(ruby.to_symbol(stringify!(version))) {
116
+ builder.version = Some(Version::try_convert(v)?);
117
+ }
118
+
115
119
  if let Some(v) = hash.get(ruby.to_symbol(stringify!(headers))) {
116
120
  builder.headers = Some(Headers::try_convert(v)?);
117
121
  }
118
122
 
123
+ if let Some(v) = hash.get(ruby.to_symbol(stringify!(orig_headers))) {
124
+ builder.orig_headers = Some(OrigHeaders::try_convert(v)?);
125
+ }
126
+
119
127
  if let Some(v) = hash.get(ruby.to_symbol(stringify!(cookies))) {
120
128
  builder.cookies = Some(Cookies::try_convert(v)?);
121
129
  }
@@ -125,8 +133,6 @@ impl Request {
125
133
  }
126
134
 
127
135
  builder.proxy = Extractor::<Proxy>::try_convert(keyword)?.into_inner();
128
- builder.version = Extractor::<Version>::try_convert(keyword)?.into_inner();
129
- builder.orig_headers = Extractor::<OrigHeaderMap>::try_convert(keyword)?.into_inner();
130
136
 
131
137
  Ok(builder)
132
138
  }
@@ -145,7 +151,13 @@ pub fn execute_request<U: AsRef<str>>(
145
151
  apply_option!(set_if_some_inner, builder, request.emulation, emulation);
146
152
 
147
153
  // Version options.
148
- apply_option!(set_if_some, builder, request.version, version);
154
+ apply_option!(
155
+ set_if_some_map,
156
+ builder,
157
+ request.version,
158
+ version,
159
+ Version::into_ffi
160
+ );
149
161
 
150
162
  // Timeout options.
151
163
  apply_option!(
@@ -170,7 +182,12 @@ pub fn execute_request<U: AsRef<str>>(
170
182
 
171
183
  // Headers options.
172
184
  apply_option!(set_if_some_into_inner, builder, request.headers, headers);
173
- apply_option!(set_if_some, builder, request.orig_headers, orig_headers);
185
+ apply_option!(
186
+ set_if_some_inner,
187
+ builder,
188
+ request.orig_headers,
189
+ orig_headers
190
+ );
174
191
  apply_option!(
175
192
  set_if_some,
176
193
  builder,
data/src/client.rs CHANGED
@@ -10,10 +10,7 @@ use magnus::{
10
10
  Module, Object, RHash, RModule, Ruby, TryConvert, Value, function, method, typed_data::Obj,
11
11
  };
12
12
  use serde::Deserialize;
13
- use wreq::{
14
- Proxy,
15
- header::{HeaderValue, OrigHeaderMap},
16
- };
13
+ use wreq::{Proxy, header::HeaderValue};
17
14
 
18
15
  use crate::{
19
16
  client::{req::execute_request, resp::Response},
@@ -22,7 +19,7 @@ use crate::{
22
19
  error::wreq_error_to_magnus,
23
20
  extractor::Extractor,
24
21
  gvl,
25
- header::Headers,
22
+ header::{Headers, OrigHeaders},
26
23
  http::Method,
27
24
  };
28
25
 
@@ -40,7 +37,7 @@ struct Builder {
40
37
  headers: Option<Headers>,
41
38
  /// The original headers to use for the client.
42
39
  #[serde(skip)]
43
- orig_headers: Option<OrigHeaderMap>,
40
+ orig_headers: Option<OrigHeaders>,
44
41
  /// Whether to use referer.
45
42
  referer: Option<bool>,
46
43
  /// Whether to allow redirects.
@@ -143,6 +140,10 @@ impl Builder {
143
140
  builder.headers = Some(Headers::try_convert(v)?);
144
141
  }
145
142
 
143
+ if let Some(v) = hash.get(ruby.to_symbol(stringify!(orig_headers))) {
144
+ builder.orig_headers = Some(OrigHeaders::try_convert(v)?);
145
+ }
146
+
146
147
  if let Some(v) = hash.get(ruby.to_symbol(stringify!(cookie_provider))) {
147
148
  builder.cookie_provider = Some((*Obj::<Jar>::try_convert(v)?).clone());
148
149
  }
@@ -152,7 +153,6 @@ impl Builder {
152
153
  }
153
154
 
154
155
  builder.user_agent = Extractor::<HeaderValue>::try_convert(*keyword)?.into_inner();
155
- builder.orig_headers = Extractor::<OrigHeaderMap>::try_convert(*keyword)?.into_inner();
156
156
  builder.proxy = Extractor::<Proxy>::try_convert(*keyword)?.into_inner();
157
157
 
158
158
  Ok(builder)
@@ -175,14 +175,19 @@ impl Client {
175
175
  // User agent options.
176
176
  apply_option!(set_if_some, builder, params.user_agent, user_agent);
177
177
 
178
- // Default headers options.
178
+ // Headers options.
179
179
  apply_option!(
180
180
  set_if_some_into_inner,
181
181
  builder,
182
182
  params.headers,
183
183
  default_headers
184
184
  );
185
- apply_option!(set_if_some, builder, params.orig_headers, orig_headers);
185
+ apply_option!(
186
+ set_if_some_inner,
187
+ builder,
188
+ params.orig_headers,
189
+ orig_headers
190
+ );
186
191
 
187
192
  // Allow redirects options.
188
193
  apply_option!(set_if_some, builder, params.referer, referer);
data/src/error.rs CHANGED
@@ -117,6 +117,14 @@ pub fn header_value_error_to_magnus(err: wreq::header::InvalidHeaderValue) -> Ma
117
117
  )
118
118
  }
119
119
 
120
+ /// Map type/value errors to corresponding [`magnus::Error`]
121
+ pub fn type_value_error_to_magnus(err: &str) -> MagnusError {
122
+ MagnusError::new(
123
+ ruby!().get_inner(&BUILDER_ERROR),
124
+ format!("type error: {err}"),
125
+ )
126
+ }
127
+
120
128
  /// Map [`wreq::Error`] to corresponding [`magnus::Error`]
121
129
  pub fn wreq_error_to_magnus(err: wreq::Error) -> MagnusError {
122
130
  let error_msg = err.to_string();
data/src/extractor.rs CHANGED
@@ -1,6 +1,6 @@
1
1
  use magnus::{RArray, RHash, RString, Ruby, TryConvert, r_hash::ForEach};
2
2
  use wreq::{
3
- Proxy, Version,
3
+ Proxy,
4
4
  header::{HeaderMap, HeaderName, HeaderValue, OrigHeaderMap},
5
5
  };
6
6
 
@@ -32,27 +32,6 @@ where
32
32
  }
33
33
  }
34
34
 
35
- // ===== impl Extractor<Version> =====
36
-
37
- impl ExtractorName for Version {
38
- const NAME: &str = "version";
39
- }
40
-
41
- impl TryConvert for Extractor<Version> {
42
- fn try_convert(value: magnus::Value) -> Result<Self, magnus::Error> {
43
- let keyword = RHash::try_convert(value)?;
44
- if let Some(version_val) = keyword.get(Version::NAME) {
45
- return <&crate::http::Version>::try_convert(version_val)
46
- .cloned()
47
- .map(crate::http::Version::into_ffi)
48
- .map(Some)
49
- .map(Extractor);
50
- }
51
-
52
- Ok(Extractor(None))
53
- }
54
- }
55
-
56
35
  // ===== impl Extractor<HeaderValue> =====
57
36
 
58
37
  impl ExtractorName for HeaderValue {
data/src/header.rs CHANGED
@@ -9,8 +9,11 @@ use magnus::{
9
9
  r_hash::ForEach,
10
10
  typed_data::{Inspect, Obj},
11
11
  };
12
+ use wreq::header::OrigHeaderMap;
12
13
 
13
- use crate::error::{header_name_error_to_magnus, header_value_error_to_magnus};
14
+ use crate::error::{
15
+ header_name_error_to_magnus, header_value_error_to_magnus, type_value_error_to_magnus,
16
+ };
14
17
 
15
18
  /// HTTP headers collection with read and write operations.
16
19
  ///
@@ -20,6 +23,9 @@ use crate::error::{header_name_error_to_magnus, header_value_error_to_magnus};
20
23
  #[magnus::wrap(class = "Wreq::Headers", free_immediately, size)]
21
24
  pub struct Headers(pub RefCell<HeaderMap>);
22
25
 
26
+ /// A map from header names to their original casing as received in an HTTP message.
27
+ pub struct OrigHeaders(pub OrigHeaderMap);
28
+
23
29
  struct HeaderIter {
24
30
  inner: http::header::IntoIter<HeaderValue>,
25
31
  next_name: Option<HeaderName>,
@@ -166,6 +172,23 @@ impl TryConvert for Headers {
166
172
  }
167
173
  }
168
174
 
175
+ // ===== impl OrigHeaders =====
176
+
177
+ impl TryConvert for OrigHeaders {
178
+ fn try_convert(value: magnus::Value) -> Result<Self, magnus::Error> {
179
+ let mut map = OrigHeaderMap::new();
180
+
181
+ let rarray = RArray::from_value(value)
182
+ .ok_or_else(|| type_value_error_to_magnus("Expected an array of strings"))?;
183
+
184
+ for value in rarray.into_iter().flat_map(RString::from_value) {
185
+ map.insert(value.to_bytes());
186
+ }
187
+
188
+ Ok(Self(map))
189
+ }
190
+ }
191
+
169
192
  // ===== impl HeaderIter =====
170
193
 
171
194
  impl Iterator for HeaderIter {
data/src/http.rs CHANGED
@@ -1,4 +1,4 @@
1
- use magnus::{Error, Module, RModule, Ruby, method, typed_data::Inspect};
1
+ use magnus::{Error, Module, RModule, Ruby, TryConvert, Value, method, typed_data::Inspect};
2
2
 
3
3
  define_ruby_enum!(
4
4
  /// An HTTP version.
@@ -41,6 +41,20 @@ impl Version {
41
41
  pub fn to_s(&self) -> String {
42
42
  self.into_ffi().inspect()
43
43
  }
44
+
45
+ /// Value-based equality for Ruby (`==`).
46
+ #[inline]
47
+ pub fn equals(&self, other: Value) -> bool {
48
+ <&Version>::try_convert(other)
49
+ .map(|other| *self == *other)
50
+ .unwrap_or(false)
51
+ }
52
+ }
53
+
54
+ impl TryConvert for Version {
55
+ fn try_convert(value: magnus::Value) -> Result<Self, magnus::Error> {
56
+ <&Version>::try_convert(value).cloned()
57
+ }
44
58
  }
45
59
 
46
60
  // ===== impl StatusCode =====
@@ -113,6 +127,7 @@ pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
113
127
  version_class.const_set("HTTP_2", Version::HTTP_2)?;
114
128
  version_class.const_set("HTTP_3", Version::HTTP_3)?;
115
129
  version_class.define_method("to_s", method!(Version::to_s, 0))?;
130
+ version_class.define_method("==", method!(Version::equals, 1))?;
116
131
 
117
132
  let status_code_class = gem_module.define_class("StatusCode", ruby.class_object())?;
118
133
  status_code_class.define_method("as_int", method!(StatusCode::as_int, 0))?;
@@ -0,0 +1,115 @@
1
+ require "test_helper"
2
+
3
+ class OrigHeaderTest < Minitest::Test
4
+ URL = "https://tls.browserleaks.com/http1"
5
+
6
+ CASES = [
7
+ {
8
+ name: "mixed_case_descending",
9
+ headers: {
10
+ "X-Zeta-Token" => "zeta",
11
+ "x-alpha-key" => "alpha",
12
+ "X-MiXeD-CaSe" => "mixed"
13
+ },
14
+ orig_headers: ["X-Zeta-Token", "x-alpha-key", "X-MiXeD-CaSe"]
15
+ },
16
+ {
17
+ name: "reverse_alpha_order",
18
+ headers: {
19
+ "X-Third" => "3",
20
+ "X-Second" => "2",
21
+ "X-First" => "1"
22
+ },
23
+ orig_headers: ["X-Third", "X-Second", "X-First"]
24
+ },
25
+ {
26
+ name: "preserve_weird_casing",
27
+ headers: {
28
+ "x-a" => "a",
29
+ "X-B" => "b",
30
+ "x-C" => "c"
31
+ },
32
+ orig_headers: ["x-C", "x-a", "X-B"]
33
+ },
34
+ {
35
+ name: "interleaved_tokens",
36
+ headers: {
37
+ "X-Token-3" => "v3",
38
+ "X-Token-1" => "v1",
39
+ "X-Token-2" => "v2"
40
+ },
41
+ orig_headers: ["X-Token-1", "X-Token-2", "X-Token-3"]
42
+ }
43
+ ].freeze
44
+
45
+ def test_client_default_orig_headers_preserves_header_order_in_multiple_shuffled_cases
46
+ CASES.each do |kase|
47
+ client = Wreq::Client.new(
48
+ headers: kase[:headers],
49
+ orig_headers: kase[:orig_headers]
50
+ )
51
+
52
+ response = client.get(URL, version: Wreq::Version::HTTP_11)
53
+ assert_equal 200, response.code, "case=#{kase[:name]}"
54
+
55
+ echoed_headers = extract_http1_headers(response.json, kase[:name])
56
+ assert_header_order(echoed_headers, kase[:orig_headers], kase[:name])
57
+ assert_header_values(echoed_headers, kase[:headers], kase[:name])
58
+ end
59
+ end
60
+
61
+ def test_module_request_orig_headers_preserves_header_order_in_multiple_shuffled_cases
62
+ CASES.each do |kase|
63
+ response = Wreq.get(
64
+ URL,
65
+ headers: kase[:headers],
66
+ orig_headers: kase[:orig_headers],
67
+ version: Wreq::Version::HTTP_11
68
+ )
69
+ assert_equal 200, response.code, "case=#{kase[:name]}"
70
+
71
+ echoed_headers = extract_http1_headers(response.json, kase[:name])
72
+ assert_header_order(echoed_headers, kase[:orig_headers], kase[:name])
73
+ assert_header_values(echoed_headers, kase[:headers], kase[:name])
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def extract_http1_headers(json, case_name)
80
+ http1 = fetch_by_name(json, "http1")
81
+ refute_nil http1, "case=#{case_name}: expected JSON key 'http1', got #{json.keys.inspect}"
82
+
83
+ headers = fetch_by_name(http1, "headers")
84
+ refute_nil headers, "case=#{case_name}: expected JSON key 'http1.headers'"
85
+ headers
86
+ end
87
+
88
+ def fetch_by_name(hash_like, key_name)
89
+ return hash_like[key_name] if hash_like.respond_to?(:key?) && hash_like.key?(key_name)
90
+ return hash_like[key_name.to_sym] if hash_like.respond_to?(:key?) && hash_like.key?(key_name.to_sym)
91
+
92
+ pair = hash_like.find { |k, _| k.to_s == key_name }
93
+ pair&.last
94
+ end
95
+
96
+ def assert_header_order(echoed_headers, ordered_names, case_name)
97
+ echoed_keys = echoed_headers.keys
98
+ positions = ordered_names.map do |expected_name|
99
+ index = echoed_keys.index(expected_name)
100
+ refute_nil index, "case=#{case_name}: expected header to exist in echo: #{expected_name}"
101
+ index
102
+ end
103
+
104
+ assert_equal positions.sort, positions,
105
+ "case=#{case_name}: expected header order #{ordered_names.inspect}, got keys #{echoed_keys.inspect}"
106
+ end
107
+
108
+ def assert_header_values(echoed_headers, expected_headers, case_name)
109
+ expected_headers.each do |name, expected_value|
110
+ assert echoed_headers.key?(name),
111
+ "case=#{case_name}: expected exact-case header name #{name}, got #{echoed_headers.keys.inspect}"
112
+ assert_equal expected_value, echoed_headers[name]
113
+ end
114
+ end
115
+ end
data/test/request_test.rb CHANGED
@@ -6,6 +6,16 @@ class WreqHttpbinTest < Minitest::Test
6
6
  @client = Wreq::Client.new(timeout: 30)
7
7
  end
8
8
 
9
+ def test_spec_http1_version
10
+ response = Wreq.get("https://tls.browserleaks.com", version: Wreq::Version::HTTP_11)
11
+ assert_equal response.version, Wreq::Version::HTTP_11
12
+ end
13
+
14
+ def test_spec_http2_version
15
+ response = Wreq.get("https://tls.browserleaks.com", version: Wreq::Version::HTTP_2)
16
+ assert_equal response.version, Wreq::Version::HTTP_2
17
+ end
18
+
9
19
  def test_module_get_method
10
20
  response = Wreq.get("http://localhost:8080/get")
11
21
  assert_equal 200, response.code
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wreq
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - SearchApi
@@ -74,6 +74,7 @@ files:
74
74
  - test/header_test.rb
75
75
  - test/inspect_test.rb
76
76
  - test/module_methods_test.rb
77
+ - test/orig_header_test.rb
77
78
  - test/request_parameters_test.rb
78
79
  - test/request_test.rb
79
80
  - test/response_test.rb