wreq 1.0.0
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 +7 -0
- data/Cargo.toml +54 -0
- data/Gemfile +17 -0
- data/LICENSE +201 -0
- data/README.md +150 -0
- data/Rakefile +90 -0
- data/build.rs +9 -0
- data/examples/body.rb +42 -0
- data/examples/client.rb +33 -0
- data/examples/emulation_request.rb +37 -0
- data/examples/headers.rb +27 -0
- data/examples/proxy.rb +113 -0
- data/examples/send_stream.rb +85 -0
- data/examples/stream.rb +14 -0
- data/examples/thread_interrupt.rb +83 -0
- data/extconf.rb +7 -0
- data/lib/wreq.rb +313 -0
- data/lib/wreq_ruby/body.rb +36 -0
- data/lib/wreq_ruby/client.rb +516 -0
- data/lib/wreq_ruby/cookie.rb +144 -0
- data/lib/wreq_ruby/emulation.rb +186 -0
- data/lib/wreq_ruby/error.rb +159 -0
- data/lib/wreq_ruby/header.rb +197 -0
- data/lib/wreq_ruby/http.rb +132 -0
- data/lib/wreq_ruby/response.rb +208 -0
- data/script/build_platform_gem.rb +34 -0
- data/src/client/body/form.rs +2 -0
- data/src/client/body/json.rs +16 -0
- data/src/client/body/stream.rs +148 -0
- data/src/client/body.rs +57 -0
- data/src/client/param.rs +19 -0
- data/src/client/query.rs +2 -0
- data/src/client/req.rs +251 -0
- data/src/client/resp.rs +250 -0
- data/src/client.rs +392 -0
- data/src/cookie.rs +277 -0
- data/src/emulation.rs +317 -0
- data/src/error.rs +147 -0
- data/src/extractor.rs +199 -0
- data/src/gvl.rs +154 -0
- data/src/header.rs +177 -0
- data/src/http.rs +127 -0
- data/src/lib.rs +97 -0
- data/src/macros.rs +118 -0
- data/src/rt.rs +47 -0
- data/test/client_cookie_test.rb +46 -0
- data/test/client_test.rb +136 -0
- data/test/cookie_test.rb +166 -0
- data/test/emulation_test.rb +21 -0
- data/test/error_handling_test.rb +89 -0
- data/test/header_test.rb +290 -0
- data/test/module_methods_test.rb +75 -0
- data/test/request_parameters_test.rb +175 -0
- data/test/request_test.rb +234 -0
- data/test/response_test.rb +69 -0
- data/test/stream_test.rb +81 -0
- data/test/test_helper.rb +9 -0
- data/wreq.gemspec +68 -0
- metadata +112 -0
data/src/gvl.rs
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
//! Allow usage of unsafe code for FFI with Ruby's GVL functions.
|
|
2
|
+
#![allow(unsafe_code)]
|
|
3
|
+
|
|
4
|
+
use std::{ffi::c_void, mem::MaybeUninit, ptr::null_mut};
|
|
5
|
+
|
|
6
|
+
use rb_sys::rb_thread_call_without_gvl;
|
|
7
|
+
use tokio::sync::watch;
|
|
8
|
+
|
|
9
|
+
/// Container for safely passing closure and result through C callback.
|
|
10
|
+
struct Args<F, R> {
|
|
11
|
+
func: Option<F>,
|
|
12
|
+
result: MaybeUninit<R>,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// Cancellation flag for thread interruption support.
|
|
16
|
+
#[derive(Clone)]
|
|
17
|
+
pub struct CancelFlag {
|
|
18
|
+
rx: watch::Receiver<bool>,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
struct CancelSender {
|
|
22
|
+
tx: watch::Sender<bool>,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl CancelSender {
|
|
26
|
+
fn new() -> (Self, CancelFlag) {
|
|
27
|
+
let (tx, rx) = watch::channel(false);
|
|
28
|
+
(Self { tx }, CancelFlag { rx })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fn cancel(&self) {
|
|
32
|
+
let _ = self.tx.send(true);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
impl CancelFlag {
|
|
37
|
+
/// Wait until cancellation is signaled (zero-latency, no polling).
|
|
38
|
+
pub async fn cancelled(&self) {
|
|
39
|
+
let mut rx = self.rx.clone();
|
|
40
|
+
if *rx.borrow_and_update() {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
loop {
|
|
44
|
+
if rx.changed().await.is_err() {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if *rx.borrow() {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
struct UnblockData {
|
|
55
|
+
sender: CancelSender,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
unsafe extern "C" fn call_without_gvl<F, R>(arg: *mut c_void) -> *mut c_void
|
|
59
|
+
where
|
|
60
|
+
F: FnOnce() -> R,
|
|
61
|
+
R: Sized,
|
|
62
|
+
{
|
|
63
|
+
let args = unsafe { &mut *(arg as *mut Args<F, R>) };
|
|
64
|
+
|
|
65
|
+
// Take closure from Option to transfer ownership.
|
|
66
|
+
if let Some(func) = args.func.take() {
|
|
67
|
+
args.result.write(func());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
null_mut()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
unsafe extern "C" fn unblock_func(arg: *mut c_void) {
|
|
74
|
+
if !arg.is_null() {
|
|
75
|
+
let data = unsafe { &*(arg as *const UnblockData) };
|
|
76
|
+
data.sender.cancel();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Executes the given closure without holding the Ruby GVL (Global VM Lock).
|
|
81
|
+
///
|
|
82
|
+
/// WARNING: Do NOT nest calls to [`nogvl`] or [`nogvl_cancellable`] inside each other.
|
|
83
|
+
/// Nesting these functions will cause Ruby thread deadlock, because the inner call
|
|
84
|
+
/// will block waiting for the GVL while the outer call has already released it.
|
|
85
|
+
/// This results in all Ruby threads being suspended indefinitely.
|
|
86
|
+
pub fn nogvl<F, R>(func: F) -> R
|
|
87
|
+
where
|
|
88
|
+
F: FnOnce() -> R,
|
|
89
|
+
R: Sized,
|
|
90
|
+
{
|
|
91
|
+
// Create stable wrapper to keep data valid during callback.
|
|
92
|
+
let mut args = Args {
|
|
93
|
+
func: Some(func),
|
|
94
|
+
result: MaybeUninit::uninit(),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
let arg_ptr = &mut args as *mut _ as *mut c_void;
|
|
98
|
+
|
|
99
|
+
unsafe {
|
|
100
|
+
rb_thread_call_without_gvl(Some(call_without_gvl::<F, R>), arg_ptr, None, null_mut());
|
|
101
|
+
args.result.assume_init()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// Executes the given closure without GVL, supporting cancellation via thread interrupt.
|
|
106
|
+
///
|
|
107
|
+
/// WARNING: Do NOT nest calls to [`nogvl`] or [`nogvl_cancellable`] inside each other.
|
|
108
|
+
/// Nesting these functions will cause Ruby thread deadlock, because the inner call
|
|
109
|
+
/// will block waiting for the GVL while the outer call has already released it.
|
|
110
|
+
/// This results in all Ruby threads being suspended indefinitely.
|
|
111
|
+
pub fn nogvl_cancellable<F, R>(func: F) -> R
|
|
112
|
+
where
|
|
113
|
+
F: FnOnce(CancelFlag) -> R,
|
|
114
|
+
R: Sized,
|
|
115
|
+
{
|
|
116
|
+
let (sender, flag) = CancelSender::new();
|
|
117
|
+
let unblock_data = UnblockData { sender };
|
|
118
|
+
|
|
119
|
+
struct Wrapper<F, R> {
|
|
120
|
+
func: Option<F>,
|
|
121
|
+
flag: CancelFlag,
|
|
122
|
+
result: MaybeUninit<R>,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let mut wrapper = Wrapper {
|
|
126
|
+
func: Some(func),
|
|
127
|
+
flag,
|
|
128
|
+
result: MaybeUninit::uninit(),
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
unsafe extern "C" fn call_with_flag<F, R>(arg: *mut c_void) -> *mut c_void
|
|
132
|
+
where
|
|
133
|
+
F: FnOnce(CancelFlag) -> R,
|
|
134
|
+
{
|
|
135
|
+
let wrapper = unsafe { &mut *(arg as *mut Wrapper<F, R>) };
|
|
136
|
+
if let Some(func) = wrapper.func.take() {
|
|
137
|
+
wrapper.result.write(func(wrapper.flag.clone()));
|
|
138
|
+
}
|
|
139
|
+
null_mut()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let wrapper_ptr = &mut wrapper as *mut _ as *mut c_void;
|
|
143
|
+
let unblock_data_ptr = &unblock_data as *const _ as *mut c_void;
|
|
144
|
+
|
|
145
|
+
unsafe {
|
|
146
|
+
rb_thread_call_without_gvl(
|
|
147
|
+
Some(call_with_flag::<F, R>),
|
|
148
|
+
wrapper_ptr,
|
|
149
|
+
Some(unblock_func),
|
|
150
|
+
unblock_data_ptr,
|
|
151
|
+
);
|
|
152
|
+
wrapper.result.assume_init()
|
|
153
|
+
}
|
|
154
|
+
}
|
data/src/header.rs
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
use std::cell::RefCell;
|
|
2
|
+
|
|
3
|
+
use bytes::Bytes;
|
|
4
|
+
use http::{HeaderMap, HeaderName, HeaderValue};
|
|
5
|
+
use magnus::{
|
|
6
|
+
Error, Module, Object, RArray, RModule, Ruby, block::Yield, function, method,
|
|
7
|
+
typed_data::Inspect,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
use crate::error::{header_name_error_to_magnus, header_value_error_to_magnus};
|
|
11
|
+
|
|
12
|
+
/// HTTP headers collection with read and write operations.
|
|
13
|
+
///
|
|
14
|
+
/// This class wraps HTTP headers and provides convenient methods for
|
|
15
|
+
/// accessing, modifying, and iterating over header name-value pairs.
|
|
16
|
+
#[derive(Clone, Default)]
|
|
17
|
+
#[magnus::wrap(class = "Wreq::Headers", free_immediately, size)]
|
|
18
|
+
pub struct Headers(RefCell<HeaderMap>);
|
|
19
|
+
|
|
20
|
+
impl Headers {
|
|
21
|
+
/// Create a new empty Headers instance.
|
|
22
|
+
#[inline]
|
|
23
|
+
pub fn new() -> Self {
|
|
24
|
+
Self::from(HeaderMap::new())
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Get a header value by name (case-insensitive).
|
|
28
|
+
#[inline]
|
|
29
|
+
pub fn get(&self, name: String) -> Option<Bytes> {
|
|
30
|
+
self.0.borrow().get(&name).cloned().map(Bytes::from_owner)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Get all values for a header name (case-insensitive).
|
|
34
|
+
#[inline]
|
|
35
|
+
pub fn get_all(ruby: &Ruby, rb_self: &Self, name: String) -> RArray {
|
|
36
|
+
ruby.ary_from_iter(
|
|
37
|
+
rb_self
|
|
38
|
+
.0
|
|
39
|
+
.borrow()
|
|
40
|
+
.get_all(&name)
|
|
41
|
+
.iter()
|
|
42
|
+
.cloned()
|
|
43
|
+
.map(Bytes::from_owner),
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Set a header, replacing any existing values.
|
|
48
|
+
pub fn set(&self, name: String, value: String) -> Result<(), Error> {
|
|
49
|
+
let header_name = name
|
|
50
|
+
.parse::<HeaderName>()
|
|
51
|
+
.map_err(header_name_error_to_magnus)?;
|
|
52
|
+
let header_value = HeaderValue::from_maybe_shared(Bytes::from(value))
|
|
53
|
+
.map_err(header_value_error_to_magnus)?;
|
|
54
|
+
|
|
55
|
+
self.0.borrow_mut().insert(header_name, header_value);
|
|
56
|
+
Ok(())
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Append a header value without replacing existing values.
|
|
60
|
+
pub fn append(&self, name: String, value: String) -> Result<(), Error> {
|
|
61
|
+
let header_name = name
|
|
62
|
+
.parse::<http::header::HeaderName>()
|
|
63
|
+
.map_err(header_name_error_to_magnus)?;
|
|
64
|
+
let header_value = HeaderValue::from_maybe_shared(Bytes::from(value))
|
|
65
|
+
.map_err(header_value_error_to_magnus)?;
|
|
66
|
+
|
|
67
|
+
self.0.borrow_mut().append(header_name, header_value);
|
|
68
|
+
Ok(())
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// Remove all values for a header name.
|
|
72
|
+
#[inline]
|
|
73
|
+
pub fn remove(&self, name: String) -> Option<Bytes> {
|
|
74
|
+
self.0.borrow_mut().remove(&name).map(Bytes::from_owner)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Check if a header exists (case-insensitive).
|
|
78
|
+
#[inline]
|
|
79
|
+
pub fn contains(&self, name: String) -> bool {
|
|
80
|
+
self.0.borrow().contains_key(&name)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Get the number of headers.
|
|
84
|
+
#[inline]
|
|
85
|
+
pub fn len(&self) -> usize {
|
|
86
|
+
self.0.borrow().len()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Check if headers are empty.
|
|
90
|
+
#[inline]
|
|
91
|
+
pub fn is_empty(&self) -> bool {
|
|
92
|
+
self.0.borrow().is_empty()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Clear all headers.
|
|
96
|
+
#[inline]
|
|
97
|
+
pub fn clear(&self) {
|
|
98
|
+
self.0.borrow_mut().clear();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Get all header names.
|
|
102
|
+
#[inline]
|
|
103
|
+
pub fn keys(ruby: &Ruby, rb_self: &Self) -> RArray {
|
|
104
|
+
ruby.ary_from_iter(rb_self.0.borrow().keys().cloned().map(Bytes::from_owner))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/// Get all header values.
|
|
108
|
+
#[inline]
|
|
109
|
+
pub fn values(ruby: &Ruby, rb_self: &Self) -> RArray {
|
|
110
|
+
ruby.ary_from_iter(rb_self.0.borrow().values().cloned().map(Bytes::from_owner))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Iterate over headers with Ruby block support.
|
|
114
|
+
#[inline]
|
|
115
|
+
pub fn each(&self) -> Yield<impl Iterator<Item = (Bytes, Bytes)>> {
|
|
116
|
+
Yield::Iter(HeaderIter {
|
|
117
|
+
inner: self.0.borrow().clone().into_iter(),
|
|
118
|
+
next_name: None,
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Convert headers to string representation.
|
|
123
|
+
#[inline]
|
|
124
|
+
pub fn to_s(&self) -> String {
|
|
125
|
+
self.0.borrow().inspect()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
impl From<HeaderMap> for Headers {
|
|
130
|
+
fn from(headers: HeaderMap) -> Self {
|
|
131
|
+
Self(RefCell::new(headers))
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
struct HeaderIter {
|
|
136
|
+
inner: http::header::IntoIter<HeaderValue>,
|
|
137
|
+
next_name: Option<HeaderName>,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
impl Iterator for HeaderIter {
|
|
141
|
+
type Item = (Bytes, Bytes);
|
|
142
|
+
fn next(&mut self) -> Option<Self::Item> {
|
|
143
|
+
let (name, value) = self.inner.next()?;
|
|
144
|
+
match (&self.next_name, name) {
|
|
145
|
+
(Some(next_name), None) => Some((
|
|
146
|
+
Bytes::from_owner(next_name.clone()),
|
|
147
|
+
Bytes::from_owner(value),
|
|
148
|
+
)),
|
|
149
|
+
(_, Some(name)) => {
|
|
150
|
+
self.next_name = Some(name.clone());
|
|
151
|
+
Some((Bytes::from_owner(name), Bytes::from_owner(value)))
|
|
152
|
+
}
|
|
153
|
+
(None, None) => None,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
|
|
159
|
+
// Define Headers class with methods
|
|
160
|
+
let headers_class = gem_module.define_class("Headers", ruby.class_object())?;
|
|
161
|
+
headers_class.define_singleton_method("new", function!(Headers::new, 0))?;
|
|
162
|
+
headers_class.define_method("get", method!(Headers::get, 1))?;
|
|
163
|
+
headers_class.define_method("get_all", method!(Headers::get_all, 1))?;
|
|
164
|
+
headers_class.define_method("set", method!(Headers::set, 2))?;
|
|
165
|
+
headers_class.define_method("append", method!(Headers::append, 2))?;
|
|
166
|
+
headers_class.define_method("remove", method!(Headers::remove, 1))?;
|
|
167
|
+
headers_class.define_method("contains?", method!(Headers::contains, 1))?;
|
|
168
|
+
headers_class.define_method("key?", method!(Headers::contains, 1))?;
|
|
169
|
+
headers_class.define_method("length", method!(Headers::len, 0))?;
|
|
170
|
+
headers_class.define_method("empty?", method!(Headers::is_empty, 0))?;
|
|
171
|
+
headers_class.define_method("clear", method!(Headers::clear, 0))?;
|
|
172
|
+
headers_class.define_method("keys", method!(Headers::keys, 0))?;
|
|
173
|
+
headers_class.define_method("values", method!(Headers::values, 0))?;
|
|
174
|
+
headers_class.define_method("each", method!(Headers::each, 0))?;
|
|
175
|
+
headers_class.define_method("to_s", method!(Headers::to_s, 0))?;
|
|
176
|
+
Ok(())
|
|
177
|
+
}
|
data/src/http.rs
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
use magnus::{Error, Module, RModule, Ruby, method, typed_data::Inspect};
|
|
2
|
+
|
|
3
|
+
define_ruby_enum!(
|
|
4
|
+
/// An HTTP version.
|
|
5
|
+
const,
|
|
6
|
+
Version,
|
|
7
|
+
"Wreq::Version",
|
|
8
|
+
wreq::Version,
|
|
9
|
+
HTTP_09,
|
|
10
|
+
HTTP_10,
|
|
11
|
+
HTTP_11,
|
|
12
|
+
HTTP_2,
|
|
13
|
+
HTTP_3,
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
define_ruby_enum!(
|
|
17
|
+
/// An HTTP method.
|
|
18
|
+
Method,
|
|
19
|
+
"Wreq::Method",
|
|
20
|
+
wreq::Method,
|
|
21
|
+
GET,
|
|
22
|
+
HEAD,
|
|
23
|
+
POST,
|
|
24
|
+
PUT,
|
|
25
|
+
DELETE,
|
|
26
|
+
OPTIONS,
|
|
27
|
+
TRACE,
|
|
28
|
+
PATCH,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
/// HTTP status code.
|
|
32
|
+
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
|
33
|
+
#[magnus::wrap(class = "Wreq::StatusCode", free_immediately, size)]
|
|
34
|
+
pub struct StatusCode(pub wreq::StatusCode);
|
|
35
|
+
|
|
36
|
+
// ===== impl Version =====
|
|
37
|
+
|
|
38
|
+
impl Version {
|
|
39
|
+
/// Convert version to string.
|
|
40
|
+
#[inline]
|
|
41
|
+
pub fn to_s(&self) -> String {
|
|
42
|
+
self.into_ffi().inspect()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ===== impl StatusCode =====
|
|
47
|
+
|
|
48
|
+
impl StatusCode {
|
|
49
|
+
/// Return the status code as an integer.
|
|
50
|
+
#[inline]
|
|
51
|
+
pub const fn as_int(&self) -> u16 {
|
|
52
|
+
self.0.as_u16()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Check if status is within 100-199.
|
|
56
|
+
#[inline]
|
|
57
|
+
pub fn is_informational(&self) -> bool {
|
|
58
|
+
self.0.is_informational()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Check if status is within 200-299.
|
|
62
|
+
#[inline]
|
|
63
|
+
pub fn is_success(&self) -> bool {
|
|
64
|
+
self.0.is_success()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Check if status is within 300-399.
|
|
68
|
+
#[inline]
|
|
69
|
+
pub fn is_redirection(&self) -> bool {
|
|
70
|
+
self.0.is_redirection()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Check if status is within 400-499.
|
|
74
|
+
#[inline]
|
|
75
|
+
pub fn is_client_error(&self) -> bool {
|
|
76
|
+
self.0.is_client_error()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Check if status is within 500-599.
|
|
80
|
+
#[inline]
|
|
81
|
+
pub fn is_server_error(&self) -> bool {
|
|
82
|
+
self.0.is_server_error()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// Convert status code to string.
|
|
86
|
+
#[inline]
|
|
87
|
+
pub fn to_s(&self) -> String {
|
|
88
|
+
self.0.to_string()
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
impl From<wreq::StatusCode> for StatusCode {
|
|
93
|
+
fn from(status: wreq::StatusCode) -> Self {
|
|
94
|
+
Self(status)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
|
|
99
|
+
let method_class = gem_module.define_class("Method", ruby.class_object())?;
|
|
100
|
+
method_class.const_set("GET", Method::GET)?;
|
|
101
|
+
method_class.const_set("POST", Method::POST)?;
|
|
102
|
+
method_class.const_set("PUT", Method::PUT)?;
|
|
103
|
+
method_class.const_set("DELETE", Method::DELETE)?;
|
|
104
|
+
method_class.const_set("PATCH", Method::PATCH)?;
|
|
105
|
+
method_class.const_set("HEAD", Method::HEAD)?;
|
|
106
|
+
method_class.const_set("TRACE", Method::TRACE)?;
|
|
107
|
+
method_class.const_set("OPTIONS", Method::OPTIONS)?;
|
|
108
|
+
|
|
109
|
+
let version_class = gem_module.define_class("Version", ruby.class_object())?;
|
|
110
|
+
version_class.const_set("HTTP_09", Version::HTTP_09)?;
|
|
111
|
+
version_class.const_set("HTTP_10", Version::HTTP_10)?;
|
|
112
|
+
version_class.const_set("HTTP_11", Version::HTTP_11)?;
|
|
113
|
+
version_class.const_set("HTTP_2", Version::HTTP_2)?;
|
|
114
|
+
version_class.const_set("HTTP_3", Version::HTTP_3)?;
|
|
115
|
+
version_class.define_method("to_s", method!(Version::to_s, 0))?;
|
|
116
|
+
|
|
117
|
+
let status_code_class = gem_module.define_class("StatusCode", ruby.class_object())?;
|
|
118
|
+
status_code_class.define_method("as_int", method!(StatusCode::as_int, 0))?;
|
|
119
|
+
status_code_class.define_method("informational?", method!(StatusCode::is_informational, 0))?;
|
|
120
|
+
status_code_class.define_method("success?", method!(StatusCode::is_success, 0))?;
|
|
121
|
+
status_code_class.define_method("redirection?", method!(StatusCode::is_redirection, 0))?;
|
|
122
|
+
status_code_class.define_method("client_error?", method!(StatusCode::is_client_error, 0))?;
|
|
123
|
+
status_code_class.define_method("server_error?", method!(StatusCode::is_server_error, 0))?;
|
|
124
|
+
status_code_class.define_method("to_s", method!(StatusCode::to_s, 0))?;
|
|
125
|
+
|
|
126
|
+
Ok(())
|
|
127
|
+
}
|
data/src/lib.rs
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#![allow(clippy::wrong_self_convention)]
|
|
2
|
+
|
|
3
|
+
#[macro_use]
|
|
4
|
+
mod macros;
|
|
5
|
+
mod client;
|
|
6
|
+
mod cookie;
|
|
7
|
+
mod emulation;
|
|
8
|
+
mod error;
|
|
9
|
+
mod extractor;
|
|
10
|
+
mod gvl;
|
|
11
|
+
mod header;
|
|
12
|
+
mod http;
|
|
13
|
+
mod rt;
|
|
14
|
+
|
|
15
|
+
use magnus::{Error, Module, Ruby, Value};
|
|
16
|
+
|
|
17
|
+
use crate::client::{Client, resp::Response};
|
|
18
|
+
|
|
19
|
+
const RUBY_MODULE_NAME: &str = "Wreq";
|
|
20
|
+
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
21
|
+
|
|
22
|
+
/// Send a HTTP request.
|
|
23
|
+
#[inline]
|
|
24
|
+
pub fn request(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
25
|
+
Client::request(&Client::default(), args)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Send a GET request.
|
|
29
|
+
#[inline]
|
|
30
|
+
pub fn get(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
31
|
+
Client::get(&Client::default(), args)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Send a POST request.
|
|
35
|
+
#[inline]
|
|
36
|
+
pub fn post(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
37
|
+
Client::post(&Client::default(), args)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Send a PUT request.
|
|
41
|
+
#[inline]
|
|
42
|
+
pub fn put(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
43
|
+
Client::put(&Client::default(), args)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Send a DELETE request.
|
|
47
|
+
#[inline]
|
|
48
|
+
pub fn delete(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
49
|
+
Client::delete(&Client::default(), args)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Send a HEAD request.
|
|
53
|
+
#[inline]
|
|
54
|
+
pub fn head(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
55
|
+
Client::head(&Client::default(), args)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Send an OPTIONS request.
|
|
59
|
+
#[inline]
|
|
60
|
+
pub fn options(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
61
|
+
Client::options(&Client::default(), args)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Send a TRACE request.
|
|
65
|
+
#[inline]
|
|
66
|
+
pub fn trace(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
67
|
+
Client::trace(&Client::default(), args)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Send a PATCH request.
|
|
71
|
+
#[inline]
|
|
72
|
+
pub fn patch(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
73
|
+
Client::patch(&Client::default(), args)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// wreq ruby binding
|
|
77
|
+
#[magnus::init]
|
|
78
|
+
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
79
|
+
let gem_module = ruby.define_module(RUBY_MODULE_NAME)?;
|
|
80
|
+
gem_module.const_set("VERSION", VERSION)?;
|
|
81
|
+
gem_module.define_module_function("request", magnus::function!(request, -1))?;
|
|
82
|
+
gem_module.define_module_function("get", magnus::function!(get, -1))?;
|
|
83
|
+
gem_module.define_module_function("post", magnus::function!(post, -1))?;
|
|
84
|
+
gem_module.define_module_function("put", magnus::function!(put, -1))?;
|
|
85
|
+
gem_module.define_module_function("delete", magnus::function!(delete, -1))?;
|
|
86
|
+
gem_module.define_module_function("head", magnus::function!(head, -1))?;
|
|
87
|
+
gem_module.define_module_function("options", magnus::function!(options, -1))?;
|
|
88
|
+
gem_module.define_module_function("trace", magnus::function!(trace, -1))?;
|
|
89
|
+
gem_module.define_module_function("patch", magnus::function!(patch, -1))?;
|
|
90
|
+
http::include(ruby, &gem_module)?;
|
|
91
|
+
header::include(ruby, &gem_module)?;
|
|
92
|
+
cookie::include(ruby, &gem_module)?;
|
|
93
|
+
client::include(ruby, &gem_module)?;
|
|
94
|
+
emulation::include(ruby, &gem_module)?;
|
|
95
|
+
error::include(ruby);
|
|
96
|
+
Ok(())
|
|
97
|
+
}
|
data/src/macros.rs
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
macro_rules! apply_option {
|
|
2
|
+
(set_if_some, $builder:expr, $option:expr, $method:ident) => {
|
|
3
|
+
if let Some(value) = $option.take() {
|
|
4
|
+
$builder = $builder.$method(value);
|
|
5
|
+
}
|
|
6
|
+
};
|
|
7
|
+
(set_if_some_ref, $builder:expr, $option:expr, $method:ident) => {
|
|
8
|
+
if let Some(value) = $option.take() {
|
|
9
|
+
$builder = $builder.$method(&value);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
(set_if_some_inner, $builder:expr, $option:expr, $method:ident) => {
|
|
13
|
+
if let Some(value) = $option.take() {
|
|
14
|
+
$builder = $builder.$method(value.0);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
(set_if_some_map, $builder:expr, $option:expr, $method:ident, $transform:expr) => {
|
|
18
|
+
if let Some(value) = $option.take() {
|
|
19
|
+
$builder = $builder.$method($transform(value));
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
(set_if_some_map_ref, $builder:expr, $option:expr, $method:ident, $transform:expr) => {
|
|
23
|
+
if let Some(value) = $option.take() {
|
|
24
|
+
$builder = $builder.$method($transform(&value));
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
(set_if_true, $builder:expr, $option:expr, $method:ident, $default:expr) => {
|
|
28
|
+
if $option.unwrap_or($default) {
|
|
29
|
+
$builder = $builder.$method();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
(set_if_true_with, $builder:expr, $option:expr, $method:ident, $default:expr, $value:expr) => {
|
|
33
|
+
if $option.unwrap_or($default) {
|
|
34
|
+
$builder = $builder.$method($value);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
macro_rules! define_ruby_enum {
|
|
40
|
+
($(#[$meta:meta])* $enum_type:ident, $ruby_class:expr, $ffi_type:ty, $($variant:ident),* $(,)?) => {
|
|
41
|
+
define_ruby_enum!($(#[$meta])* $enum_type, $ruby_class, $ffi_type, $( ($variant, $variant) ),*);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
($(#[$meta:meta])* const, $enum_type:ident, $ruby_class:expr, $ffi_type:ty, $($variant:ident),* $(,)?) => {
|
|
45
|
+
define_ruby_enum!($(#[$meta])* const, $enum_type, $ruby_class, $ffi_type, $( ($variant, $variant) ),*);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
($(#[$meta:meta])* $enum_type:ident, $ruby_class:expr, $ffi_type:ty, $(($rust_variant:ident, $ffi_variant:ident)),* $(,)?) => {
|
|
49
|
+
$(#[$meta])*
|
|
50
|
+
#[magnus::wrap(class = $ruby_class, free_immediately, size)]
|
|
51
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
52
|
+
#[allow(non_camel_case_types)]
|
|
53
|
+
#[allow(clippy::upper_case_acronyms)]
|
|
54
|
+
pub enum $enum_type {
|
|
55
|
+
$($rust_variant),*
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
impl $enum_type {
|
|
59
|
+
pub fn into_ffi(self) -> $ffi_type {
|
|
60
|
+
match self {
|
|
61
|
+
$(<$enum_type>::$rust_variant => <$ffi_type>::$ffi_variant,)*
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#[allow(dead_code)]
|
|
66
|
+
pub fn from_ffi(ffi: $ffi_type) -> Self {
|
|
67
|
+
#[allow(unreachable_patterns)]
|
|
68
|
+
match ffi {
|
|
69
|
+
$(<$ffi_type>::$ffi_variant => <$enum_type>::$rust_variant,)*
|
|
70
|
+
_ => unreachable!(),
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
($(#[$meta:meta])* const, $enum_type:ident, $ruby_class:expr, $ffi_type:ty, $(($rust_variant:ident, $ffi_variant:ident)),* $(,)?) => {
|
|
77
|
+
$(#[$meta])*
|
|
78
|
+
#[magnus::wrap(class = $ruby_class, free_immediately, size)]
|
|
79
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
80
|
+
#[allow(non_camel_case_types)]
|
|
81
|
+
#[allow(clippy::upper_case_acronyms)]
|
|
82
|
+
pub enum $enum_type {
|
|
83
|
+
$($rust_variant),*
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
impl $enum_type {
|
|
87
|
+
pub const fn into_ffi(self) -> $ffi_type {
|
|
88
|
+
match self {
|
|
89
|
+
$(<$enum_type>::$rust_variant => <$ffi_type>::$ffi_variant,)*
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#[allow(dead_code)]
|
|
94
|
+
pub const fn from_ffi(ffi: $ffi_type) -> Self {
|
|
95
|
+
#[allow(unreachable_patterns)]
|
|
96
|
+
match ffi {
|
|
97
|
+
$(<$ffi_type>::$ffi_variant => <$enum_type>::$rust_variant,)*
|
|
98
|
+
_ => unreachable!(),
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
macro_rules! ruby {
|
|
106
|
+
() => {
|
|
107
|
+
magnus::Ruby::get().expect("Failed to get Ruby VM instance")
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
macro_rules! extract_request {
|
|
112
|
+
($args:expr, $required:ty) => {{
|
|
113
|
+
let args = magnus::scan_args::scan_args::<$required, (), (), (), magnus::RHash, ()>($args)?;
|
|
114
|
+
let required = args.required;
|
|
115
|
+
let request = crate::client::req::Request::new(&ruby!(), args.keywords)?;
|
|
116
|
+
(required, request)
|
|
117
|
+
}};
|
|
118
|
+
}
|