wreq 1.1.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.
@@ -88,26 +88,16 @@ unless defined?(Wreq)
88
88
  def bytes
89
89
  end
90
90
 
91
- # Get the response body as text.
92
- #
93
- # @return [String] Response body decoded as UTF-8 text
94
- # @example
95
- # html = response.text
96
- # puts html
97
- # @raise [Wreq::DecodingError] if body cannot be decoded as binary
98
- def text
99
- end
100
-
101
91
  # Get the response body as text with a specific charset.
102
92
  # This method allows you to specify a default encoding
103
93
  # to use when decoding the response body.
104
94
  # # @param default_encoding [String] Default encoding to use (e.g., "UTF-8")
105
95
  # # @return [String] Response body decoded as text using the specified encoding
106
96
  # @example
107
- # html = response.text_with_charset("ISO-8859-1")
97
+ # html = response.text("ISO-8859-1")
108
98
  # puts html
109
99
  # @raise [Wreq::DecodingError] if body cannot be decoded with the specified encoding
110
- def text_with_charset(default_encoding)
100
+ def text(default_encoding: "UTF-8")
111
101
  end
112
102
 
113
103
  # Parse the response body as JSON.
@@ -120,14 +110,17 @@ unless defined?(Wreq)
120
110
  def json
121
111
  end
122
112
 
123
- # Get a streaming iterator for the response body, yielding each chunk.
113
+ # Stream the response body, yielding each chunk to the given block.
124
114
  #
125
115
  # This method allows you to process large HTTP responses efficiently,
126
116
  # by yielding each chunk of the body as it arrives, without loading
127
117
  # the entire response into memory.
128
118
  #
129
- # @return An iterator over response body chunks (binary String)
119
+ # @return [nil]
130
120
  # @yield [chunk] Each chunk of the response body as a binary String
121
+ # @raise [LocalJumpError] if called without a block
122
+ # @raise [Wreq::TimeoutError, Wreq::BodyError, Wreq::ConnectionResetError, Wreq::RequestError]
123
+ # if streaming fails while reading the response body
131
124
  # @example Save response to file
132
125
  # File.open("output.bin", "wb") do |f|
133
126
  # response.chunks { |chunk| f.write(chunk) }
@@ -137,7 +130,7 @@ unless defined?(Wreq)
137
130
  # response.chunks { |chunk| total += chunk.bytesize }
138
131
  # puts "Downloaded #{total} bytes"
139
132
  #
140
- # Note: The returned Receiver is only for reading response bodies, not for uploads.
133
+ # Exceptions raised inside the block are propagated to the caller.
141
134
  def chunks
142
135
  end
143
136
 
@@ -156,27 +149,35 @@ end
156
149
 
157
150
  module Wreq
158
151
  class Response
159
- # Returns a compact string representation of the response.
152
+ # Returns the response body as a string.
153
+ #
154
+ # @return [String] Response body text
155
+ # @example
156
+ # puts response.to_s
157
+ # puts response
158
+ # File.write("page.html", response)
159
+ def to_s
160
+ text
161
+ end
162
+
163
+ # Returns a compact string representation for debugging.
160
164
  #
161
165
  # Format: #<Wreq::Response STATUS content-type="..." body=SIZE>
162
166
  #
163
167
  # @return [String] Compact formatted response information
164
168
  # @example
165
- # puts response.to_s
169
+ # p response
166
170
  # # => #<Wreq::Response 200 content-type="application/json" body=456B>
167
- def to_s
171
+ def inspect
168
172
  parts = ["#<Wreq::Response"]
169
173
 
170
- # Status code
171
174
  parts << code.to_s
172
175
 
173
- # Content-Type header if present
174
176
  if headers.respond_to?(:get)
175
177
  content_type = headers.get("content-type")
176
178
  parts << "content-type=#{content_type.inspect}" if content_type
177
179
  end
178
180
 
179
- # Body size
180
181
  if content_length
181
182
  parts << "body=#{format_bytes(content_length)}"
182
183
  end
@@ -13,7 +13,7 @@ use tokio::sync::{
13
13
  };
14
14
 
15
15
  use crate::{
16
- error::{memory_error, mpsc_send_error_to_magnus},
16
+ error::{memory_error, mpsc_send_error_to_magnus, wreq_error_to_magnus},
17
17
  rt,
18
18
  };
19
19
 
@@ -37,20 +37,15 @@ impl BodyReceiver {
37
37
  pub fn new(stream: impl Stream<Item = wreq::Result<Bytes>> + Send + 'static) -> BodyReceiver {
38
38
  BodyReceiver(Mutex::new(Box::pin(stream)))
39
39
  }
40
- }
41
40
 
42
- impl Iterator for BodyReceiver {
43
- type Item = Bytes;
44
-
45
- fn next(&mut self) -> Option<Self::Item> {
46
- rt::maybe_block_on(async {
47
- self.0
48
- .lock()
49
- .await
50
- .as_mut()
51
- .next()
52
- .await
53
- .and_then(|r| r.ok())
41
+ /// Read the next body chunk, converting stream errors into Ruby errors.
42
+ pub fn next(&self) -> Result<Option<Bytes>, Error> {
43
+ rt::try_block_on(async {
44
+ match self.0.lock().await.as_mut().next().await {
45
+ Some(Ok(data)) => Ok(Some(data)),
46
+ Some(Err(err)) => Err(wreq_error_to_magnus(err)),
47
+ None => Ok(None),
48
+ }
54
49
  })
55
50
  }
56
51
  }
data/src/client/req.rs CHANGED
@@ -1,20 +1,19 @@
1
1
  use std::{net::IpAddr, time::Duration};
2
2
 
3
- use http::{HeaderValue, header};
3
+ use http::header;
4
4
  use magnus::{RHash, TryConvert, typed_data::Obj, value::ReprValue};
5
5
  use serde::Deserialize;
6
- use wreq::{
7
- Client, Proxy, Version,
8
- header::{HeaderMap, OrigHeaderMap},
9
- };
6
+ use wreq::{Client, Proxy};
10
7
 
11
8
  use super::body::{Body, Form, Json};
12
9
  use crate::{
13
10
  client::{query::Query, resp::Response},
11
+ cookie::Cookies,
14
12
  emulate::Emulation,
15
13
  error::wreq_error_to_magnus,
16
14
  extractor::Extractor,
17
- http::Method,
15
+ header::{Headers, OrigHeaders},
16
+ http::{Method, Version},
18
17
  rt,
19
18
  };
20
19
 
@@ -46,20 +45,20 @@ pub struct Request {
46
45
  #[serde(skip)]
47
46
  version: Option<Version>,
48
47
 
48
+ /// The option enables default headers.
49
+ default_headers: Option<bool>,
50
+
49
51
  /// The headers to use for the request.
50
52
  #[serde(skip)]
51
- headers: Option<HeaderMap>,
53
+ headers: Option<Headers>,
52
54
 
53
55
  /// The original headers to use for the request.
54
56
  #[serde(skip)]
55
- orig_headers: Option<OrigHeaderMap>,
56
-
57
- /// The option enables default headers.
58
- default_headers: Option<bool>,
57
+ orig_headers: Option<OrigHeaders>,
59
58
 
60
59
  /// The cookies to use for the request.
61
60
  #[serde(skip)]
62
- cookies: Option<Vec<HeaderValue>>,
61
+ cookies: Option<Cookies>,
63
62
 
64
63
  /// Whether to allow redirects.
65
64
  allow_redirects: Option<bool>,
@@ -105,35 +104,36 @@ pub struct Request {
105
104
  impl Request {
106
105
  /// Create a new [`Request`] from Ruby keyword arguments.
107
106
  pub fn new(ruby: &magnus::Ruby, hash: RHash) -> Result<Self, magnus::Error> {
108
- let kwargs = hash.as_value();
109
- let mut builder: Self = serde_magnus::deserialize(ruby, kwargs)?;
107
+ let keyword = hash.as_value();
108
+ let mut builder: Self = serde_magnus::deserialize(ruby, keyword)?;
110
109
 
111
- // extra emulation handling
112
- if let Some(v) = hash.get(ruby.to_symbol("emulation")) {
113
- let emulation_obj = Obj::<Emulation>::try_convert(v)?;
114
- builder.emulation = Some((*emulation_obj).clone());
110
+ if let Some(v) = hash.get(ruby.to_symbol(stringify!(emulation))) {
111
+ let obj = Obj::<Emulation>::try_convert(v)?;
112
+ builder.emulation = Some((*obj).clone());
115
113
  }
116
114
 
117
- // extra version handling
118
- builder.version = Extractor::<Version>::try_convert(kwargs)?.into_inner();
119
-
120
- // extra headers handling
121
- builder.headers = Extractor::<HeaderMap>::try_convert(kwargs)?.into_inner();
115
+ if let Some(v) = hash.get(ruby.to_symbol(stringify!(version))) {
116
+ builder.version = Some(Version::try_convert(v)?);
117
+ }
122
118
 
123
- // extra original headers handling
124
- builder.orig_headers = Extractor::<OrigHeaderMap>::try_convert(kwargs)?.into_inner();
119
+ if let Some(v) = hash.get(ruby.to_symbol(stringify!(headers))) {
120
+ builder.headers = Some(Headers::try_convert(v)?);
121
+ }
125
122
 
126
- // extra cookies handling
127
- builder.cookies = Extractor::<Vec<HeaderValue>>::try_convert(kwargs)?.into_inner();
123
+ if let Some(v) = hash.get(ruby.to_symbol(stringify!(orig_headers))) {
124
+ builder.orig_headers = Some(OrigHeaders::try_convert(v)?);
125
+ }
128
126
 
129
- // extra proxy handling
130
- builder.proxy = Extractor::<Proxy>::try_convert(kwargs)?.into_inner();
127
+ if let Some(v) = hash.get(ruby.to_symbol(stringify!(cookies))) {
128
+ builder.cookies = Some(Cookies::try_convert(v)?);
129
+ }
131
130
 
132
- // extra body handling
133
- if let Some(body) = hash.get(ruby.to_symbol("body")) {
134
- builder.body = Some(Body::try_convert(body)?);
131
+ if let Some(v) = hash.get(ruby.to_symbol(stringify!(body))) {
132
+ builder.body = Some(Body::try_convert(v)?);
135
133
  }
136
134
 
135
+ builder.proxy = Extractor::<Proxy>::try_convert(keyword)?.into_inner();
136
+
137
137
  Ok(builder)
138
138
  }
139
139
  }
@@ -151,7 +151,13 @@ pub fn execute_request<U: AsRef<str>>(
151
151
  apply_option!(set_if_some_inner, builder, request.emulation, emulation);
152
152
 
153
153
  // Version options.
154
- 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
+ );
155
161
 
156
162
  // Timeout options.
157
163
  apply_option!(
@@ -175,8 +181,13 @@ pub fn execute_request<U: AsRef<str>>(
175
181
  apply_option!(set_if_some, builder, request.interface, interface);
176
182
 
177
183
  // Headers options.
178
- apply_option!(set_if_some, builder, request.headers, headers);
179
- apply_option!(set_if_some, builder, request.orig_headers, orig_headers);
184
+ apply_option!(set_if_some_into_inner, builder, request.headers, headers);
185
+ apply_option!(
186
+ set_if_some_inner,
187
+ builder,
188
+ request.orig_headers,
189
+ orig_headers
190
+ );
180
191
  apply_option!(
181
192
  set_if_some,
182
193
  builder,
@@ -184,6 +195,13 @@ pub fn execute_request<U: AsRef<str>>(
184
195
  default_headers
185
196
  );
186
197
 
198
+ // Cookies options.
199
+ if let Some(cookies) = request.cookies.take() {
200
+ for cookie in cookies.0 {
201
+ builder = builder.header(header::COOKIE, cookie);
202
+ }
203
+ }
204
+
187
205
  // Authentication options.
188
206
  apply_option!(
189
207
  set_if_some_map_ref,
@@ -197,13 +215,6 @@ pub fn execute_request<U: AsRef<str>>(
197
215
  builder = builder.basic_auth(basic_auth.0, basic_auth.1);
198
216
  }
199
217
 
200
- // Cookies options.
201
- if let Some(cookies) = request.cookies.take() {
202
- for cookie in cookies {
203
- builder = builder.header(header::COOKIE, cookie);
204
- }
205
- }
206
-
207
218
  // Allow redirects options.
208
219
  match request.allow_redirects {
209
220
  Some(false) => {
data/src/client/resp.rs CHANGED
@@ -5,14 +5,14 @@ use bytes::Bytes;
5
5
  use futures_util::TryFutureExt;
6
6
  use http::{Extensions, HeaderMap, response::Response as HttpResponse};
7
7
  use http_body_util::BodyExt;
8
- use magnus::{Error, Module, RArray, RModule, Ruby, Value, block::Yield};
8
+ use magnus::{Error, Module, RArray, RModule, Ruby, Value, scan_args::scan_args};
9
9
  use wreq::Uri;
10
10
 
11
11
  use crate::{
12
12
  client::body::{BodyReceiver, Json},
13
13
  cookie::Cookie,
14
- error::{memory_error, wreq_error_to_magnus},
15
- gvl,
14
+ error::{memory_error, no_block_given_error, wreq_error_to_magnus},
15
+ gvl::{self, nogvl},
16
16
  header::Headers,
17
17
  http::{StatusCode, Version},
18
18
  rt,
@@ -173,20 +173,18 @@ impl Response {
173
173
  rt::try_block_on(response.bytes().map_err(wreq_error_to_magnus))
174
174
  }
175
175
 
176
- /// Get the response body as a UTF-8 string.
177
- pub fn text(&self) -> Result<String, Error> {
178
- let response = self.response(false)?;
179
- rt::try_block_on(response.text().map_err(wreq_error_to_magnus))
180
- }
181
-
182
176
  /// Get the full response text given a specific encoding.
183
- pub fn text_with_charset(&self, default_encoding: String) -> Result<String, Error> {
177
+ pub fn text(&self, args: &[Value]) -> Result<String, Error> {
178
+ let args = scan_args::<(), (Option<String>,), (), (), (), ()>(args)?;
184
179
  let response = self.response(false)?;
185
- rt::try_block_on(
186
- response
187
- .text_with_charset(default_encoding)
188
- .map_err(wreq_error_to_magnus),
189
- )
180
+ match args.optional.0 {
181
+ Some(encoding) => rt::try_block_on(
182
+ response
183
+ .text_with_charset(encoding)
184
+ .map_err(wreq_error_to_magnus),
185
+ ),
186
+ None => rt::try_block_on(response.text().map_err(wreq_error_to_magnus)),
187
+ }
190
188
  }
191
189
 
192
190
  /// Get the response body as JSON.
@@ -201,12 +199,24 @@ impl Response {
201
199
  })
202
200
  }
203
201
 
204
- /// Get a chunk iterator for the response body.
205
- pub fn chunks(&self) -> Result<Yield<BodyReceiver>, Error> {
206
- self.response(true)
207
- .map(wreq::Response::bytes_stream)
208
- .map(BodyReceiver::new)
209
- .map(Yield::Iter)
202
+ /// Yield response body chunks to the given Ruby block.
203
+ pub fn chunks(ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
204
+ if !ruby.block_given() {
205
+ return Err(no_block_given_error());
206
+ }
207
+
208
+ let receiver = nogvl(|| {
209
+ rb_self
210
+ .response(true)
211
+ .map(wreq::Response::bytes_stream)
212
+ .map(BodyReceiver::new)
213
+ })?;
214
+
215
+ while let Some(chunk) = receiver.next()? {
216
+ let _: Value = ruby.yield_value(chunk)?;
217
+ }
218
+
219
+ Ok(())
210
220
  }
211
221
 
212
222
  /// Close the response body, dropping any resources.
@@ -224,27 +234,23 @@ impl Drop for Response {
224
234
  }
225
235
 
226
236
  pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
227
- let response_class = gem_module.define_class("Response", ruby.class_object())?;
228
- response_class.define_method("code", magnus::method!(Response::code, 0))?;
229
- response_class.define_method("status", magnus::method!(Response::status, 0))?;
230
- response_class.define_method("version", magnus::method!(Response::version, 0))?;
231
- response_class.define_method("url", magnus::method!(Response::url, 0))?;
232
- response_class.define_method(
237
+ let response = gem_module.define_class("Response", ruby.class_object())?;
238
+ response.define_method("code", magnus::method!(Response::code, 0))?;
239
+ response.define_method("status", magnus::method!(Response::status, 0))?;
240
+ response.define_method("version", magnus::method!(Response::version, 0))?;
241
+ response.define_method("url", magnus::method!(Response::url, 0))?;
242
+ response.define_method(
233
243
  "content_length",
234
244
  magnus::method!(Response::content_length, 0),
235
245
  )?;
236
- response_class.define_method("cookies", magnus::method!(Response::cookies, 0))?;
237
- response_class.define_method("headers", magnus::method!(Response::headers, 0))?;
238
- response_class.define_method("local_addr", magnus::method!(Response::local_addr, 0))?;
239
- response_class.define_method("remote_addr", magnus::method!(Response::remote_addr, 0))?;
240
- response_class.define_method("bytes", magnus::method!(Response::bytes, 0))?;
241
- response_class.define_method("text", magnus::method!(Response::text, 0))?;
242
- response_class.define_method(
243
- "text_with_charset",
244
- magnus::method!(Response::text_with_charset, 1),
245
- )?;
246
- response_class.define_method("json", magnus::method!(Response::json, 0))?;
247
- response_class.define_method("chunks", magnus::method!(Response::chunks, 0))?;
248
- response_class.define_method("close", magnus::method!(Response::close, 0))?;
246
+ response.define_method("cookies", magnus::method!(Response::cookies, 0))?;
247
+ response.define_method("headers", magnus::method!(Response::headers, 0))?;
248
+ response.define_method("local_addr", magnus::method!(Response::local_addr, 0))?;
249
+ response.define_method("remote_addr", magnus::method!(Response::remote_addr, 0))?;
250
+ response.define_method("bytes", magnus::method!(Response::bytes, 0))?;
251
+ response.define_method("text", magnus::method!(Response::text, -1))?;
252
+ response.define_method("json", magnus::method!(Response::json, 0))?;
253
+ response.define_method("chunks", magnus::method!(Response::chunks, 0))?;
254
+ response.define_method("close", magnus::method!(Response::close, 0))?;
249
255
  Ok(())
250
256
  }
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::{HeaderMap, HeaderValue, OrigHeaderMap},
16
- };
13
+ use wreq::{Proxy, header::HeaderValue};
17
14
 
18
15
  use crate::{
19
16
  client::{req::execute_request, resp::Response},
@@ -22,6 +19,7 @@ use crate::{
22
19
  error::wreq_error_to_magnus,
23
20
  extractor::Extractor,
24
21
  gvl,
22
+ header::{Headers, OrigHeaders},
25
23
  http::Method,
26
24
  };
27
25
 
@@ -36,10 +34,10 @@ struct Builder {
36
34
  user_agent: Option<HeaderValue>,
37
35
  /// The headers to use for the client.
38
36
  #[serde(skip)]
39
- headers: Option<HeaderMap>,
37
+ headers: Option<Headers>,
40
38
  /// The original headers to use for the client.
41
39
  #[serde(skip)]
42
- orig_headers: Option<OrigHeaderMap>,
40
+ orig_headers: Option<OrigHeaders>,
43
41
  /// Whether to use referer.
44
42
  referer: Option<bool>,
45
43
  /// Whether to allow redirects.
@@ -83,7 +81,7 @@ struct Builder {
83
81
  /// Sets the maximum idle connection per host allowed in the pool.
84
82
  pool_max_idle_per_host: Option<usize>,
85
83
  /// Sets the maximum number of connections in the pool.
86
- pool_max_size: Option<u32>,
84
+ pool_max_size: Option<usize>,
87
85
 
88
86
  // ========= Protocol options =========
89
87
  /// Whether to use the HTTP/1 protocol only.
@@ -128,35 +126,36 @@ pub struct Client(wreq::Client);
128
126
  impl Builder {
129
127
  /// Create a new [`Builder`] from Ruby keyword arguments.
130
128
  fn new(ruby: &magnus::Ruby, keyword: &Value) -> Result<Self, magnus::Error> {
131
- if let Ok(hash) = RHash::try_convert(*keyword) {
132
- let mut builder: Self = serde_magnus::deserialize(ruby, hash)?;
133
- // extra emulation handling
134
- if let Some(v) = hash.get(ruby.to_symbol("emulation")) {
135
- let emulation_obj = Obj::<Emulation>::try_convert(v)?;
136
- builder.emulation = Some((*emulation_obj).clone());
137
- }
129
+ let Ok(hash) = RHash::try_convert(*keyword) else {
130
+ return Ok(Default::default());
131
+ };
138
132
 
139
- // extra user agent handling
140
- builder.user_agent = Extractor::<HeaderValue>::try_convert(*keyword)?.into_inner();
133
+ let mut builder: Self = serde_magnus::deserialize(ruby, hash)?;
141
134
 
142
- // extra headers handling
143
- builder.headers = Extractor::<HeaderMap>::try_convert(*keyword)?.into_inner();
135
+ if let Some(v) = hash.get(ruby.to_symbol(stringify!(emulation))) {
136
+ builder.emulation = Some((*Obj::<Emulation>::try_convert(v)?).clone());
137
+ }
144
138
 
145
- // extra original headers handling
146
- builder.orig_headers = Extractor::<OrigHeaderMap>::try_convert(*keyword)?.into_inner();
139
+ if let Some(v) = hash.get(ruby.to_symbol(stringify!(headers))) {
140
+ builder.headers = Some(Headers::try_convert(v)?);
141
+ }
147
142
 
148
- // extra proxy handling
149
- builder.proxy = Extractor::<Proxy>::try_convert(*keyword)?.into_inner();
143
+ if let Some(v) = hash.get(ruby.to_symbol(stringify!(orig_headers))) {
144
+ builder.orig_headers = Some(OrigHeaders::try_convert(v)?);
145
+ }
150
146
 
151
- // extra cookie store handling
152
- if let Some(jar) = hash.get(ruby.to_symbol("cookie_provider")) {
153
- builder.cookie_provider = Some((*Obj::<Jar>::try_convert(jar)?).clone());
154
- }
147
+ if let Some(v) = hash.get(ruby.to_symbol(stringify!(cookie_provider))) {
148
+ builder.cookie_provider = Some((*Obj::<Jar>::try_convert(v)?).clone());
149
+ }
155
150
 
156
- return Ok(builder);
151
+ if let Some(jar) = hash.get(ruby.to_symbol(stringify!(cookie_provider))) {
152
+ builder.cookie_provider = Some((*Obj::<Jar>::try_convert(jar)?).clone());
157
153
  }
158
154
 
159
- Ok(Default::default())
155
+ builder.user_agent = Extractor::<HeaderValue>::try_convert(*keyword)?.into_inner();
156
+ builder.proxy = Extractor::<Proxy>::try_convert(*keyword)?.into_inner();
157
+
158
+ Ok(builder)
160
159
  }
161
160
  }
162
161
 
@@ -164,9 +163,9 @@ impl Builder {
164
163
 
165
164
  impl Client {
166
165
  /// Create a new [`Client`] with the given keyword arguments.
167
- pub fn new(ruby: &Ruby, kwargs: &[Value]) -> Result<Self, magnus::Error> {
168
- if let Some(kwargs) = kwargs.first() {
169
- let mut params = Builder::new(ruby, kwargs)?;
166
+ pub fn new(ruby: &Ruby, keyword: &[Value]) -> Result<Self, magnus::Error> {
167
+ if let Some(keyword) = keyword.first() {
168
+ let mut params = Builder::new(ruby, keyword)?;
170
169
  gvl::nogvl(|| {
171
170
  let mut builder = wreq::Client::builder();
172
171
 
@@ -176,9 +175,19 @@ impl Client {
176
175
  // User agent options.
177
176
  apply_option!(set_if_some, builder, params.user_agent, user_agent);
178
177
 
179
- // Default headers options.
180
- apply_option!(set_if_some, builder, params.headers, default_headers);
181
- apply_option!(set_if_some, builder, params.orig_headers, orig_headers);
178
+ // Headers options.
179
+ apply_option!(
180
+ set_if_some_into_inner,
181
+ builder,
182
+ params.headers,
183
+ default_headers
184
+ );
185
+ apply_option!(
186
+ set_if_some_inner,
187
+ builder,
188
+ params.orig_headers,
189
+ orig_headers
190
+ );
182
191
 
183
192
  // Allow redirects options.
184
193
  apply_option!(set_if_some, builder, params.referer, referer);
@@ -286,7 +295,7 @@ impl Client {
286
295
  apply_option!(set_if_some, builder, params.https_only, https_only);
287
296
 
288
297
  // TLS options.
289
- apply_option!(set_if_some, builder, params.verify, cert_verification);
298
+ apply_option!(set_if_some, builder, params.verify, tls_cert_verification);
290
299
 
291
300
  // Network options.
292
301
  apply_option!(set_if_some, builder, params.proxy, proxy);
data/src/cookie.rs CHANGED
@@ -1,13 +1,14 @@
1
1
  use std::{fmt, sync::Arc, time::SystemTime};
2
2
 
3
+ use bytes::Bytes;
3
4
  use cookie::{Cookie as RawCookie, Expiration, ParseError, time::Duration};
4
5
  use magnus::{
5
- Error, Module, Object, RModule, Ruby, Value, function, method, typed_data::Obj,
6
- value::ReprValue,
6
+ Error, Module, Object, RHash, RModule, RString, Ruby, TryConvert, Value, function, method,
7
+ r_hash::ForEach, typed_data::Obj, value::ReprValue,
7
8
  };
8
9
  use wreq::header::{self, HeaderMap, HeaderValue};
9
10
 
10
- use crate::gvl;
11
+ use crate::{error::header_value_error_to_magnus, gvl};
11
12
 
12
13
  define_ruby_enum!(
13
14
  /// The Cookie SameSite attribute.
@@ -25,6 +26,10 @@ define_ruby_enum!(
25
26
  #[magnus::wrap(class = "Wreq::Cookie", free_immediately, size)]
26
27
  pub struct Cookie(RawCookie<'static>);
27
28
 
29
+ /// A collection of HTTP cookies.
30
+ #[derive(Default)]
31
+ pub struct Cookies(pub Vec<HeaderValue>);
32
+
28
33
  /// A good default `CookieStore` implementation.
29
34
  ///
30
35
  /// This is the implementation used when simply calling `cookie_store(true)`.
@@ -197,6 +202,36 @@ impl fmt::Display for Cookie {
197
202
  }
198
203
  }
199
204
 
205
+ // ===== impl Cookies =====
206
+
207
+ impl TryConvert for Cookies {
208
+ fn try_convert(value: magnus::Value) -> Result<Self, magnus::Error> {
209
+ // try extract uncompressed cookies
210
+ if let Some(rhash) = RHash::from_value(value) {
211
+ let mut cookies = Vec::new();
212
+ rhash.foreach(|name: RString, value: RString| {
213
+ let cookie = format!("{name}={value}");
214
+ let header_value = HeaderValue::from_maybe_shared(Bytes::from(cookie))
215
+ .map_err(header_value_error_to_magnus)?;
216
+ cookies.push(header_value);
217
+ Ok(ForEach::Continue)
218
+ })?;
219
+
220
+ return Ok(Self(cookies));
221
+ }
222
+
223
+ // try extract compressed cookies
224
+ if let Some(cookies) = RString::from_value(value) {
225
+ return Ok(Self(vec![
226
+ HeaderValue::from_maybe_shared(cookies.to_bytes())
227
+ .map_err(header_value_error_to_magnus)?,
228
+ ]));
229
+ }
230
+
231
+ Ok(Self::default())
232
+ }
233
+ }
234
+
200
235
  // ===== impl Jar =====
201
236
 
202
237
  impl Jar {
@@ -221,13 +256,14 @@ impl Jar {
221
256
  }
222
257
 
223
258
  /// Add a cookie to this jar.
224
- pub fn add_cookie(&self, cookie: &Cookie, url: String) {
225
- gvl::nogvl(|| self.0.add(cookie.0.clone(), &url))
226
- }
259
+ pub fn add(&self, cookie: Value, url: String) {
260
+ if let Ok(cookie) = Obj::<Cookie>::try_convert(cookie) {
261
+ gvl::nogvl(|| self.0.add(cookie.0.clone(), &url))
262
+ }
227
263
 
228
- /// Add a cookie str to this jar.
229
- pub fn add_cookie_str(&self, cookie: String, url: String) {
230
- gvl::nogvl(|| self.0.add(cookie.as_ref(), &url))
264
+ if let Ok(cookie_str) = String::try_convert(cookie) {
265
+ gvl::nogvl(|| self.0.add(cookie_str.as_ref(), &url))
266
+ }
231
267
  }
232
268
 
233
269
  /// Remove a cookie from this jar by name and URL.
@@ -268,8 +304,7 @@ pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
268
304
  let jar_class = gem_module.define_class("Jar", ruby.class_object())?;
269
305
  jar_class.define_singleton_method("new", function!(Jar::new, 0))?;
270
306
  jar_class.define_method("get_all", method!(Jar::get_all, 0))?;
271
- jar_class.define_method("add_cookie", method!(Jar::add_cookie, 2))?;
272
- jar_class.define_method("add_cookie_str", method!(Jar::add_cookie_str, 2))?;
307
+ jar_class.define_method("add", method!(Jar::add, 2))?;
273
308
  jar_class.define_method("remove", method!(Jar::remove, 2))?;
274
309
  jar_class.define_method("clear", method!(Jar::clear, 0))?;
275
310