wreq 1.2.4 → 1.2.6
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.lock +22 -19
- data/Cargo.toml +15 -2
- data/crates/wreq-util/.github/FUNDING.yml +15 -0
- data/crates/wreq-util/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- data/crates/wreq-util/.github/ISSUE_TEMPLATE/custom.md +10 -0
- data/crates/wreq-util/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/crates/wreq-util/.github/dependabot.yml +22 -0
- data/crates/wreq-util/.github/workflows/ci.yml +82 -0
- data/crates/wreq-util/.github/workflows/release-plz.yml +52 -0
- data/crates/wreq-util/.gitignore +24 -0
- data/crates/wreq-util/CHANGELOG.md +74 -0
- data/crates/wreq-util/Cargo.toml +94 -0
- data/crates/wreq-util/LICENSE +201 -0
- data/crates/wreq-util/README.md +62 -0
- data/crates/wreq-util/examples/emulate.rs +39 -0
- data/crates/wreq-util/examples/tower_delay.rs +191 -0
- data/crates/wreq-util/release-plz.toml +2 -0
- data/crates/wreq-util/rustfmt.toml +5 -0
- data/crates/wreq-util/src/emulate/compress.rs +85 -0
- data/crates/wreq-util/src/emulate/macros.rs +372 -0
- data/crates/wreq-util/src/emulate/profile/chrome/header.rs +74 -0
- data/crates/wreq-util/src/emulate/profile/chrome/http2.rs +65 -0
- data/crates/wreq-util/src/emulate/profile/chrome/tls.rs +153 -0
- data/crates/wreq-util/src/emulate/profile/chrome.rs +2028 -0
- data/crates/wreq-util/src/emulate/profile/firefox/header.rs +37 -0
- data/crates/wreq-util/src/emulate/profile/firefox/http2.rs +113 -0
- data/crates/wreq-util/src/emulate/profile/firefox/tls.rs +253 -0
- data/crates/wreq-util/src/emulate/profile/firefox.rs +518 -0
- data/crates/wreq-util/src/emulate/profile/okhttp.rs +241 -0
- data/crates/wreq-util/src/emulate/profile/opera/header.rs +29 -0
- data/crates/wreq-util/src/emulate/profile/opera/http2.rs +50 -0
- data/crates/wreq-util/src/emulate/profile/opera/tls.rs +97 -0
- data/crates/wreq-util/src/emulate/profile/opera.rs +299 -0
- data/crates/wreq-util/src/emulate/profile/safari/header.rs +59 -0
- data/crates/wreq-util/src/emulate/profile/safari/http2.rs +116 -0
- data/crates/wreq-util/src/emulate/profile/safari/tls.rs +177 -0
- data/crates/wreq-util/src/emulate/profile/safari.rs +222 -0
- data/crates/wreq-util/src/emulate/profile.rs +47 -0
- data/crates/wreq-util/src/emulate.rs +369 -0
- data/crates/wreq-util/src/lib.rs +14 -0
- data/crates/wreq-util/src/rand.rs +24 -0
- data/crates/wreq-util/src/tower/delay/future.rs +43 -0
- data/crates/wreq-util/src/tower/delay/layer.rs +201 -0
- data/crates/wreq-util/src/tower/delay/service.rs +190 -0
- data/crates/wreq-util/src/tower/delay.rs +98 -0
- data/crates/wreq-util/src/tower.rs +7 -0
- data/crates/wreq-util/tests/client.rs +153 -0
- data/crates/wreq-util/tests/emulate_chrome.rs +270 -0
- data/crates/wreq-util/tests/emulate_firefox.rs +92 -0
- data/crates/wreq-util/tests/emulate_okhttp.rs +46 -0
- data/crates/wreq-util/tests/emulate_opera.rs +113 -0
- data/crates/wreq-util/tests/emulate_safari.rs +151 -0
- data/crates/wreq-util/tests/support/mod.rs +55 -0
- data/crates/wreq-util/tests/support/server.rs +232 -0
- data/examples/emulate_request.rb +1 -1
- data/lib/wreq.rb +72 -45
- data/lib/wreq_ruby/body.rb +30 -9
- data/lib/wreq_ruby/client.rb +88 -55
- data/lib/wreq_ruby/cookie.rb +81 -21
- data/lib/wreq_ruby/emulate.rb +77 -7
- data/lib/wreq_ruby/error.rb +5 -7
- data/lib/wreq_ruby/header.rb +205 -114
- data/lib/wreq_ruby/http.rb +74 -0
- data/lib/wreq_ruby/response.rb +31 -3
- data/script/build_windows_gnu.ps1 +6 -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 +86 -50
- data/src/error.rs +198 -45
- data/src/extractor.rs +16 -76
- data/src/header.rs +318 -130
- 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/header_test.rb +228 -192
- data/test/json_precision_test.rb +209 -0
- data/test/option_validation_test.rb +311 -0
- data/test/stream_test.rb +36 -0
- data/test/value_semantics_test.rb +238 -0
- metadata +77 -1
data/src/client/body/json.rs
CHANGED
|
@@ -1,16 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
//! JSON conversion at the Ruby request and response boundary.
|
|
2
|
+
//!
|
|
3
|
+
//! Request values are converted from Ruby into an owned [`Json`] tree before
|
|
4
|
+
//! network I/O. Response bodies are deserialized into the same tree by wreq and
|
|
5
|
+
//! then converted back into Ruby values.
|
|
6
|
+
//!
|
|
7
|
+
//! The underlying `serde_json` configuration preserves object insertion order
|
|
8
|
+
//! and arbitrary-size integer tokens in both directions.
|
|
3
9
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
use ::serde::{Deserialize, Serialize};
|
|
11
|
+
use magnus::{Error, Ruby, TryConvert, Value};
|
|
12
|
+
|
|
13
|
+
use crate::{error::json_serialization_error, serde::deserialize_json};
|
|
14
|
+
|
|
15
|
+
/// An owned JSON tree shared by request and response conversion.
|
|
16
|
+
///
|
|
17
|
+
/// This wrapper keeps the configured `serde_json::Value` representation private
|
|
18
|
+
/// so callers use the same precision and ordering behavior in both directions.
|
|
19
|
+
#[derive(Deserialize, Serialize)]
|
|
20
|
+
#[serde(transparent)]
|
|
21
|
+
pub struct Json(serde_json::Value);
|
|
22
|
+
|
|
23
|
+
/// Convert supported Ruby request values into an owned JSON tree.
|
|
24
|
+
///
|
|
25
|
+
/// Supported values are `Hash`, `Array`, `String`, `Symbol`, `Integer`, finite
|
|
26
|
+
/// `Float`, booleans, and `nil`. Hash keys must be strings or symbols. Arrays
|
|
27
|
+
/// and hashes are limited to 100 nesting levels, which also bounds cyclic input.
|
|
28
|
+
/// Unsupported values are reported as `Wreq::BuilderError` before network I/O.
|
|
29
|
+
impl TryConvert for Json {
|
|
30
|
+
fn try_convert(value: Value) -> Result<Self, Error> {
|
|
31
|
+
let ruby = Ruby::get_with(value);
|
|
32
|
+
deserialize_json(&ruby, value)
|
|
33
|
+
.map(Self)
|
|
34
|
+
.map_err(|error| json_serialization_error(&ruby, error))
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#[cfg(test)]
|
|
39
|
+
mod tests {
|
|
40
|
+
use super::Json;
|
|
41
|
+
|
|
42
|
+
#[test]
|
|
43
|
+
fn preserves_number_precision_and_object_order() {
|
|
44
|
+
let source = br#"{"second":115792089237316195423570985008687907853269984665640564039457584007913129639936,"first":1}"#;
|
|
45
|
+
let json: Json = serde_json::from_slice(source).unwrap();
|
|
46
|
+
|
|
47
|
+
assert_eq!(source, serde_json::to_vec(&json).unwrap().as_slice());
|
|
48
|
+
}
|
|
16
49
|
}
|
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
|
}
|