wreq 1.2.5 → 1.2.7
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 +4 -4
- data/.cargo/config.toml +4 -0
- data/Cargo.lock +135 -119
- data/Cargo.toml +11 -3
- data/Rakefile +6 -0
- data/crates/wreq-util/README.md +1 -1
- data/crates/wreq-util/examples/emulate.rs +3 -3
- data/crates/wreq-util/src/emulate/macros.rs +13 -11
- data/crates/wreq-util/src/emulate/profile/chrome/header.rs +18 -3
- data/crates/wreq-util/src/emulate/profile/firefox/header.rs +16 -0
- data/crates/wreq-util/src/emulate/profile/opera/header.rs +6 -7
- data/crates/wreq-util/tests/client.rs +103 -1
- data/docs/windows-gnu-tokio-crash.md +49 -3
- data/extconf.rb +4 -0
- data/lib/wreq.rb +70 -45
- data/lib/wreq_ruby/body.rb +29 -11
- data/lib/wreq_ruby/client.rb +88 -55
- data/lib/wreq_ruby/cookie.rb +79 -19
- data/lib/wreq_ruby/emulate.rb +74 -6
- data/lib/wreq_ruby/error.rb +5 -7
- data/lib/wreq_ruby/http.rb +74 -0
- data/lib/wreq_ruby/response.rb +3 -0
- data/script/rust_env.rb +85 -0
- data/src/arch.rs +22 -0
- data/src/client/body/form.rs +2 -0
- data/src/client/body/json.rs +47 -14
- data/src/client/body/stream.rs +147 -43
- data/src/client/body.rs +11 -15
- data/src/client/param.rs +7 -7
- data/src/client/req.rs +87 -47
- data/src/client/resp.rs +23 -24
- data/src/client.rs +310 -230
- data/src/cookie.rs +280 -87
- data/src/emulate.rs +85 -50
- data/src/error.rs +198 -41
- data/src/extractor.rs +16 -76
- data/src/header.rs +146 -23
- data/src/http.rs +62 -33
- data/src/lib.rs +26 -20
- data/src/macros.rs +71 -46
- data/src/options.rs +284 -0
- data/src/rt.rs +22 -11
- data/src/serde/de/array_deserializer.rs +48 -0
- data/src/serde/de/array_enumerator.rs +59 -0
- data/src/serde/de/deserializer.rs +307 -0
- data/src/serde/de/enum_deserializer.rs +47 -0
- data/src/serde/de/hash_deserializer.rs +89 -0
- data/src/serde/de/number_deserializer.rs +51 -0
- data/src/serde/de/variant_deserializer.rs +101 -0
- data/src/serde/de.rs +33 -0
- data/src/serde/error.rs +146 -0
- data/src/serde/ser/enums.rs +14 -0
- data/src/serde/ser/map_serializer.rs +56 -0
- data/src/serde/ser/seq_serializer.rs +71 -0
- data/src/serde/ser/serializer.rs +194 -0
- data/src/serde/ser/struct_serializer.rs +106 -0
- data/src/serde/ser/struct_variant_serializer.rs +44 -0
- data/src/serde/ser/tuple_variant_serializer.rs +41 -0
- data/src/serde/ser.rs +18 -0
- data/src/serde/tests.rs +237 -0
- data/src/serde.rs +202 -0
- data/test/body_sender_test.rb +246 -0
- data/test/cookie_test.rb +211 -10
- data/test/json_precision_test.rb +209 -0
- data/test/option_validation_test.rb +311 -0
- data/test/value_semantics_test.rb +238 -0
- data/wreq.gemspec +1 -1
- metadata +28 -4
- data/script/build_windows_gnu.ps1 +0 -264
- data/src/header/helper.rs +0 -112
data/src/client/body/stream.rs
CHANGED
|
@@ -1,34 +1,58 @@
|
|
|
1
|
+
//! Streaming request and response body support.
|
|
2
|
+
|
|
1
3
|
use std::{
|
|
4
|
+
cell::{Ref, RefCell, RefMut},
|
|
5
|
+
panic::catch_unwind,
|
|
2
6
|
pin::Pin,
|
|
3
|
-
sync::RwLock,
|
|
4
7
|
task::{Context, Poll},
|
|
5
8
|
};
|
|
6
9
|
|
|
7
10
|
use bytes::Bytes;
|
|
8
11
|
use futures_util::{Stream, StreamExt};
|
|
9
|
-
use magnus::{Error, RString,
|
|
10
|
-
use tokio::sync::{
|
|
11
|
-
Mutex,
|
|
12
|
-
mpsc::{self},
|
|
13
|
-
};
|
|
12
|
+
use magnus::{Error, Integer, RString, Ruby, Value, scan_args::scan_args};
|
|
13
|
+
use tokio::sync::{Mutex, Semaphore, mpsc};
|
|
14
14
|
|
|
15
15
|
use crate::{
|
|
16
|
-
error::{
|
|
16
|
+
error::{
|
|
17
|
+
argument_error, body_sender_borrow_error, body_sender_borrow_mut_error,
|
|
18
|
+
body_sender_send_error, closed_body_sender_error, memory_error, type_error, wreq_error,
|
|
19
|
+
},
|
|
17
20
|
rt,
|
|
18
21
|
};
|
|
19
22
|
|
|
23
|
+
/// Number of chunks buffered when Ruby omits the channel capacity.
|
|
24
|
+
const DEFAULT_CHANNEL_CAPACITY: usize = 8;
|
|
25
|
+
|
|
20
26
|
/// A receiver for streaming HTTP response bodies.
|
|
21
27
|
pub struct BodyReceiver(Mutex<Pin<Box<dyn Stream<Item = wreq::Result<Bytes>> + Send>>>);
|
|
22
28
|
|
|
23
|
-
/// A
|
|
29
|
+
/// A bounded producer for a single streaming HTTP request body.
|
|
30
|
+
///
|
|
31
|
+
/// The receiving side may be attached to one request. The producer remains
|
|
32
|
+
/// writable until [`BodySender::close`] is called or the request drops its
|
|
33
|
+
/// receiver. Ruby's GVL protects state access; no [`RefCell`] borrow is kept
|
|
34
|
+
/// while request backpressure waits without the GVL.
|
|
24
35
|
#[magnus::wrap(class = "Wreq::BodySender", free_immediately, size)]
|
|
25
|
-
pub struct BodySender(
|
|
36
|
+
pub struct BodySender(RefCell<InnerBodySender>);
|
|
26
37
|
|
|
38
|
+
/// Mutable ownership state for both halves of the body channel.
|
|
27
39
|
struct InnerBodySender {
|
|
40
|
+
/// Producing side, removed by [`BodySender::close`].
|
|
28
41
|
tx: Option<mpsc::Sender<Bytes>>,
|
|
42
|
+
/// Receiving side, removed when the sender is attached to a request.
|
|
29
43
|
rx: Option<mpsc::Receiver<Bytes>>,
|
|
30
44
|
}
|
|
31
45
|
|
|
46
|
+
impl InnerBodySender {
|
|
47
|
+
/// Return whether the channel can no longer accept body chunks.
|
|
48
|
+
fn is_closed(&self) -> bool {
|
|
49
|
+
match &self.tx {
|
|
50
|
+
Some(tx) => tx.is_closed(),
|
|
51
|
+
None => true,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
32
56
|
// ===== impl BodyReceiver =====
|
|
33
57
|
|
|
34
58
|
impl BodyReceiver {
|
|
@@ -39,8 +63,9 @@ impl BodyReceiver {
|
|
|
39
63
|
}
|
|
40
64
|
|
|
41
65
|
/// Read the next body chunk, converting stream errors into Ruby errors.
|
|
42
|
-
pub fn next(&self) -> Result<Option<Bytes>, Error> {
|
|
66
|
+
pub fn next(&self, ruby: &Ruby) -> Result<Option<Bytes>, Error> {
|
|
43
67
|
rt::try_block_on(
|
|
68
|
+
ruby,
|
|
44
69
|
async {
|
|
45
70
|
match self.0.lock().await.as_mut().next().await {
|
|
46
71
|
Some(Ok(data)) => Ok(Some(data)),
|
|
@@ -48,7 +73,7 @@ impl BodyReceiver {
|
|
|
48
73
|
None => Ok(None),
|
|
49
74
|
}
|
|
50
75
|
},
|
|
51
|
-
|
|
76
|
+
wreq_error,
|
|
52
77
|
)
|
|
53
78
|
}
|
|
54
79
|
}
|
|
@@ -56,54 +81,133 @@ impl BodyReceiver {
|
|
|
56
81
|
// ===== impl BodySender =====
|
|
57
82
|
|
|
58
83
|
impl BodySender {
|
|
59
|
-
///
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
84
|
+
/// Create a bounded request-body channel.
|
|
85
|
+
///
|
|
86
|
+
/// Ruby: `Wreq::BodySender.new(capacity = 8)`. Capacity must be greater
|
|
87
|
+
/// than zero and no larger than [`Semaphore::MAX_PERMITS`].
|
|
88
|
+
///
|
|
89
|
+
/// # Errors
|
|
90
|
+
///
|
|
91
|
+
/// Returns `TypeError` for a non-Integer capacity and `ArgumentError` for
|
|
92
|
+
/// an invalid range or argument count.
|
|
93
|
+
pub fn new(ruby: &Ruby, args: &[Value]) -> Result<Self, Error> {
|
|
94
|
+
let capacity = parse_capacity(ruby, args)?;
|
|
95
|
+
|
|
96
|
+
// Create the Tokio channel without allowing an unwind to cross the Ruby FFI boundary.
|
|
97
|
+
//
|
|
98
|
+
// Known panic conditions are rejected by [`parse_capacity`]. The unwind guard
|
|
99
|
+
// remains as a defensive fallback if Tokio adds another channel invariant.
|
|
100
|
+
let (tx, rx) =
|
|
101
|
+
catch_unwind(|| mpsc::channel(capacity)).map_err(|_| invalid_capacity_error(ruby))?;
|
|
102
|
+
|
|
103
|
+
Ok(BodySender(RefCell::new(InnerBodySender {
|
|
69
104
|
tx: Some(tx),
|
|
70
105
|
rx: Some(rx),
|
|
71
|
-
}))
|
|
106
|
+
})))
|
|
72
107
|
}
|
|
73
108
|
|
|
74
|
-
///
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
109
|
+
/// Push a binary chunk, waiting for capacity when the channel is full.
|
|
110
|
+
///
|
|
111
|
+
/// Ruby: `push(data)` where `data` is a String.
|
|
112
|
+
///
|
|
113
|
+
/// # Errors
|
|
114
|
+
///
|
|
115
|
+
/// Returns `IOError` after either channel side has closed. An interrupted
|
|
116
|
+
/// wait raises Ruby's standard `Interrupt` exception.
|
|
117
|
+
pub fn push(ruby: &Ruby, rb_self: &Self, data: RString) -> Result<(), Error> {
|
|
118
|
+
// Clone during the shared borrow, then release it before waiting
|
|
119
|
+
// for capacity. Request attachment needs a mutable borrow.
|
|
120
|
+
let tx = match &rb_self.read_inner(ruby)?.tx {
|
|
121
|
+
Some(tx) if !tx.is_closed() => tx.clone(),
|
|
122
|
+
_ => return Err(closed_body_sender_error(ruby)),
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
rt::try_block_on(ruby, tx.send(data.to_bytes()), body_sender_send_error)
|
|
82
126
|
}
|
|
83
127
|
|
|
84
|
-
///
|
|
85
|
-
|
|
86
|
-
|
|
128
|
+
/// Close the producing side while retaining the receiver and queued chunks.
|
|
129
|
+
///
|
|
130
|
+
/// Calling this method more than once has no additional effect.
|
|
131
|
+
///
|
|
132
|
+
/// # Errors
|
|
133
|
+
///
|
|
134
|
+
/// Returns `Wreq::BodyError` if the internal state is already borrowed.
|
|
135
|
+
pub fn close(ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
|
|
136
|
+
let mut inner = rb_self.write_inner(ruby)?;
|
|
87
137
|
inner.tx.take();
|
|
88
|
-
|
|
138
|
+
Ok(())
|
|
89
139
|
}
|
|
90
|
-
}
|
|
91
140
|
|
|
92
|
-
|
|
93
|
-
|
|
141
|
+
/// Return whether this sender can no longer accept body chunks.
|
|
142
|
+
///
|
|
143
|
+
/// # Errors
|
|
144
|
+
///
|
|
145
|
+
/// Returns `Wreq::BodyError` if the internal state is already borrowed.
|
|
146
|
+
pub fn is_closed(ruby: &Ruby, rb_self: &Self) -> Result<bool, Error> {
|
|
147
|
+
rb_self.read_inner(ruby).map(|r| r.is_closed())
|
|
148
|
+
}
|
|
94
149
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
.
|
|
99
|
-
.
|
|
150
|
+
/// Borrow the channel state without panicking on accidental re-entry.
|
|
151
|
+
fn read_inner(&self, ruby: &Ruby) -> Result<Ref<'_, InnerBodySender>, Error> {
|
|
152
|
+
self.0
|
|
153
|
+
.try_borrow()
|
|
154
|
+
.map_err(|err| body_sender_borrow_error(ruby, err))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// Mutably borrow the channel state without panicking on accidental re-entry.
|
|
158
|
+
fn write_inner(&self, ruby: &Ruby) -> Result<RefMut<'_, InnerBodySender>, Error> {
|
|
159
|
+
self.0
|
|
160
|
+
.try_borrow_mut()
|
|
161
|
+
.map_err(|err| body_sender_borrow_mut_error(ruby, err))
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// Move the receiving side into one request body.
|
|
165
|
+
///
|
|
166
|
+
/// # Errors
|
|
167
|
+
///
|
|
168
|
+
/// Returns `Wreq::MemoryError` if the receiver was already consumed, or
|
|
169
|
+
/// `Wreq::BodyError` if Ruby re-enters while the state is borrowed.
|
|
170
|
+
pub(super) fn take_receiver(&self, ruby: &Ruby) -> Result<ReceiverStream<Bytes>, Error> {
|
|
171
|
+
self.write_inner(ruby)?
|
|
100
172
|
.rx
|
|
101
173
|
.take()
|
|
102
174
|
.map(ReceiverStream::new)
|
|
103
|
-
.ok_or_else(memory_error)
|
|
175
|
+
.ok_or_else(|| memory_error(ruby))
|
|
104
176
|
}
|
|
105
177
|
}
|
|
106
178
|
|
|
179
|
+
/// Parse and validate the optional Ruby channel capacity.
|
|
180
|
+
///
|
|
181
|
+
/// [`mpsc::channel`] panics for zero or values above
|
|
182
|
+
/// [`Semaphore::MAX_PERMITS`], so validation must finish before channel creation.
|
|
183
|
+
fn parse_capacity(ruby: &Ruby, args: &[Value]) -> Result<usize, Error> {
|
|
184
|
+
let Some(value) = scan_args::<(), (Option<Value>,), (), (), (), ()>(args)?
|
|
185
|
+
.optional
|
|
186
|
+
.0
|
|
187
|
+
else {
|
|
188
|
+
return Ok(DEFAULT_CHANNEL_CAPACITY);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
let integer = Integer::from_value(value)
|
|
192
|
+
.ok_or_else(|| type_error(ruby, "capacity must be an Integer"))?;
|
|
193
|
+
let capacity = integer
|
|
194
|
+
.to_i64()
|
|
195
|
+
.ok()
|
|
196
|
+
.and_then(|capacity| usize::try_from(capacity).ok())
|
|
197
|
+
.filter(|capacity| (1..=Semaphore::MAX_PERMITS).contains(capacity))
|
|
198
|
+
.ok_or_else(|| invalid_capacity_error(ruby))?;
|
|
199
|
+
|
|
200
|
+
Ok(capacity)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/// Build the synchronous Ruby error used for an invalid channel capacity.
|
|
204
|
+
fn invalid_capacity_error(ruby: &Ruby) -> Error {
|
|
205
|
+
argument_error(
|
|
206
|
+
ruby,
|
|
207
|
+
format!("capacity must be between 1 and {}", Semaphore::MAX_PERMITS),
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
107
211
|
/// A wrapper around [`tokio::sync::mpsc::Receiver`] that implements [`Stream`].
|
|
108
212
|
pub struct ReceiverStream<T> {
|
|
109
213
|
inner: mpsc::Receiver<T>,
|
data/src/client/body.rs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
mod form;
|
|
2
|
-
mod json;
|
|
3
|
-
mod stream;
|
|
1
|
+
pub mod form;
|
|
2
|
+
pub mod json;
|
|
3
|
+
pub mod stream;
|
|
4
4
|
|
|
5
5
|
use bytes::Bytes;
|
|
6
6
|
use futures_util::StreamExt;
|
|
@@ -9,29 +9,24 @@ use magnus::{
|
|
|
9
9
|
typed_data::Obj,
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
pub use self::{
|
|
13
|
-
form::Form,
|
|
14
|
-
json::Json,
|
|
15
|
-
stream::{BodyReceiver, BodySender, ReceiverStream},
|
|
16
|
-
};
|
|
17
|
-
|
|
18
12
|
/// Represents the body of an HTTP request.
|
|
19
13
|
/// Supports text, bytes, and streaming bodies (Proc/Enumerator).
|
|
20
14
|
pub enum Body {
|
|
21
15
|
/// Static bytes body
|
|
22
16
|
Bytes(Bytes),
|
|
23
17
|
/// Streaming body
|
|
24
|
-
Stream(ReceiverStream<Bytes>),
|
|
18
|
+
Stream(stream::ReceiverStream<Bytes>),
|
|
25
19
|
}
|
|
26
20
|
|
|
27
21
|
impl TryConvert for Body {
|
|
28
22
|
fn try_convert(val: Value) -> Result<Self, Error> {
|
|
23
|
+
let ruby = Ruby::get_with(val);
|
|
29
24
|
if let Ok(s) = RString::try_convert(val) {
|
|
30
25
|
return Ok(Body::Bytes(s.to_bytes()));
|
|
31
26
|
}
|
|
32
27
|
|
|
33
|
-
let obj = Obj::<BodySender>::try_convert(val)?;
|
|
34
|
-
let stream =
|
|
28
|
+
let obj = Obj::<stream::BodySender>::try_convert(val)?;
|
|
29
|
+
let stream = obj.take_receiver(&ruby)?;
|
|
35
30
|
Ok(Body::Stream(stream))
|
|
36
31
|
}
|
|
37
32
|
}
|
|
@@ -50,8 +45,9 @@ impl From<Body> for wreq::Body {
|
|
|
50
45
|
|
|
51
46
|
pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
|
|
52
47
|
let sender_class = gem_module.define_class("BodySender", ruby.class_object())?;
|
|
53
|
-
sender_class.define_singleton_method("new", function!(BodySender::new, -1))?;
|
|
54
|
-
sender_class.define_method("push", method!(BodySender::push, 1))?;
|
|
55
|
-
sender_class.define_method("close", magnus::method!(BodySender::close, 0))?;
|
|
48
|
+
sender_class.define_singleton_method("new", function!(stream::BodySender::new, -1))?;
|
|
49
|
+
sender_class.define_method("push", method!(stream::BodySender::push, 1))?;
|
|
50
|
+
sender_class.define_method("close", magnus::method!(stream::BodySender::close, 0))?;
|
|
51
|
+
sender_class.define_method("closed?", magnus::method!(stream::BodySender::is_closed, 0))?;
|
|
56
52
|
Ok(())
|
|
57
53
|
}
|
data/src/client/param.rs
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
+
use ::serde::{Deserialize, Serialize};
|
|
1
2
|
use indexmap::IndexMap;
|
|
2
|
-
use serde::{Deserialize, Serialize};
|
|
3
3
|
|
|
4
|
-
///
|
|
4
|
+
/// HTTP parameters represented as an insertion-ordered Ruby mapping.
|
|
5
5
|
pub type Params = IndexMap<String, ParamValue>;
|
|
6
6
|
|
|
7
|
-
///
|
|
7
|
+
/// A scalar Ruby value accepted in query-string and form mappings.
|
|
8
8
|
#[derive(Serialize, Deserialize)]
|
|
9
9
|
#[serde(untagged)]
|
|
10
10
|
pub enum ParamValue {
|
|
11
|
-
/// A
|
|
11
|
+
/// A Ruby `true` or `false` value.
|
|
12
12
|
Boolean(bool),
|
|
13
|
-
///
|
|
13
|
+
/// A Ruby Integer that fits in the native pointer-sized range.
|
|
14
14
|
Number(isize),
|
|
15
|
-
/// A
|
|
15
|
+
/// A Ruby Float.
|
|
16
16
|
Float64(f64),
|
|
17
|
-
/// A
|
|
17
|
+
/// A Ruby String or Symbol.
|
|
18
18
|
String(String),
|
|
19
19
|
}
|
data/src/client/req.rs
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
use std::{net::IpAddr, time::Duration};
|
|
2
2
|
|
|
3
|
+
use ::serde::Deserialize;
|
|
3
4
|
use http::header;
|
|
4
5
|
use magnus::{RHash, TryConvert, typed_data::Obj, value::ReprValue};
|
|
5
|
-
use serde::Deserialize;
|
|
6
6
|
use wreq::{Client, Proxy};
|
|
7
7
|
|
|
8
|
-
use super::body::{Body, Form, Json};
|
|
8
|
+
use super::body::{Body, form::Form, json::Json};
|
|
9
9
|
use crate::{
|
|
10
|
+
arch::SUPPORTS_INTERFACE,
|
|
10
11
|
client::{query::Query, resp::Response},
|
|
11
12
|
cookie::Cookies,
|
|
12
13
|
emulate::Emulation,
|
|
13
|
-
error::
|
|
14
|
+
error::wreq_error,
|
|
14
15
|
extractor::Extractor,
|
|
15
16
|
header::{Headers, OrigHeaders},
|
|
16
17
|
http::{Method, Version},
|
|
18
|
+
options::{NativeOption, Options},
|
|
17
19
|
rt,
|
|
18
20
|
};
|
|
19
21
|
|
|
@@ -22,12 +24,12 @@ use crate::{
|
|
|
22
24
|
#[non_exhaustive]
|
|
23
25
|
pub struct Request {
|
|
24
26
|
/// The emulation option for the request.
|
|
25
|
-
#[serde(
|
|
26
|
-
emulation:
|
|
27
|
+
#[serde(default)]
|
|
28
|
+
emulation: NativeOption<Emulation>,
|
|
27
29
|
|
|
28
30
|
/// The proxy to use for the request.
|
|
29
|
-
#[serde(
|
|
30
|
-
proxy:
|
|
31
|
+
#[serde(default)]
|
|
32
|
+
proxy: NativeOption<Proxy>,
|
|
31
33
|
|
|
32
34
|
/// Bind to a local IP Address.
|
|
33
35
|
local_address: Option<IpAddr>,
|
|
@@ -43,23 +45,23 @@ pub struct Request {
|
|
|
43
45
|
read_timeout: Option<u64>,
|
|
44
46
|
|
|
45
47
|
/// The HTTP version to use for the request.
|
|
46
|
-
#[serde(
|
|
47
|
-
version:
|
|
48
|
+
#[serde(default)]
|
|
49
|
+
version: NativeOption<Version>,
|
|
48
50
|
|
|
49
51
|
/// The option enables default headers.
|
|
50
52
|
default_headers: Option<bool>,
|
|
51
53
|
|
|
52
54
|
/// The headers to use for the request.
|
|
53
|
-
#[serde(
|
|
54
|
-
headers:
|
|
55
|
+
#[serde(default)]
|
|
56
|
+
headers: NativeOption<Headers>,
|
|
55
57
|
|
|
56
58
|
/// The original headers to use for the request.
|
|
57
|
-
#[serde(
|
|
58
|
-
orig_headers:
|
|
59
|
+
#[serde(default)]
|
|
60
|
+
orig_headers: NativeOption<OrigHeaders>,
|
|
59
61
|
|
|
60
62
|
/// The cookies to use for the request.
|
|
61
|
-
#[serde(
|
|
62
|
-
cookies:
|
|
63
|
+
#[serde(default)]
|
|
64
|
+
cookies: NativeOption<Cookies>,
|
|
63
65
|
|
|
64
66
|
/// Whether to allow redirects.
|
|
65
67
|
allow_redirects: Option<bool>,
|
|
@@ -95,57 +97,95 @@ pub struct Request {
|
|
|
95
97
|
form: Option<Form>,
|
|
96
98
|
|
|
97
99
|
/// The JSON body to use for the request.
|
|
98
|
-
|
|
100
|
+
#[serde(default)]
|
|
101
|
+
json: NativeOption<Json>,
|
|
99
102
|
|
|
100
103
|
/// The body to use for the request.
|
|
101
|
-
#[serde(
|
|
102
|
-
body:
|
|
104
|
+
#[serde(default)]
|
|
105
|
+
body: NativeOption<Body>,
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
impl Request {
|
|
106
109
|
/// Create a new [`Request`] from Ruby keyword arguments.
|
|
110
|
+
///
|
|
111
|
+
/// # Errors
|
|
112
|
+
///
|
|
113
|
+
/// Returns before network I/O for unknown, duplicate, unsupported,
|
|
114
|
+
/// conflicting, ineffective, or invalid option values.
|
|
107
115
|
pub fn new(ruby: &magnus::Ruby, hash: RHash) -> Result<Self, magnus::Error> {
|
|
108
116
|
let keyword = hash.as_value();
|
|
109
|
-
let
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
builder.proxy = Extractor::<Proxy>::try_convert(keyword)?.into_inner();
|
|
117
|
+
let options = Options::new(ruby, hash);
|
|
118
|
+
let mut builder = Self::deserialize_options(&options)?;
|
|
119
|
+
options
|
|
120
|
+
.validator()
|
|
121
|
+
.require_when_present(
|
|
122
|
+
stringify!(max_redirects),
|
|
123
|
+
builder.max_redirects.is_some(),
|
|
124
|
+
builder.allow_redirects == Some(true),
|
|
125
|
+
":allow_redirects to be true",
|
|
126
|
+
)
|
|
127
|
+
.finish()?;
|
|
128
|
+
|
|
129
|
+
extract_native_option!(
|
|
130
|
+
options,
|
|
131
|
+
builder,
|
|
132
|
+
emulation,
|
|
133
|
+
Obj<Emulation> => |value| (*value).clone()
|
|
134
|
+
);
|
|
135
|
+
extract_native_option!(options, builder, version);
|
|
136
|
+
extract_native_option!(options, builder, headers);
|
|
137
|
+
extract_native_option!(options, builder, orig_headers);
|
|
138
|
+
extract_native_option!(options, builder, cookies);
|
|
139
|
+
extract_native_option!(options, builder, json, present);
|
|
140
|
+
builder
|
|
141
|
+
.proxy
|
|
142
|
+
.set(Extractor::<Proxy>::try_convert(keyword)?.into_inner());
|
|
143
|
+
extract_native_option!(options, builder, body);
|
|
137
144
|
|
|
138
145
|
Ok(builder)
|
|
139
146
|
}
|
|
147
|
+
|
|
148
|
+
/// Validate pre-conversion rules and deserialize the request options.
|
|
149
|
+
///
|
|
150
|
+
/// # Errors
|
|
151
|
+
///
|
|
152
|
+
/// Returns `ArgumentError` for failed rules or the Ruby conversion error
|
|
153
|
+
/// produced by an invalid option value.
|
|
154
|
+
fn deserialize_options(options: &Options<'_>) -> Result<Self, magnus::Error> {
|
|
155
|
+
options
|
|
156
|
+
.validate_keys::<Self>()?
|
|
157
|
+
.validator()
|
|
158
|
+
.reject_unsupported(stringify!(interface), SUPPORTS_INTERFACE)
|
|
159
|
+
.reject_conflicts([
|
|
160
|
+
(stringify!(body), options.is_non_nil(stringify!(body))),
|
|
161
|
+
(stringify!(form), options.is_non_nil(stringify!(form))),
|
|
162
|
+
(stringify!(json), options.is_present(stringify!(json))),
|
|
163
|
+
])
|
|
164
|
+
.reject_conflicts([
|
|
165
|
+
(stringify!(auth), options.is_non_nil(stringify!(auth))),
|
|
166
|
+
(
|
|
167
|
+
stringify!(bearer_auth),
|
|
168
|
+
options.is_non_nil(stringify!(bearer_auth)),
|
|
169
|
+
),
|
|
170
|
+
(
|
|
171
|
+
stringify!(basic_auth),
|
|
172
|
+
options.is_non_nil(stringify!(basic_auth)),
|
|
173
|
+
),
|
|
174
|
+
])
|
|
175
|
+
.finish()?
|
|
176
|
+
.deserialize::<Self>()
|
|
177
|
+
}
|
|
140
178
|
}
|
|
141
179
|
|
|
142
180
|
pub fn execute_request<U: AsRef<str>>(
|
|
181
|
+
ruby: &magnus::Ruby,
|
|
143
182
|
client: Client,
|
|
144
183
|
method: Method,
|
|
145
184
|
url: U,
|
|
146
185
|
mut request: Request,
|
|
147
186
|
) -> Result<Response, magnus::Error> {
|
|
148
187
|
rt::try_block_on(
|
|
188
|
+
ruby,
|
|
149
189
|
async move {
|
|
150
190
|
let mut builder = client.request(method.into_ffi(), url.as_ref());
|
|
151
191
|
|
|
@@ -269,6 +309,6 @@ pub fn execute_request<U: AsRef<str>>(
|
|
|
269
309
|
// Send request.
|
|
270
310
|
builder.send().await.map(Response::new)
|
|
271
311
|
},
|
|
272
|
-
|
|
312
|
+
wreq_error,
|
|
273
313
|
)
|
|
274
314
|
}
|
data/src/client/resp.rs
CHANGED
|
@@ -9,10 +9,10 @@ use magnus::{Error, Module, RArray, RModule, Ruby, Value, scan_args::scan_args};
|
|
|
9
9
|
use wreq::Uri;
|
|
10
10
|
|
|
11
11
|
use crate::{
|
|
12
|
-
client::body::{
|
|
12
|
+
client::body::{json::Json, stream::BodyReceiver},
|
|
13
13
|
cookie::Cookie,
|
|
14
|
-
error::{memory_error, no_block_given_error,
|
|
15
|
-
gvl
|
|
14
|
+
error::{memory_error, no_block_given_error, wreq_error},
|
|
15
|
+
gvl,
|
|
16
16
|
header::Headers,
|
|
17
17
|
http::{StatusCode, Version},
|
|
18
18
|
rt,
|
|
@@ -64,7 +64,7 @@ impl Response {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/// Internal method to get the wreq::Response, optionally streaming the body.
|
|
67
|
-
fn response(&self, stream: bool) -> Result<wreq::Response, Error> {
|
|
67
|
+
fn response(&self, ruby: &Ruby, stream: bool) -> Result<wreq::Response, Error> {
|
|
68
68
|
let build_response = |body: wreq::Body| -> wreq::Response {
|
|
69
69
|
let mut response = HttpResponse::new(body);
|
|
70
70
|
*response.version_mut() = self.version.into_ffi();
|
|
@@ -81,8 +81,9 @@ impl Response {
|
|
|
81
81
|
Ok(build_response(body))
|
|
82
82
|
} else {
|
|
83
83
|
let bytes = rt::try_block_on(
|
|
84
|
+
ruby,
|
|
84
85
|
BodyExt::collect(body).map_ok(|buf| buf.to_bytes()),
|
|
85
|
-
|
|
86
|
+
wreq_error,
|
|
86
87
|
)?;
|
|
87
88
|
|
|
88
89
|
self.body
|
|
@@ -103,7 +104,7 @@ impl Response {
|
|
|
103
104
|
};
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
Err(memory_error())
|
|
107
|
+
Err(memory_error(ruby))
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
|
|
@@ -167,44 +168,42 @@ impl Response {
|
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
/// Get the response body as bytes.
|
|
170
|
-
pub fn bytes(&
|
|
171
|
-
let response =
|
|
172
|
-
rt::try_block_on(response.bytes(),
|
|
171
|
+
pub fn bytes(ruby: &Ruby, rb_self: &Self) -> Result<Bytes, Error> {
|
|
172
|
+
let response = rb_self.response(ruby, false)?;
|
|
173
|
+
rt::try_block_on(ruby, response.bytes(), wreq_error)
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
/// Get the full response text given a specific encoding.
|
|
176
|
-
pub fn text(&
|
|
177
|
+
pub fn text(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<String, Error> {
|
|
177
178
|
let args = scan_args::<(), (Option<String>,), (), (), (), ()>(args)?;
|
|
178
|
-
let response =
|
|
179
|
+
let response = rb_self.response(ruby, false)?;
|
|
179
180
|
match args.optional.0 {
|
|
180
181
|
Some(encoding) => {
|
|
181
|
-
rt::try_block_on(response.text_with_charset(encoding),
|
|
182
|
+
rt::try_block_on(ruby, response.text_with_charset(encoding), wreq_error)
|
|
182
183
|
}
|
|
183
|
-
None => rt::try_block_on(response.text(),
|
|
184
|
+
None => rt::try_block_on(ruby, response.text(), wreq_error),
|
|
184
185
|
}
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
/// Get the response body as JSON.
|
|
188
189
|
pub fn json(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
189
|
-
let response = rb_self.response(false)?;
|
|
190
|
-
let json = rt::try_block_on(response.json::<Json>(),
|
|
191
|
-
|
|
190
|
+
let response = rb_self.response(ruby, false)?;
|
|
191
|
+
let json = rt::try_block_on(ruby, response.json::<Json>(), wreq_error)?;
|
|
192
|
+
crate::serde::serialize(ruby, &json)
|
|
192
193
|
}
|
|
193
194
|
|
|
194
195
|
/// Yield response body chunks to the given Ruby block.
|
|
195
196
|
pub fn chunks(ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
|
|
196
197
|
if !ruby.block_given() {
|
|
197
|
-
return Err(no_block_given_error());
|
|
198
|
+
return Err(no_block_given_error(ruby));
|
|
198
199
|
}
|
|
199
200
|
|
|
200
|
-
let receiver =
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
.map(BodyReceiver::new)
|
|
205
|
-
})?;
|
|
201
|
+
let receiver = rb_self
|
|
202
|
+
.response(ruby, true)
|
|
203
|
+
.map(wreq::Response::bytes_stream)
|
|
204
|
+
.map(BodyReceiver::new)?;
|
|
206
205
|
|
|
207
|
-
while let Some(chunk) = receiver.next()? {
|
|
206
|
+
while let Some(chunk) = receiver.next(ruby)? {
|
|
208
207
|
let _: Value = ruby.yield_value(chunk)?;
|
|
209
208
|
}
|
|
210
209
|
|