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
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
unless defined?(Wreq)
|
|
4
|
+
module Wreq
|
|
5
|
+
# HTTP response object containing status, headers, and body.
|
|
6
|
+
#
|
|
7
|
+
# This class wraps a native Rust implementation providing efficient
|
|
8
|
+
# access to HTTP response data including status codes, headers, body
|
|
9
|
+
# content, and streaming capabilities.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic response handling
|
|
12
|
+
# response = client.get("https://api.example.com")
|
|
13
|
+
# puts response.status.as_int # => 200
|
|
14
|
+
# puts response.text
|
|
15
|
+
#
|
|
16
|
+
# @example JSON response
|
|
17
|
+
# response = client.get("https://api.example.com/data")
|
|
18
|
+
# data = response.json
|
|
19
|
+
#
|
|
20
|
+
# @example Streaming response
|
|
21
|
+
# response = client.get("https://example.com/large-file")
|
|
22
|
+
# response.stream.each do |chunk|
|
|
23
|
+
# # Process chunk
|
|
24
|
+
# end
|
|
25
|
+
class Response
|
|
26
|
+
# Get the HTTP status code as an integer.
|
|
27
|
+
#
|
|
28
|
+
# @return [Integer] Status code (e.g., 200, 404, 500)
|
|
29
|
+
# @example
|
|
30
|
+
# response.code # => 200
|
|
31
|
+
def code
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Get the HTTP status code object.
|
|
35
|
+
#
|
|
36
|
+
# @return [Wreq::StatusCode] Status code wrapper with helper methods
|
|
37
|
+
# @example
|
|
38
|
+
# status = response.status
|
|
39
|
+
# status.success? # => true
|
|
40
|
+
def status
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Get the HTTP protocol version used.
|
|
44
|
+
#
|
|
45
|
+
# @return [Wreq::Version] HTTP version (HTTP/1.1, HTTP/2, etc.)
|
|
46
|
+
# @example
|
|
47
|
+
# response.version # => Wreq::Version::HTTP_11
|
|
48
|
+
def version
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Get the final URL after redirects.
|
|
52
|
+
#
|
|
53
|
+
# @return [String] The final URL
|
|
54
|
+
# @example
|
|
55
|
+
# response.url # => "https://example.com/final-page"
|
|
56
|
+
def url
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get the content length if known.
|
|
60
|
+
#
|
|
61
|
+
# @return [Integer, nil] Content length in bytes, or nil if unknown
|
|
62
|
+
# @example
|
|
63
|
+
# response.content_length # => 1024
|
|
64
|
+
def content_length
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Get the local socket address.
|
|
68
|
+
#
|
|
69
|
+
# @return [String, nil] Local address (e.g., "127.0.0.1:54321"), or nil
|
|
70
|
+
# @example
|
|
71
|
+
# response.local_addr # => "192.168.1.100:54321"
|
|
72
|
+
def local_addr
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Get the remote socket address.
|
|
76
|
+
#
|
|
77
|
+
# @return [String, nil] Remote address (e.g., "93.184.216.34:443"), or nil
|
|
78
|
+
# @example
|
|
79
|
+
# response.remote_addr # => "93.184.216.34:443"
|
|
80
|
+
def remote_addr
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Get the response bytes as a binary string.
|
|
84
|
+
# @return [String] Response body as binary data
|
|
85
|
+
# @example
|
|
86
|
+
# binary_data = response.bytes
|
|
87
|
+
# puts binary_data.size # => 1024
|
|
88
|
+
def bytes
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Get the response body as text.
|
|
92
|
+
#
|
|
93
|
+
# @return [String] Response body decoded as UTF-8 text
|
|
94
|
+
# @example
|
|
95
|
+
# html = response.text
|
|
96
|
+
# puts html
|
|
97
|
+
# @raise [Wreq::DecodingError] if body cannot be decoded as binary
|
|
98
|
+
def text
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Get the response body as text with a specific charset.
|
|
102
|
+
# This method allows you to specify a default encoding
|
|
103
|
+
# to use when decoding the response body.
|
|
104
|
+
# # @param default_encoding [String] Default encoding to use (e.g., "UTF-8")
|
|
105
|
+
# # @return [String] Response body decoded as text using the specified encoding
|
|
106
|
+
# @example
|
|
107
|
+
# html = response.text_with_charset("ISO-8859-1")
|
|
108
|
+
# puts html
|
|
109
|
+
# @raise [Wreq::DecodingError] if body cannot be decoded with the specified encoding
|
|
110
|
+
def text_with_charset(default_encoding)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Parse the response body as JSON.
|
|
114
|
+
#
|
|
115
|
+
# @return [Object] Parsed JSON (Hash, Array, String, Integer, Float, Boolean, nil)
|
|
116
|
+
# @raise [Wreq::DecodingError] if body is not valid JSON
|
|
117
|
+
# @example
|
|
118
|
+
# data = response.json
|
|
119
|
+
# puts data["key"]
|
|
120
|
+
def json
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Get a streaming iterator for the response body, yielding each chunk.
|
|
124
|
+
#
|
|
125
|
+
# This method allows you to process large HTTP responses efficiently,
|
|
126
|
+
# by yielding each chunk of the body as it arrives, without loading
|
|
127
|
+
# the entire response into memory.
|
|
128
|
+
#
|
|
129
|
+
# @return An iterator over response body chunks (binary String)
|
|
130
|
+
# @yield [chunk] Each chunk of the response body as a binary String
|
|
131
|
+
# @example Save response to file
|
|
132
|
+
# File.open("output.bin", "wb") do |f|
|
|
133
|
+
# response.chunks { |chunk| f.write(chunk) }
|
|
134
|
+
# end
|
|
135
|
+
# @example Count total bytes streamed
|
|
136
|
+
# total = 0
|
|
137
|
+
# response.chunks { |chunk| total += chunk.bytesize }
|
|
138
|
+
# puts "Downloaded #{total} bytes"
|
|
139
|
+
#
|
|
140
|
+
# Note: The returned Receiver is only for reading response bodies, not for uploads.
|
|
141
|
+
def chunks
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Close the response and free associated resources.
|
|
145
|
+
#
|
|
146
|
+
# @return [void]
|
|
147
|
+
# @example
|
|
148
|
+
# response.close
|
|
149
|
+
def close
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# ======================== Ruby API Extensions ========================
|
|
156
|
+
|
|
157
|
+
module Wreq
|
|
158
|
+
class Response
|
|
159
|
+
# Returns a compact string representation of the response.
|
|
160
|
+
#
|
|
161
|
+
# Format: #<Wreq::Response STATUS content-type="..." body=SIZE>
|
|
162
|
+
#
|
|
163
|
+
# @return [String] Compact formatted response information
|
|
164
|
+
# @example
|
|
165
|
+
# puts response.to_s
|
|
166
|
+
# # => #<Wreq::Response 200 content-type="application/json" body=456B>
|
|
167
|
+
def to_s
|
|
168
|
+
parts = ["#<Wreq::Response"]
|
|
169
|
+
|
|
170
|
+
# Status code
|
|
171
|
+
parts << code.to_s
|
|
172
|
+
|
|
173
|
+
# Content-Type header if present
|
|
174
|
+
if headers.respond_to?(:get)
|
|
175
|
+
content_type = headers.get("content-type")
|
|
176
|
+
parts << "content-type=#{content_type.inspect}" if content_type
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Body size
|
|
180
|
+
if content_length
|
|
181
|
+
parts << "body=#{format_bytes(content_length)}"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
parts.join(" ") + ">"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
|
|
189
|
+
def format_bytes(bytes)
|
|
190
|
+
return "0B" if bytes.zero?
|
|
191
|
+
|
|
192
|
+
units = ["B", "KB", "MB", "GB"]
|
|
193
|
+
size = bytes.to_f
|
|
194
|
+
unit_index = 0
|
|
195
|
+
|
|
196
|
+
while size >= 1024 && unit_index < units.length - 1
|
|
197
|
+
size /= 1024.0
|
|
198
|
+
unit_index += 1
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
if unit_index == 0
|
|
202
|
+
"#{size.to_i}#{units[unit_index]}"
|
|
203
|
+
else
|
|
204
|
+
"#{size.round(1)}#{units[unit_index]}"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Build a platform-specific gem with pre-compiled native extensions.
|
|
5
|
+
#
|
|
6
|
+
# Usage: ruby script/build_platform_gem.rb PLATFORM
|
|
7
|
+
# Example: ruby script/build_platform_gem.rb arm64-darwin
|
|
8
|
+
#
|
|
9
|
+
# Expects compiled .bundle/.so files in version-specific directories:
|
|
10
|
+
# lib/wreq_ruby/3.3/wreq_ruby.bundle
|
|
11
|
+
# lib/wreq_ruby/3.4/wreq_ruby.bundle
|
|
12
|
+
# lib/wreq_ruby/4.0/wreq_ruby.bundle
|
|
13
|
+
|
|
14
|
+
require "rubygems/package"
|
|
15
|
+
require "fileutils"
|
|
16
|
+
|
|
17
|
+
platform = ARGV.fetch(0) { abort "Usage: #{$0} PLATFORM" }
|
|
18
|
+
|
|
19
|
+
spec = Gem::Specification.load("wreq.gemspec")
|
|
20
|
+
spec.platform = Gem::Platform.new(platform)
|
|
21
|
+
spec.extensions = []
|
|
22
|
+
# Keep in sync with Rakefile cross_compiling block
|
|
23
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.3", "< 4.1.dev")
|
|
24
|
+
|
|
25
|
+
# Add version-specific compiled extensions
|
|
26
|
+
binaries = Dir.glob("lib/wreq_ruby/[0-9]*/*.{bundle,so}")
|
|
27
|
+
abort "No compiled binaries found in lib/wreq_ruby/*/. Did compilation succeed?" if binaries.empty?
|
|
28
|
+
spec.files += binaries
|
|
29
|
+
|
|
30
|
+
FileUtils.mkdir_p("pkg")
|
|
31
|
+
gem_file = Gem::Package.build(spec)
|
|
32
|
+
FileUtils.mv(gem_file, "pkg/")
|
|
33
|
+
|
|
34
|
+
puts "Built: pkg/#{File.basename(gem_file)}"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
use indexmap::IndexMap;
|
|
2
|
+
use serde::{Deserialize, Serialize};
|
|
3
|
+
|
|
4
|
+
/// Represents a JSON value for HTTP requests.
|
|
5
|
+
/// Supports objects, arrays, numbers, strings, booleans, and null.
|
|
6
|
+
#[derive(Serialize, Deserialize)]
|
|
7
|
+
#[serde(untagged)]
|
|
8
|
+
pub enum Json {
|
|
9
|
+
Object(IndexMap<String, Json>),
|
|
10
|
+
Boolean(bool),
|
|
11
|
+
Number(isize),
|
|
12
|
+
Float(f64),
|
|
13
|
+
String(String),
|
|
14
|
+
Null(Option<isize>),
|
|
15
|
+
Array(Vec<Json>),
|
|
16
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
pin::Pin,
|
|
3
|
+
sync::RwLock,
|
|
4
|
+
task::{Context, Poll},
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
use bytes::Bytes;
|
|
8
|
+
use futures_util::{Stream, StreamExt, TryFutureExt};
|
|
9
|
+
use magnus::{Error, RString, TryConvert, Value};
|
|
10
|
+
use tokio::sync::{
|
|
11
|
+
Mutex,
|
|
12
|
+
mpsc::{self},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
use crate::{
|
|
16
|
+
error::{memory_error, mpsc_send_error_to_magnus},
|
|
17
|
+
rt,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/// A receiver for streaming HTTP response bodies.
|
|
21
|
+
pub struct BodyReceiver(Mutex<Pin<Box<dyn Stream<Item = wreq::Result<Bytes>> + Send>>>);
|
|
22
|
+
|
|
23
|
+
/// A sender for streaming HTTP request bodies.
|
|
24
|
+
#[magnus::wrap(class = "Wreq::BodySender", free_immediately, size)]
|
|
25
|
+
pub struct BodySender(RwLock<InnerBodySender>);
|
|
26
|
+
|
|
27
|
+
struct InnerBodySender {
|
|
28
|
+
tx: Option<mpsc::Sender<Bytes>>,
|
|
29
|
+
rx: Option<mpsc::Receiver<Bytes>>,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ===== impl BodyReceiver =====
|
|
33
|
+
|
|
34
|
+
impl BodyReceiver {
|
|
35
|
+
/// Create a new [`BodyReceiver`] instance.
|
|
36
|
+
#[inline]
|
|
37
|
+
pub fn new(stream: impl Stream<Item = wreq::Result<Bytes>> + Send + 'static) -> BodyReceiver {
|
|
38
|
+
BodyReceiver(Mutex::new(Box::pin(stream)))
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
impl Iterator for BodyReceiver {
|
|
43
|
+
type Item = Bytes;
|
|
44
|
+
|
|
45
|
+
fn next(&mut self) -> Option<Self::Item> {
|
|
46
|
+
rt::maybe_block_on(async {
|
|
47
|
+
self.0
|
|
48
|
+
.lock()
|
|
49
|
+
.await
|
|
50
|
+
.as_mut()
|
|
51
|
+
.next()
|
|
52
|
+
.await
|
|
53
|
+
.and_then(|r| r.ok())
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ===== impl BodySender =====
|
|
59
|
+
|
|
60
|
+
impl BodySender {
|
|
61
|
+
/// Ruby: `Wreq::Sender.new(capacity = 8)`
|
|
62
|
+
pub fn new(args: &[Value]) -> Self {
|
|
63
|
+
let capacity: usize = if let Some(v) = args.first() {
|
|
64
|
+
usize::try_convert(*v).unwrap_or(8)
|
|
65
|
+
} else {
|
|
66
|
+
8
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
let (tx, rx) = mpsc::channel(capacity);
|
|
70
|
+
BodySender(RwLock::new(InnerBodySender {
|
|
71
|
+
tx: Some(tx),
|
|
72
|
+
rx: Some(rx),
|
|
73
|
+
}))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Ruby: `push(data)` where data is String or bytes
|
|
77
|
+
pub fn push(rb_self: &Self, data: RString) -> Result<(), Error> {
|
|
78
|
+
let bytes = data.to_bytes();
|
|
79
|
+
let inner = rb_self.0.read().unwrap();
|
|
80
|
+
if let Some(ref tx) = inner.tx {
|
|
81
|
+
rt::try_block_on(tx.send(bytes).map_err(mpsc_send_error_to_magnus))?;
|
|
82
|
+
}
|
|
83
|
+
Ok(())
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Ruby: `close` to close the sender
|
|
87
|
+
pub fn close(&self) {
|
|
88
|
+
let mut inner = self.0.write().unwrap();
|
|
89
|
+
inner.tx.take();
|
|
90
|
+
inner.rx.take();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
impl TryFrom<&BodySender> for ReceiverStream<Bytes> {
|
|
95
|
+
type Error = magnus::Error;
|
|
96
|
+
|
|
97
|
+
fn try_from(sender: &BodySender) -> Result<Self, Self::Error> {
|
|
98
|
+
sender
|
|
99
|
+
.0
|
|
100
|
+
.write()
|
|
101
|
+
.unwrap()
|
|
102
|
+
.rx
|
|
103
|
+
.take()
|
|
104
|
+
.map(ReceiverStream::new)
|
|
105
|
+
.ok_or_else(memory_error)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// A wrapper around [`tokio::sync::mpsc::Receiver`] that implements [`Stream`].
|
|
110
|
+
pub struct ReceiverStream<T> {
|
|
111
|
+
inner: mpsc::Receiver<T>,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
impl<T> ReceiverStream<T> {
|
|
115
|
+
/// Create a new [`ReceiverStream`].
|
|
116
|
+
#[inline]
|
|
117
|
+
pub fn new(recv: mpsc::Receiver<T>) -> Self {
|
|
118
|
+
Self { inner: recv }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
impl<T> Stream for ReceiverStream<T> {
|
|
123
|
+
type Item = T;
|
|
124
|
+
|
|
125
|
+
#[inline]
|
|
126
|
+
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
|
127
|
+
self.inner.poll_recv(cx)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// Returns the bounds of the stream based on the underlying receiver.
|
|
131
|
+
///
|
|
132
|
+
/// For open channels, it returns `(receiver.len(), None)`.
|
|
133
|
+
///
|
|
134
|
+
/// For closed channels, it returns `(receiver.len(), Some(used_capacity))`
|
|
135
|
+
/// where `used_capacity` is calculated as `receiver.max_capacity() -
|
|
136
|
+
/// receiver.capacity()`. This accounts for any [`Permit`] that is still
|
|
137
|
+
/// able to send a message.
|
|
138
|
+
///
|
|
139
|
+
/// [`Permit`]: struct@tokio::sync::mpsc::Permit
|
|
140
|
+
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
141
|
+
if self.inner.is_closed() {
|
|
142
|
+
let used_capacity = self.inner.max_capacity() - self.inner.capacity();
|
|
143
|
+
(self.inner.len(), Some(used_capacity))
|
|
144
|
+
} else {
|
|
145
|
+
(self.inner.len(), None)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
data/src/client/body.rs
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
mod form;
|
|
2
|
+
mod json;
|
|
3
|
+
mod stream;
|
|
4
|
+
|
|
5
|
+
use bytes::Bytes;
|
|
6
|
+
use futures_util::StreamExt;
|
|
7
|
+
use magnus::{
|
|
8
|
+
Error, Module, Object, RModule, RString, Ruby, TryConvert, Value, function, method,
|
|
9
|
+
typed_data::Obj,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
pub use self::{
|
|
13
|
+
form::Form,
|
|
14
|
+
json::Json,
|
|
15
|
+
stream::{BodyReceiver, BodySender, ReceiverStream},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/// Represents the body of an HTTP request.
|
|
19
|
+
/// Supports text, bytes, and streaming bodies (Proc/Enumerator).
|
|
20
|
+
pub enum Body {
|
|
21
|
+
/// Static bytes body
|
|
22
|
+
Bytes(Bytes),
|
|
23
|
+
/// Streaming body
|
|
24
|
+
Stream(ReceiverStream<Bytes>),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
impl TryConvert for Body {
|
|
28
|
+
fn try_convert(val: Value) -> Result<Self, Error> {
|
|
29
|
+
if let Ok(s) = RString::try_convert(val) {
|
|
30
|
+
return Ok(Body::Bytes(s.to_bytes()));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let obj = Obj::<BodySender>::try_convert(val)?;
|
|
34
|
+
let stream = ReceiverStream::try_from(&*obj)?;
|
|
35
|
+
Ok(Body::Stream(stream))
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
impl From<Body> for wreq::Body {
|
|
40
|
+
fn from(body: Body) -> Self {
|
|
41
|
+
match body {
|
|
42
|
+
Body::Bytes(b) => wreq::Body::from(b),
|
|
43
|
+
Body::Stream(stream) => {
|
|
44
|
+
let try_stream = stream.map(Ok::<Bytes, std::io::Error>);
|
|
45
|
+
wreq::Body::wrap_stream(try_stream)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
|
|
52
|
+
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))?;
|
|
56
|
+
Ok(())
|
|
57
|
+
}
|
data/src/client/param.rs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
use indexmap::IndexMap;
|
|
2
|
+
use serde::{Deserialize, Serialize};
|
|
3
|
+
|
|
4
|
+
/// Represents HTTP parameters from Python as either a mapping or a sequence of key-value pairs.
|
|
5
|
+
pub type Params = IndexMap<String, ParamValue>;
|
|
6
|
+
|
|
7
|
+
/// Represents a single parameter value that can be automatically converted from Python types.
|
|
8
|
+
#[derive(Serialize, Deserialize)]
|
|
9
|
+
#[serde(untagged)]
|
|
10
|
+
pub enum ParamValue {
|
|
11
|
+
/// A boolean value from Python `bool`.
|
|
12
|
+
Boolean(bool),
|
|
13
|
+
/// An integer value from Python `int`.
|
|
14
|
+
Number(isize),
|
|
15
|
+
/// A floating-point value from Python `float`.
|
|
16
|
+
Float64(f64),
|
|
17
|
+
/// A string value from Python `str`.
|
|
18
|
+
String(String),
|
|
19
|
+
}
|
data/src/client/query.rs
ADDED