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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/Cargo.toml +54 -0
  3. data/Gemfile +17 -0
  4. data/LICENSE +201 -0
  5. data/README.md +150 -0
  6. data/Rakefile +90 -0
  7. data/build.rs +9 -0
  8. data/examples/body.rb +42 -0
  9. data/examples/client.rb +33 -0
  10. data/examples/emulation_request.rb +37 -0
  11. data/examples/headers.rb +27 -0
  12. data/examples/proxy.rb +113 -0
  13. data/examples/send_stream.rb +85 -0
  14. data/examples/stream.rb +14 -0
  15. data/examples/thread_interrupt.rb +83 -0
  16. data/extconf.rb +7 -0
  17. data/lib/wreq.rb +313 -0
  18. data/lib/wreq_ruby/body.rb +36 -0
  19. data/lib/wreq_ruby/client.rb +516 -0
  20. data/lib/wreq_ruby/cookie.rb +144 -0
  21. data/lib/wreq_ruby/emulation.rb +186 -0
  22. data/lib/wreq_ruby/error.rb +159 -0
  23. data/lib/wreq_ruby/header.rb +197 -0
  24. data/lib/wreq_ruby/http.rb +132 -0
  25. data/lib/wreq_ruby/response.rb +208 -0
  26. data/script/build_platform_gem.rb +34 -0
  27. data/src/client/body/form.rs +2 -0
  28. data/src/client/body/json.rs +16 -0
  29. data/src/client/body/stream.rs +148 -0
  30. data/src/client/body.rs +57 -0
  31. data/src/client/param.rs +19 -0
  32. data/src/client/query.rs +2 -0
  33. data/src/client/req.rs +251 -0
  34. data/src/client/resp.rs +250 -0
  35. data/src/client.rs +392 -0
  36. data/src/cookie.rs +277 -0
  37. data/src/emulation.rs +317 -0
  38. data/src/error.rs +147 -0
  39. data/src/extractor.rs +199 -0
  40. data/src/gvl.rs +154 -0
  41. data/src/header.rs +177 -0
  42. data/src/http.rs +127 -0
  43. data/src/lib.rs +97 -0
  44. data/src/macros.rs +118 -0
  45. data/src/rt.rs +47 -0
  46. data/test/client_cookie_test.rb +46 -0
  47. data/test/client_test.rb +136 -0
  48. data/test/cookie_test.rb +166 -0
  49. data/test/emulation_test.rb +21 -0
  50. data/test/error_handling_test.rb +89 -0
  51. data/test/header_test.rb +290 -0
  52. data/test/module_methods_test.rb +75 -0
  53. data/test/request_parameters_test.rb +175 -0
  54. data/test/request_test.rb +234 -0
  55. data/test/response_test.rb +69 -0
  56. data/test/stream_test.rb +81 -0
  57. data/test/test_helper.rb +9 -0
  58. data/wreq.gemspec +68 -0
  59. metadata +112 -0
data/src/emulation.rs ADDED
@@ -0,0 +1,317 @@
1
+ use magnus::{
2
+ Error, Module, Object, RHash, RModule, Ruby, TryConvert, Value, function, method,
3
+ typed_data::{Inspect, Obj},
4
+ };
5
+
6
+ define_ruby_enum!(
7
+ /// An emulation.
8
+ const,
9
+ EmulationDevice,
10
+ "Wreq::EmulationDevice",
11
+ wreq_util::Emulation,
12
+ Chrome100,
13
+ Chrome101,
14
+ Chrome104,
15
+ Chrome105,
16
+ Chrome106,
17
+ Chrome107,
18
+ Chrome108,
19
+ Chrome109,
20
+ Chrome110,
21
+ Chrome114,
22
+ Chrome116,
23
+ Chrome117,
24
+ Chrome118,
25
+ Chrome119,
26
+ Chrome120,
27
+ Chrome123,
28
+ Chrome124,
29
+ Chrome126,
30
+ Chrome127,
31
+ Chrome128,
32
+ Chrome129,
33
+ Chrome130,
34
+ Chrome131,
35
+ Chrome132,
36
+ Chrome133,
37
+ Chrome134,
38
+ Chrome135,
39
+ Chrome136,
40
+ Chrome137,
41
+ Chrome138,
42
+ Chrome139,
43
+ Chrome140,
44
+ Chrome141,
45
+ Chrome142,
46
+ Chrome143,
47
+ Chrome144,
48
+ Chrome145,
49
+ Edge101,
50
+ Edge122,
51
+ Edge127,
52
+ Edge131,
53
+ Edge134,
54
+ Edge135,
55
+ Edge136,
56
+ Edge137,
57
+ Edge138,
58
+ Edge139,
59
+ Edge140,
60
+ Edge141,
61
+ Edge142,
62
+ Edge143,
63
+ Edge144,
64
+ Edge145,
65
+ Firefox109,
66
+ Firefox117,
67
+ Firefox128,
68
+ Firefox133,
69
+ Firefox135,
70
+ FirefoxPrivate135,
71
+ FirefoxAndroid135,
72
+ Firefox136,
73
+ FirefoxPrivate136,
74
+ Firefox139,
75
+ Firefox142,
76
+ Firefox143,
77
+ Firefox144,
78
+ Firefox145,
79
+ Firefox146,
80
+ Firefox147,
81
+ SafariIos17_2,
82
+ SafariIos17_4_1,
83
+ SafariIos16_5,
84
+ Safari15_3,
85
+ Safari15_5,
86
+ Safari15_6_1,
87
+ Safari16,
88
+ Safari16_5,
89
+ Safari17_0,
90
+ Safari17_2_1,
91
+ Safari17_4_1,
92
+ Safari17_5,
93
+ Safari17_6,
94
+ Safari18,
95
+ SafariIPad18,
96
+ Safari18_2,
97
+ Safari18_3,
98
+ Safari18_3_1,
99
+ SafariIos18_1_1,
100
+ Safari18_5,
101
+ Safari26,
102
+ Safari26_1,
103
+ Safari26_2,
104
+ SafariIos26,
105
+ SafariIos26_2,
106
+ SafariIPad26,
107
+ SafariIpad26_2,
108
+ OkHttp3_9,
109
+ OkHttp3_11,
110
+ OkHttp3_13,
111
+ OkHttp3_14,
112
+ OkHttp4_9,
113
+ OkHttp4_10,
114
+ OkHttp4_12,
115
+ OkHttp5,
116
+ Opera116,
117
+ Opera117,
118
+ Opera118,
119
+ Opera119
120
+ );
121
+
122
+ define_ruby_enum!(
123
+ /// An emulation operating system.
124
+ const,
125
+ EmulationOS,
126
+ "Wreq::EmulationOS",
127
+ wreq_util::EmulationOS,
128
+ Windows,
129
+ MacOS,
130
+ Linux,
131
+ Android,
132
+ IOS,
133
+ );
134
+
135
+ /// A struct to represent the `EmulationOption` class.
136
+ #[derive(Clone)]
137
+ #[magnus::wrap(class = "Wreq::Emulation", free_immediately, size)]
138
+ pub struct Emulation(pub wreq_util::EmulationOption);
139
+
140
+ // ===== impl EmulationDevice =====
141
+
142
+ impl EmulationDevice {
143
+ pub fn to_s(&self) -> String {
144
+ self.into_ffi().inspect()
145
+ }
146
+ }
147
+
148
+ // ===== impl EmulationOS =====
149
+
150
+ impl EmulationOS {
151
+ pub fn to_s(&self) -> String {
152
+ self.into_ffi().inspect()
153
+ }
154
+ }
155
+
156
+ // ===== impl Emulation =====
157
+
158
+ impl Emulation {
159
+ fn new(ruby: &Ruby, args: &[Value]) -> Result<Self, Error> {
160
+ let mut device = None;
161
+ let mut os = None;
162
+ let mut skip_http2 = None;
163
+ let mut skip_headers = None;
164
+
165
+ if let Some(hash) = args.first().and_then(|v| RHash::from_value(*v)) {
166
+ if let Some(v) = hash.get(ruby.to_symbol("device")) {
167
+ device = Some(Obj::<EmulationDevice>::try_convert(v)?);
168
+ }
169
+ if let Some(v) = hash.get(ruby.to_symbol("os")) {
170
+ os = Some(Obj::<EmulationOS>::try_convert(v)?);
171
+ }
172
+ if let Some(v) = hash.get(ruby.to_symbol("skip_http2")) {
173
+ skip_http2 = Some(bool::try_convert(v)?);
174
+ }
175
+ if let Some(v) = hash.get(ruby.to_symbol("skip_headers")) {
176
+ skip_headers = Some(bool::try_convert(v)?);
177
+ }
178
+ }
179
+
180
+ let emulation = wreq_util::EmulationOption::builder()
181
+ .emulation(device.map(|obj| obj.into_ffi()).unwrap_or_default())
182
+ .emulation_os(os.map(|os| os.into_ffi()).unwrap_or_default())
183
+ .skip_http2(skip_http2.unwrap_or(false))
184
+ .skip_headers(skip_headers.unwrap_or(false))
185
+ .build();
186
+
187
+ Ok(Self(emulation))
188
+ }
189
+ }
190
+
191
+ pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
192
+ // EmulationDevice enum binding
193
+ let emulation_class = gem_module.define_class("EmulationDevice", ruby.class_object())?;
194
+ emulation_class.define_method("to_s", method!(EmulationDevice::to_s, 0))?;
195
+ emulation_class.const_set("Chrome100", EmulationDevice::Chrome100)?;
196
+ emulation_class.const_set("Chrome101", EmulationDevice::Chrome101)?;
197
+ emulation_class.const_set("Chrome104", EmulationDevice::Chrome104)?;
198
+ emulation_class.const_set("Chrome105", EmulationDevice::Chrome105)?;
199
+ emulation_class.const_set("Chrome106", EmulationDevice::Chrome106)?;
200
+ emulation_class.const_set("Chrome107", EmulationDevice::Chrome107)?;
201
+ emulation_class.const_set("Chrome108", EmulationDevice::Chrome108)?;
202
+ emulation_class.const_set("Chrome109", EmulationDevice::Chrome109)?;
203
+ emulation_class.const_set("Chrome110", EmulationDevice::Chrome110)?;
204
+ emulation_class.const_set("Chrome114", EmulationDevice::Chrome114)?;
205
+ emulation_class.const_set("Chrome116", EmulationDevice::Chrome116)?;
206
+ emulation_class.const_set("Chrome117", EmulationDevice::Chrome117)?;
207
+ emulation_class.const_set("Chrome118", EmulationDevice::Chrome118)?;
208
+ emulation_class.const_set("Chrome119", EmulationDevice::Chrome119)?;
209
+ emulation_class.const_set("Chrome120", EmulationDevice::Chrome120)?;
210
+ emulation_class.const_set("Chrome123", EmulationDevice::Chrome123)?;
211
+ emulation_class.const_set("Chrome124", EmulationDevice::Chrome124)?;
212
+ emulation_class.const_set("Chrome126", EmulationDevice::Chrome126)?;
213
+ emulation_class.const_set("Chrome127", EmulationDevice::Chrome127)?;
214
+ emulation_class.const_set("Chrome128", EmulationDevice::Chrome128)?;
215
+ emulation_class.const_set("Chrome129", EmulationDevice::Chrome129)?;
216
+ emulation_class.const_set("Chrome130", EmulationDevice::Chrome130)?;
217
+ emulation_class.const_set("Chrome131", EmulationDevice::Chrome131)?;
218
+ emulation_class.const_set("Chrome132", EmulationDevice::Chrome132)?;
219
+ emulation_class.const_set("Chrome133", EmulationDevice::Chrome133)?;
220
+ emulation_class.const_set("Chrome134", EmulationDevice::Chrome134)?;
221
+ emulation_class.const_set("Chrome135", EmulationDevice::Chrome135)?;
222
+ emulation_class.const_set("Chrome136", EmulationDevice::Chrome136)?;
223
+ emulation_class.const_set("Chrome137", EmulationDevice::Chrome137)?;
224
+ emulation_class.const_set("Chrome138", EmulationDevice::Chrome138)?;
225
+ emulation_class.const_set("Chrome139", EmulationDevice::Chrome139)?;
226
+ emulation_class.const_set("Chrome140", EmulationDevice::Chrome140)?;
227
+ emulation_class.const_set("Chrome141", EmulationDevice::Chrome141)?;
228
+ emulation_class.const_set("Chrome142", EmulationDevice::Chrome142)?;
229
+ emulation_class.const_set("Chrome143", EmulationDevice::Chrome143)?;
230
+ emulation_class.const_set("Chrome144", EmulationDevice::Chrome144)?;
231
+ emulation_class.const_set("Chrome145", EmulationDevice::Chrome145)?;
232
+ emulation_class.const_set("Edge101", EmulationDevice::Edge101)?;
233
+ emulation_class.const_set("Edge122", EmulationDevice::Edge122)?;
234
+ emulation_class.const_set("Edge127", EmulationDevice::Edge127)?;
235
+ emulation_class.const_set("Edge131", EmulationDevice::Edge131)?;
236
+ emulation_class.const_set("Edge134", EmulationDevice::Edge134)?;
237
+ emulation_class.const_set("Edge135", EmulationDevice::Edge135)?;
238
+ emulation_class.const_set("Edge136", EmulationDevice::Edge136)?;
239
+ emulation_class.const_set("Edge137", EmulationDevice::Edge137)?;
240
+ emulation_class.const_set("Edge138", EmulationDevice::Edge138)?;
241
+ emulation_class.const_set("Edge139", EmulationDevice::Edge139)?;
242
+ emulation_class.const_set("Edge140", EmulationDevice::Edge140)?;
243
+ emulation_class.const_set("Edge141", EmulationDevice::Edge141)?;
244
+ emulation_class.const_set("Edge142", EmulationDevice::Edge142)?;
245
+ emulation_class.const_set("Edge143", EmulationDevice::Edge143)?;
246
+ emulation_class.const_set("Edge144", EmulationDevice::Edge144)?;
247
+ emulation_class.const_set("Edge145", EmulationDevice::Edge145)?;
248
+ emulation_class.const_set("Firefox109", EmulationDevice::Firefox109)?;
249
+ emulation_class.const_set("Firefox117", EmulationDevice::Firefox117)?;
250
+ emulation_class.const_set("Firefox128", EmulationDevice::Firefox128)?;
251
+ emulation_class.const_set("Firefox133", EmulationDevice::Firefox133)?;
252
+ emulation_class.const_set("Firefox135", EmulationDevice::Firefox135)?;
253
+ emulation_class.const_set("FirefoxPrivate135", EmulationDevice::FirefoxPrivate135)?;
254
+ emulation_class.const_set("FirefoxAndroid135", EmulationDevice::FirefoxAndroid135)?;
255
+ emulation_class.const_set("Firefox136", EmulationDevice::Firefox136)?;
256
+ emulation_class.const_set("FirefoxPrivate136", EmulationDevice::FirefoxPrivate136)?;
257
+ emulation_class.const_set("Firefox139", EmulationDevice::Firefox139)?;
258
+ emulation_class.const_set("Firefox142", EmulationDevice::Firefox142)?;
259
+ emulation_class.const_set("Firefox143", EmulationDevice::Firefox143)?;
260
+ emulation_class.const_set("Firefox144", EmulationDevice::Firefox144)?;
261
+ emulation_class.const_set("Firefox145", EmulationDevice::Firefox145)?;
262
+ emulation_class.const_set("Firefox146", EmulationDevice::Firefox146)?;
263
+ emulation_class.const_set("Firefox147", EmulationDevice::Firefox147)?;
264
+ emulation_class.const_set("SafariIos17_2", EmulationDevice::SafariIos17_2)?;
265
+ emulation_class.const_set("SafariIos17_4_1", EmulationDevice::SafariIos17_4_1)?;
266
+ emulation_class.const_set("SafariIos16_5", EmulationDevice::SafariIos16_5)?;
267
+ emulation_class.const_set("Safari15_3", EmulationDevice::Safari15_3)?;
268
+ emulation_class.const_set("Safari15_5", EmulationDevice::Safari15_5)?;
269
+ emulation_class.const_set("Safari15_6_1", EmulationDevice::Safari15_6_1)?;
270
+ emulation_class.const_set("Safari16", EmulationDevice::Safari16)?;
271
+ emulation_class.const_set("Safari16_5", EmulationDevice::Safari16_5)?;
272
+ emulation_class.const_set("Safari17_0", EmulationDevice::Safari17_0)?;
273
+ emulation_class.const_set("Safari17_2_1", EmulationDevice::Safari17_2_1)?;
274
+ emulation_class.const_set("Safari17_4_1", EmulationDevice::Safari17_4_1)?;
275
+ emulation_class.const_set("Safari17_5", EmulationDevice::Safari17_5)?;
276
+ emulation_class.const_set("Safari17_6", EmulationDevice::Safari17_6)?;
277
+ emulation_class.const_set("Safari18", EmulationDevice::Safari18)?;
278
+ emulation_class.const_set("SafariIPad18", EmulationDevice::SafariIPad18)?;
279
+ emulation_class.const_set("Safari18_2", EmulationDevice::Safari18_2)?;
280
+ emulation_class.const_set("Safari18_3", EmulationDevice::Safari18_3)?;
281
+ emulation_class.const_set("Safari18_3_1", EmulationDevice::Safari18_3_1)?;
282
+ emulation_class.const_set("SafariIos18_1_1", EmulationDevice::SafariIos18_1_1)?;
283
+ emulation_class.const_set("Safari18_5", EmulationDevice::Safari18_5)?;
284
+ emulation_class.const_set("Safari26", EmulationDevice::Safari26)?;
285
+ emulation_class.const_set("Safari26_1", EmulationDevice::Safari26_1)?;
286
+ emulation_class.const_set("Safari26_2", EmulationDevice::Safari26_2)?;
287
+ emulation_class.const_set("SafariIos26", EmulationDevice::SafariIos26)?;
288
+ emulation_class.const_set("SafariIos26_2", EmulationDevice::SafariIos26_2)?;
289
+ emulation_class.const_set("SafariIPad26", EmulationDevice::SafariIPad26)?;
290
+ emulation_class.const_set("SafariIpad26_2", EmulationDevice::SafariIpad26_2)?;
291
+ emulation_class.const_set("OkHttp3_9", EmulationDevice::OkHttp3_9)?;
292
+ emulation_class.const_set("OkHttp3_11", EmulationDevice::OkHttp3_11)?;
293
+ emulation_class.const_set("OkHttp3_13", EmulationDevice::OkHttp3_13)?;
294
+ emulation_class.const_set("OkHttp3_14", EmulationDevice::OkHttp3_14)?;
295
+ emulation_class.const_set("OkHttp4_9", EmulationDevice::OkHttp4_9)?;
296
+ emulation_class.const_set("OkHttp4_10", EmulationDevice::OkHttp4_10)?;
297
+ emulation_class.const_set("OkHttp4_12", EmulationDevice::OkHttp4_12)?;
298
+ emulation_class.const_set("OkHttp5", EmulationDevice::OkHttp5)?;
299
+ emulation_class.const_set("Opera116", EmulationDevice::Opera116)?;
300
+ emulation_class.const_set("Opera117", EmulationDevice::Opera117)?;
301
+ emulation_class.const_set("Opera118", EmulationDevice::Opera118)?;
302
+ emulation_class.const_set("Opera119", EmulationDevice::Opera119)?;
303
+
304
+ // EmulationOS enum binding
305
+ let emulation_os_class = gem_module.define_class("EmulationOS", ruby.class_object())?;
306
+ emulation_os_class.define_method("to_s", method!(EmulationOS::to_s, 0))?;
307
+ emulation_os_class.const_set("Windows", EmulationOS::Windows)?;
308
+ emulation_os_class.const_set("MacOS", EmulationOS::MacOS)?;
309
+ emulation_os_class.const_set("Linux", EmulationOS::Linux)?;
310
+ emulation_os_class.const_set("Android", EmulationOS::Android)?;
311
+ emulation_os_class.const_set("IOS", EmulationOS::IOS)?;
312
+
313
+ // Emulation class binding
314
+ let emulation_option_class = gem_module.define_class("Emulation", ruby.class_object())?;
315
+ emulation_option_class.define_singleton_method("new", function!(Emulation::new, -1))?;
316
+ Ok(())
317
+ }
data/src/error.rs ADDED
@@ -0,0 +1,147 @@
1
+ use magnus::{
2
+ Error as MagnusError, RModule, Ruby, exception::ExceptionClass, prelude::*, value::Lazy,
3
+ };
4
+ use tokio::sync::mpsc::error::SendError;
5
+
6
+ const RACE_CONDITION_ERROR_MSG: &str = r#"Due to Rust's memory management with borrowing,
7
+ you cannot use certain instances multiple times as they may be consumed.
8
+
9
+ This error can occur in the following cases:
10
+ 1) You passed a non-clonable instance to a function that requires ownership.
11
+ 2) You attempted to use a method that consumes ownership more than once (e.g., reading a response body twice).
12
+ 3) You tried to reference an instance after it was borrowed.
13
+
14
+ Potential solutions:
15
+ 1) Avoid sharing instances; create a new instance each time you use it.
16
+ 2) Refrain from performing actions that consume ownership multiple times.
17
+ 3) Change the order of operations to reference the instance before borrowing it.
18
+ "#;
19
+
20
+ static WREQ: Lazy<RModule> = Lazy::new(|ruby| ruby.define_module(crate::RUBY_MODULE_NAME).unwrap());
21
+
22
+ macro_rules! define_exception {
23
+ ($name:ident, $ruby_name:literal, $parent_method:ident) => {
24
+ static $name: Lazy<ExceptionClass> = Lazy::new(|ruby| {
25
+ ruby.get_inner(&WREQ)
26
+ .define_error($ruby_name, ruby.$parent_method())
27
+ .unwrap()
28
+ });
29
+ };
30
+ }
31
+
32
+ macro_rules! map_wreq_error {
33
+ ($ruby:expr, $err:expr, $msg:expr, $($check_method:ident => $exception:ident),* $(,)?) => {
34
+ {
35
+ $(
36
+ if $err.$check_method() {
37
+ return MagnusError::new($ruby.get_inner(&$exception), $msg);
38
+ }
39
+ )*
40
+ MagnusError::new($ruby.exception_runtime_error(), $msg)
41
+ }
42
+ };
43
+ }
44
+
45
+ // System-level and runtime errors
46
+ define_exception!(MEMORY, "MemoryError", exception_runtime_error);
47
+
48
+ // Network connection errors
49
+ define_exception!(CONNECTION_ERROR, "ConnectionError", exception_runtime_error);
50
+ define_exception!(
51
+ PROXY_CONNECTION_ERROR,
52
+ "ProxyConnectionError",
53
+ exception_runtime_error
54
+ );
55
+ define_exception!(
56
+ CONNECTION_RESET_ERROR,
57
+ "ConnectionResetError",
58
+ exception_runtime_error
59
+ );
60
+ define_exception!(TLS_ERROR, "TlsError", exception_runtime_error);
61
+
62
+ // HTTP protocol and request/response errors
63
+ define_exception!(REQUEST_ERROR, "RequestError", exception_runtime_error);
64
+ define_exception!(STATUS_ERROR, "StatusError", exception_runtime_error);
65
+ define_exception!(REDIRECT_ERROR, "RedirectError", exception_runtime_error);
66
+ define_exception!(TIMEOUT_ERROR, "TimeoutError", exception_runtime_error);
67
+
68
+ // Data processing and encoding errors
69
+ define_exception!(BODY_ERROR, "BodyError", exception_runtime_error);
70
+ define_exception!(DECODING_ERROR, "DecodingError", exception_runtime_error);
71
+
72
+ // Configuration and builder errors
73
+ define_exception!(BUILDER_ERROR, "BuilderError", exception_runtime_error);
74
+
75
+ // Thread interruption error
76
+ define_exception!(INTERRUPT_ERROR, "InterruptError", exception_interrupt);
77
+
78
+ /// Memory error constant
79
+ pub fn memory_error() -> MagnusError {
80
+ MagnusError::new(ruby!().get_inner(&MEMORY), RACE_CONDITION_ERROR_MSG)
81
+ }
82
+
83
+ /// Thread interruption error (raised when Thread.kill cancels a request)
84
+ pub fn interrupt_error() -> MagnusError {
85
+ MagnusError::new(ruby!().get_inner(&INTERRUPT_ERROR), "request interrupted")
86
+ }
87
+
88
+ /// Map [`tokio::sync::mpsc::error::SendError`] to corresponding [`magnus::Error`]
89
+ pub fn mpsc_send_error_to_magnus<T>(err: SendError<T>) -> MagnusError {
90
+ MagnusError::new(
91
+ ruby!().get_inner(&BODY_ERROR),
92
+ format!("failed to send body chunk: {}", err),
93
+ )
94
+ }
95
+
96
+ /// Map [`wreq::header::InvalidHeaderName`] to corresponding [`magnus::Error`]
97
+ pub fn header_name_error_to_magnus(err: wreq::header::InvalidHeaderName) -> MagnusError {
98
+ MagnusError::new(
99
+ ruby!().get_inner(&BUILDER_ERROR),
100
+ format!("invalid header name: {err}"),
101
+ )
102
+ }
103
+
104
+ /// Map [`wreq::header::InvalidHeaderValue`] to corresponding [`magnus::Error`]
105
+ pub fn header_value_error_to_magnus(err: wreq::header::InvalidHeaderValue) -> MagnusError {
106
+ MagnusError::new(
107
+ ruby!().get_inner(&BUILDER_ERROR),
108
+ format!("invalid header value: {err}"),
109
+ )
110
+ }
111
+
112
+ /// Map [`wreq::Error`] to corresponding [`magnus::Error`]
113
+ pub fn wreq_error_to_magnus(err: wreq::Error) -> MagnusError {
114
+ let error_msg = err.to_string();
115
+ map_wreq_error!(
116
+ ruby!(),
117
+ err,
118
+ error_msg,
119
+ is_builder => BUILDER_ERROR,
120
+ is_body => BODY_ERROR,
121
+ is_tls => TLS_ERROR,
122
+ is_connection_reset => CONNECTION_RESET_ERROR,
123
+ is_connect => CONNECTION_ERROR,
124
+ is_proxy_connect => PROXY_CONNECTION_ERROR,
125
+ is_decode => DECODING_ERROR,
126
+ is_redirect => REDIRECT_ERROR,
127
+ is_timeout => TIMEOUT_ERROR,
128
+ is_status => STATUS_ERROR,
129
+ is_request => REQUEST_ERROR,
130
+ )
131
+ }
132
+
133
+ pub fn include(ruby: &Ruby) {
134
+ Lazy::force(&MEMORY, ruby);
135
+ Lazy::force(&CONNECTION_ERROR, ruby);
136
+ Lazy::force(&PROXY_CONNECTION_ERROR, ruby);
137
+ Lazy::force(&CONNECTION_RESET_ERROR, ruby);
138
+ Lazy::force(&TLS_ERROR, ruby);
139
+ Lazy::force(&REQUEST_ERROR, ruby);
140
+ Lazy::force(&STATUS_ERROR, ruby);
141
+ Lazy::force(&REDIRECT_ERROR, ruby);
142
+ Lazy::force(&TIMEOUT_ERROR, ruby);
143
+ Lazy::force(&BODY_ERROR, ruby);
144
+ Lazy::force(&DECODING_ERROR, ruby);
145
+ Lazy::force(&BUILDER_ERROR, ruby);
146
+ Lazy::force(&INTERRUPT_ERROR, ruby);
147
+ }
data/src/extractor.rs ADDED
@@ -0,0 +1,199 @@
1
+ use bytes::Bytes;
2
+ use magnus::{RArray, RHash, RString, Ruby, TryConvert, r_hash::ForEach};
3
+ use wreq::{
4
+ Proxy, Version,
5
+ header::{HeaderMap, HeaderName, HeaderValue, OrigHeaderMap},
6
+ };
7
+
8
+ use crate::error::{
9
+ header_name_error_to_magnus, header_value_error_to_magnus, wreq_error_to_magnus,
10
+ };
11
+
12
+ /// A trait that defines the parameter name for extraction.
13
+ pub trait ExtractorName {
14
+ /// The name of the parameter in the Ruby hash.
15
+ const NAME: &str;
16
+ }
17
+
18
+ /// A generic extractor for various types.
19
+ pub struct Extractor<T>(Option<T>)
20
+ where
21
+ T: ExtractorName;
22
+
23
+ impl<T> Extractor<T>
24
+ where
25
+ T: ExtractorName,
26
+ {
27
+ /// Consumes the extractor and returns the wrapped value.
28
+ ///
29
+ /// Returns `Some(T)` if a value was extracted, `None` otherwise.
30
+ #[inline]
31
+ pub fn into_inner(self) -> Option<T> {
32
+ self.0
33
+ }
34
+ }
35
+
36
+ // ===== impl Extractor<Version> =====
37
+
38
+ impl ExtractorName for Version {
39
+ const NAME: &str = "version";
40
+ }
41
+
42
+ impl TryConvert for Extractor<Version> {
43
+ fn try_convert(value: magnus::Value) -> Result<Self, magnus::Error> {
44
+ let keyword = RHash::try_convert(value)?;
45
+ if let Some(version_val) = keyword.get(Version::NAME) {
46
+ return <&crate::http::Version>::try_convert(version_val)
47
+ .cloned()
48
+ .map(crate::http::Version::into_ffi)
49
+ .map(Some)
50
+ .map(Extractor);
51
+ }
52
+
53
+ Ok(Extractor(None))
54
+ }
55
+ }
56
+
57
+ // ===== impl Extractor<HeaderValue> =====
58
+
59
+ impl ExtractorName for HeaderValue {
60
+ const NAME: &str = "user_agent";
61
+ }
62
+
63
+ impl TryConvert for Extractor<HeaderValue> {
64
+ fn try_convert(value: magnus::Value) -> Result<Self, magnus::Error> {
65
+ let ruby = Ruby::get_with(value);
66
+ let keyword = RHash::try_convert(value)?;
67
+
68
+ if let Some(ruby_value) = keyword
69
+ .get(ruby.to_symbol(HeaderValue::NAME))
70
+ .and_then(RString::from_value)
71
+ {
72
+ return HeaderValue::from_maybe_shared(ruby_value.to_bytes())
73
+ .map(Some)
74
+ .map(Extractor)
75
+ .map_err(header_value_error_to_magnus);
76
+ }
77
+
78
+ Ok(Extractor(None))
79
+ }
80
+ }
81
+
82
+ // ===== impl Extractor<HeaderMap> =====
83
+
84
+ impl ExtractorName for HeaderMap {
85
+ const NAME: &str = "headers";
86
+ }
87
+
88
+ impl TryConvert for Extractor<HeaderMap> {
89
+ fn try_convert(value: magnus::Value) -> Result<Self, magnus::Error> {
90
+ let ruby = Ruby::get_with(value);
91
+ let keyword = RHash::try_convert(value)?;
92
+ let mut headers = HeaderMap::new();
93
+
94
+ if let Some(hash) = keyword
95
+ .get(ruby.to_symbol(HeaderMap::NAME))
96
+ .and_then(RHash::from_value)
97
+ {
98
+ hash.foreach(|name: RString, value: RString| {
99
+ let name = HeaderName::from_bytes(&name.to_bytes())
100
+ .map_err(header_name_error_to_magnus)?;
101
+ let value = HeaderValue::from_maybe_shared(value.to_bytes())
102
+ .map_err(header_value_error_to_magnus)?;
103
+ headers.insert(name, value);
104
+
105
+ Ok(ForEach::Continue)
106
+ })?;
107
+
108
+ return Ok(Extractor(Some(headers)));
109
+ }
110
+
111
+ Ok(Extractor(None))
112
+ }
113
+ }
114
+
115
+ // ===== impl Extractor<OrigHeaderMap> =====
116
+
117
+ impl ExtractorName for OrigHeaderMap {
118
+ const NAME: &str = "orig_headers";
119
+ }
120
+
121
+ impl TryConvert for Extractor<OrigHeaderMap> {
122
+ fn try_convert(value: magnus::Value) -> Result<Self, magnus::Error> {
123
+ let ruby = Ruby::get_with(value);
124
+ let keyword = RHash::try_convert(value)?;
125
+
126
+ if let Some(orig_headers) = keyword
127
+ .get(ruby.to_symbol(OrigHeaderMap::NAME))
128
+ .and_then(RArray::from_value)
129
+ {
130
+ let mut map = OrigHeaderMap::new();
131
+ for value in orig_headers.into_iter().flat_map(RString::from_value) {
132
+ map.insert(value.to_bytes());
133
+ }
134
+ return Ok(Extractor(Some(map)));
135
+ }
136
+
137
+ Ok(Extractor(None))
138
+ }
139
+ }
140
+
141
+ // ===== impl Extractor<Vec<HeaderValue>> =====
142
+
143
+ impl ExtractorName for Vec<HeaderValue> {
144
+ const NAME: &str = "cookies";
145
+ }
146
+
147
+ impl TryConvert for Extractor<Vec<HeaderValue>> {
148
+ fn try_convert(value: magnus::Value) -> Result<Self, magnus::Error> {
149
+ use percent_encoding::{NON_ALPHANUMERIC, percent_encode};
150
+
151
+ let ruby = Ruby::get_with(value);
152
+ let keyword = RHash::try_convert(value)?;
153
+
154
+ if let Some(hash) = keyword
155
+ .get(ruby.to_symbol(Vec::<HeaderValue>::NAME))
156
+ .and_then(RHash::from_value)
157
+ {
158
+ let mut cookies = Vec::new();
159
+ hash.foreach(|name: RString, value: RString| {
160
+ let value = value.to_bytes();
161
+ let encoded_value = percent_encode(&value, NON_ALPHANUMERIC);
162
+ let cookie = format!("{name}={encoded_value}");
163
+ let header_value = HeaderValue::from_maybe_shared(Bytes::from(cookie))
164
+ .map_err(header_value_error_to_magnus)?;
165
+ cookies.push(header_value);
166
+ Ok(ForEach::Continue)
167
+ })?;
168
+
169
+ return Ok(Extractor(Some(cookies)));
170
+ }
171
+
172
+ Ok(Extractor(None))
173
+ }
174
+ }
175
+
176
+ // ===== impl Extractor<Proxy> =====
177
+
178
+ impl ExtractorName for Proxy {
179
+ const NAME: &str = "proxy";
180
+ }
181
+
182
+ impl TryConvert for Extractor<Proxy> {
183
+ fn try_convert(value: magnus::Value) -> Result<Self, magnus::Error> {
184
+ let ruby = Ruby::get_with(value);
185
+ let keyword = RHash::try_convert(value)?;
186
+
187
+ if let Some(proxy) = keyword
188
+ .get(ruby.to_symbol(Proxy::NAME))
189
+ .and_then(RString::from_value)
190
+ {
191
+ return Proxy::all(proxy.to_bytes().as_ref())
192
+ .map(Some)
193
+ .map(Extractor)
194
+ .map_err(wreq_error_to_magnus);
195
+ }
196
+
197
+ Ok(Extractor(None))
198
+ }
199
+ }