wreq 1.2.3-x64-mingw-ucrt

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 (69) hide show
  1. checksums.yaml +7 -0
  2. data/Cargo.lock +1687 -0
  3. data/Cargo.toml +54 -0
  4. data/Gemfile +18 -0
  5. data/LICENSE +201 -0
  6. data/README.md +153 -0
  7. data/Rakefile +90 -0
  8. data/build.rs +16 -0
  9. data/docs/windows-gnu-tokio-crash.md +70 -0
  10. data/examples/body.rb +42 -0
  11. data/examples/client.rb +33 -0
  12. data/examples/cookie.rb +24 -0
  13. data/examples/emulate_request.rb +37 -0
  14. data/examples/headers.rb +27 -0
  15. data/examples/proxy.rb +113 -0
  16. data/examples/send_stream.rb +85 -0
  17. data/examples/stream.rb +14 -0
  18. data/examples/thread_interrupt.rb +83 -0
  19. data/extconf.rb +7 -0
  20. data/lib/wreq.rb +303 -0
  21. data/lib/wreq_ruby/3.3/wreq_ruby.so +0 -0
  22. data/lib/wreq_ruby/3.4/wreq_ruby.so +0 -0
  23. data/lib/wreq_ruby/4.0/wreq_ruby.so +0 -0
  24. data/lib/wreq_ruby/body.rb +36 -0
  25. data/lib/wreq_ruby/client.rb +526 -0
  26. data/lib/wreq_ruby/cookie.rb +156 -0
  27. data/lib/wreq_ruby/emulate.rb +232 -0
  28. data/lib/wreq_ruby/error.rb +157 -0
  29. data/lib/wreq_ruby/header.rb +205 -0
  30. data/lib/wreq_ruby/http.rb +160 -0
  31. data/lib/wreq_ruby/response.rb +209 -0
  32. data/script/build_platform_gem.rb +34 -0
  33. data/script/build_windows_gnu.ps1 +257 -0
  34. data/src/arch.rs +33 -0
  35. data/src/client/body/form.rs +2 -0
  36. data/src/client/body/json.rs +16 -0
  37. data/src/client/body/stream.rs +146 -0
  38. data/src/client/body.rs +57 -0
  39. data/src/client/param.rs +19 -0
  40. data/src/client/query.rs +2 -0
  41. data/src/client/req.rs +274 -0
  42. data/src/client/resp.rs +248 -0
  43. data/src/client.rs +413 -0
  44. data/src/cookie.rs +312 -0
  45. data/src/emulate.rs +376 -0
  46. data/src/error.rs +163 -0
  47. data/src/extractor.rs +117 -0
  48. data/src/gvl.rs +154 -0
  49. data/src/header.rs +245 -0
  50. data/src/http.rs +142 -0
  51. data/src/lib.rs +98 -0
  52. data/src/macros.rs +123 -0
  53. data/src/rt.rs +46 -0
  54. data/test/client_cookie_test.rb +46 -0
  55. data/test/client_test.rb +136 -0
  56. data/test/cookie_test.rb +182 -0
  57. data/test/emulation_test.rb +21 -0
  58. data/test/error_handling_test.rb +92 -0
  59. data/test/header_test.rb +290 -0
  60. data/test/inspect_test.rb +125 -0
  61. data/test/module_methods_test.rb +75 -0
  62. data/test/orig_header_test.rb +115 -0
  63. data/test/request_parameters_test.rb +175 -0
  64. data/test/request_test.rb +244 -0
  65. data/test/response_test.rb +69 -0
  66. data/test/stream_test.rb +397 -0
  67. data/test/test_helper.rb +52 -0
  68. data/wreq.gemspec +68 -0
  69. metadata +123 -0
@@ -0,0 +1,257 @@
1
+ param(
2
+ [switch]$SkipBundleInstall,
3
+ [switch]$SkipToolInstall,
4
+ [switch]$BuildGem,
5
+ [switch]$TestGem,
6
+ [switch]$RunTests,
7
+ [string]$SmokeUrl = $env:WREQ_SMOKE_URL
8
+ )
9
+
10
+ $ErrorActionPreference = "Stop"
11
+
12
+ $target = "x86_64-pc-windows-gnu"
13
+ $platform = "x64-mingw-ucrt"
14
+ $gemPattern = "pkg\wreq-*-$platform.gem"
15
+ if ([string]::IsNullOrWhiteSpace($SmokeUrl)) {
16
+ $SmokeUrl = "https://httpbin.io/get"
17
+ }
18
+ $rubyRoot = Split-Path (Split-Path (Get-Command ruby).Source -Parent) -Parent
19
+ $ucrt = Join-Path $rubyRoot "msys64\ucrt64"
20
+ $ucrtBin = Join-Path $ucrt "bin"
21
+ $msysPackages = @(
22
+ "mingw-w64-ucrt-x86_64-gcc",
23
+ "mingw-w64-ucrt-x86_64-clang",
24
+ "mingw-w64-ucrt-x86_64-cmake",
25
+ "mingw-w64-ucrt-x86_64-pkgconf"
26
+ )
27
+
28
+ function Invoke-Step {
29
+ param(
30
+ [string]$Name,
31
+ [scriptblock]$Command
32
+ )
33
+
34
+ Write-Host "==> $Name"
35
+ & $Command
36
+ }
37
+
38
+ function Get-MissingUcrtTools {
39
+ $missing = @()
40
+
41
+ if (-not (Test-Path (Join-Path $ucrtBin "gcc.exe")) -or -not (Test-Path (Join-Path $ucrtBin "g++.exe"))) {
42
+ $missing += "gcc/g++"
43
+ }
44
+
45
+ if (-not (Test-Path (Join-Path $ucrtBin "clang.exe")) -or -not (Test-Path (Join-Path $ucrtBin "libclang.dll"))) {
46
+ $missing += "clang/libclang"
47
+ }
48
+
49
+ if (-not (Test-Path (Join-Path $ucrtBin "cmake.exe"))) {
50
+ $missing += "cmake"
51
+ }
52
+
53
+ if (-not (Test-Path (Join-Path $ucrtBin "pkgconf.exe")) -and -not (Test-Path (Join-Path $ucrtBin "pkg-config.exe"))) {
54
+ $missing += "pkgconf"
55
+ }
56
+
57
+ $missing
58
+ }
59
+
60
+ function Get-LatestPlatformGem {
61
+ $gem = Get-ChildItem -Path $gemPattern -ErrorAction SilentlyContinue |
62
+ Sort-Object LastWriteTime -Descending |
63
+ Select-Object -First 1
64
+
65
+ if (-not $gem) {
66
+ throw "No Windows GNU platform gem found at '$gemPattern'. Re-run with -BuildGem first."
67
+ }
68
+
69
+ $gem
70
+ }
71
+
72
+ function Invoke-RubySmoke {
73
+ param(
74
+ [string]$Name,
75
+ [string]$Code
76
+ )
77
+
78
+ Write-Host "Smoke: $Name"
79
+ ruby -e $Code
80
+ if ($LASTEXITCODE -ne 0) {
81
+ throw "$Name failed with exit code $LASTEXITCODE."
82
+ }
83
+ }
84
+
85
+ Invoke-Step "Check Ruby platform" {
86
+ $rubyArch = & ruby -rrbconfig -e "print RbConfig::CONFIG['arch']"
87
+ if ($rubyArch -ne $platform) {
88
+ throw "Expected Ruby platform '$platform', got '$rubyArch'. Use RubyInstaller UCRT for this build."
89
+ }
90
+
91
+ ruby -v
92
+ ruby -rrbconfig -rrubygems -e "puts RbConfig::CONFIG.values_at('arch', 'host', 'CC').join(%q{ }); puts Gem::Platform.local"
93
+ }
94
+
95
+ Invoke-Step "Install Rust target" {
96
+ $installedTargets = @(rustup target list --installed)
97
+ if ($installedTargets -contains $target) {
98
+ Write-Host "Rust target $target already installed; skipping rustup."
99
+ return
100
+ }
101
+
102
+ rustup target add $target
103
+ if ($LASTEXITCODE -ne 0) {
104
+ throw "Failed to install Rust target $target."
105
+ }
106
+ }
107
+
108
+ Invoke-Step "Check MSYS2 UCRT build tools" {
109
+ $missing = @(Get-MissingUcrtTools)
110
+ if ($missing.Count -eq 0) {
111
+ Write-Host "MSYS2 UCRT build tools already present; skipping pacman."
112
+ return
113
+ }
114
+
115
+ if ($SkipToolInstall) {
116
+ throw "Missing MSYS2 UCRT build tools: $($missing -join ', '). Re-run without -SkipToolInstall or install them with ridk."
117
+ }
118
+
119
+ Write-Host "Missing MSYS2 UCRT build tools: $($missing -join ', ')"
120
+ ridk exec pacman -S --needed --noconfirm @msysPackages
121
+ if ($LASTEXITCODE -ne 0) {
122
+ $stillMissing = @(Get-MissingUcrtTools)
123
+ if ($stillMissing.Count -eq 0) {
124
+ Write-Warning "pacman returned exit code $LASTEXITCODE, but all required tools are present; continuing."
125
+ return
126
+ }
127
+
128
+ throw "Failed to install MSYS2 UCRT build tools. Missing: $($stillMissing -join ', '). If pacman cannot lock its database, close other pacman/ridk shells, run PowerShell as Administrator, or install RubyInstaller in a user-writable directory."
129
+ }
130
+ }
131
+
132
+ Invoke-Step "Configure Windows GNU toolchain" {
133
+ $env:CARGO_BUILD_TARGET = $target
134
+ $env:LIBCLANG_PATH = $ucrtBin
135
+ $env:CC_x86_64_pc_windows_gnu = Join-Path $ucrtBin "gcc.exe"
136
+ $env:CXX_x86_64_pc_windows_gnu = Join-Path $ucrtBin "g++.exe"
137
+ $env:CMAKE = Join-Path $ucrtBin "cmake.exe"
138
+ Remove-Item Env:\CMAKE_GENERATOR -ErrorAction SilentlyContinue
139
+
140
+ rustc -Vv
141
+ Write-Host "LIBCLANG_PATH=$env:LIBCLANG_PATH"
142
+ Write-Host "CC_x86_64_pc_windows_gnu=$env:CC_x86_64_pc_windows_gnu"
143
+ Write-Host "CXX_x86_64_pc_windows_gnu=$env:CXX_x86_64_pc_windows_gnu"
144
+ Write-Host "CMAKE=$env:CMAKE"
145
+ }
146
+
147
+ if (-not $SkipBundleInstall) {
148
+ Invoke-Step "Install Ruby dependencies" {
149
+ bundle install
150
+ }
151
+ }
152
+
153
+ Invoke-Step "Compile native extension" {
154
+ ridk exec ruby -S bundle exec rake compile
155
+ }
156
+
157
+ Invoke-Step "Verify extension loads" {
158
+ ruby -Ilib -rwreq -e "puts Wreq::VERSION; puts Wreq::Client.new.class"
159
+ }
160
+
161
+ if ($RunTests) {
162
+ Invoke-Step "Run tests" {
163
+ ridk exec ruby -S bundle exec rake test
164
+ }
165
+ }
166
+
167
+ if ($BuildGem) {
168
+ Invoke-Step "Build local platform gem" {
169
+ $rubyMinor = & ruby -e "print RUBY_VERSION[/\A\d+\.\d+/]"
170
+ $dest = "lib\wreq_ruby\$rubyMinor"
171
+ New-Item -ItemType Directory -Force $dest | Out-Null
172
+ Copy-Item "lib\wreq_ruby\wreq_ruby.so" (Join-Path $dest "wreq_ruby.so") -Force
173
+
174
+ ruby script/build_platform_gem.rb $platform
175
+ }
176
+ }
177
+
178
+ if ($TestGem) {
179
+ Invoke-Step "Install and smoke test platform gem" {
180
+ $gem = Get-LatestPlatformGem
181
+ $gemHome = Join-Path ([System.IO.Path]::GetTempPath()) "wreq-gem-test-$PID"
182
+ $gemBin = Join-Path $gemHome "bin"
183
+ $testDir = Join-Path ([System.IO.Path]::GetTempPath()) "wreq-gem-test-cwd-$PID"
184
+
185
+ Remove-Item -Recurse -Force $gemHome, $testDir -ErrorAction SilentlyContinue
186
+ New-Item -ItemType Directory -Force $gemHome, $gemBin, $testDir | Out-Null
187
+
188
+ $oldGemHome = $env:GEM_HOME
189
+ $oldGemPath = $env:GEM_PATH
190
+ $oldGemrc = $env:GEMRC
191
+ $oldRubyopt = $env:RUBYOPT
192
+ $oldRubylib = $env:RUBYLIB
193
+ $oldBundleGemfile = $env:BUNDLE_GEMFILE
194
+ $oldBundleBinPath = $env:BUNDLE_BIN_PATH
195
+ $oldBundlerVersion = $env:BUNDLER_VERSION
196
+ $oldSmokeUrl = $env:WREQ_SMOKE_URL
197
+ $oldWreqGemHome = $env:WREQ_GEM_HOME
198
+
199
+ try {
200
+ $env:GEM_HOME = $gemHome
201
+ $env:GEM_PATH = $gemHome
202
+ $env:GEMRC = ""
203
+ $env:RUBYOPT = ""
204
+ $env:RUBYLIB = ""
205
+ $env:BUNDLE_GEMFILE = ""
206
+ $env:BUNDLE_BIN_PATH = ""
207
+ $env:BUNDLER_VERSION = ""
208
+ $env:WREQ_SMOKE_URL = $SmokeUrl
209
+ $env:WREQ_GEM_HOME = $gemHome
210
+
211
+ gem install --norc --local --install-dir $gemHome --bindir $gemBin --no-document --force --no-user-install $gem.FullName
212
+ if ($LASTEXITCODE -ne 0) {
213
+ throw "Failed to install $($gem.FullName)."
214
+ }
215
+
216
+ Push-Location $testDir
217
+ try {
218
+ Invoke-RubySmoke "Load installed platform gem" @'
219
+ STDOUT.sync = true
220
+ STDERR.sync = true
221
+ require "wreq"
222
+ spec = Gem.loaded_specs.fetch("wreq")
223
+ expected = File.expand_path(ENV.fetch("WREQ_GEM_HOME"))
224
+ actual = File.expand_path(spec.full_gem_path)
225
+ raise "loaded #{actual}, expected under #{expected}" unless actual.start_with?(expected)
226
+ puts "loaded #{spec.full_name}"
227
+ puts actual
228
+ puts "wreq #{Wreq::VERSION}"
229
+ '@
230
+
231
+ Invoke-RubySmoke "Request through installed platform gem" @'
232
+ STDOUT.sync = true
233
+ STDERR.sync = true
234
+ require "wreq"
235
+ client = Wreq::Client.new
236
+ resp = client.get(ENV.fetch("WREQ_SMOKE_URL"), timeout: 20)
237
+ puts "HTTP #{resp.code}"
238
+ raise "unexpected status #{resp.code}" unless resp.code == 200
239
+ '@
240
+ } finally {
241
+ Pop-Location
242
+ }
243
+ } finally {
244
+ $env:GEM_HOME = $oldGemHome
245
+ $env:GEM_PATH = $oldGemPath
246
+ $env:GEMRC = $oldGemrc
247
+ $env:RUBYOPT = $oldRubyopt
248
+ $env:RUBYLIB = $oldRubylib
249
+ $env:BUNDLE_GEMFILE = $oldBundleGemfile
250
+ $env:BUNDLE_BIN_PATH = $oldBundleBinPath
251
+ $env:BUNDLER_VERSION = $oldBundlerVersion
252
+ $env:WREQ_SMOKE_URL = $oldSmokeUrl
253
+ $env:WREQ_GEM_HOME = $oldWreqGemHome
254
+ Remove-Item -Recurse -Force $gemHome, $testDir -ErrorAction SilentlyContinue
255
+ }
256
+ }
257
+ }
data/src/arch.rs ADDED
@@ -0,0 +1,33 @@
1
+ //! Platform-specific support code.
2
+ //!
3
+ //! Keep this module small and focused on platform quirks that affect linking,
4
+ //! ABI boundaries, or OS APIs used by the Rust extension. Normal HTTP client
5
+ //! behavior should stay in the client/runtime modules so platform workarounds
6
+ //! do not leak into the rest of the binding.
7
+
8
+ #[cfg(all(target_os = "windows", target_env = "gnu"))]
9
+ mod windows_gnu {
10
+ //! Windows GNU support.
11
+ //!
12
+ //! RubyInstaller's GNU/UCRT Ruby exports some symbols with the same names as
13
+ //! Win32 APIs. When GNU ld links this extension against Ruby, those symbols
14
+ //! can shadow the real Windows APIs unless we pin the calls we care about.
15
+
16
+ #[link(name = "kernel32")]
17
+ unsafe extern "system" {
18
+ fn SleepEx(milliseconds: u32, alertable: i32) -> u32;
19
+ }
20
+
21
+ // RubyInstaller's GNU/UCRT Ruby exports a Ruby-aware Sleep symbol. GNU ld can
22
+ // bind extension calls to that symbol instead of KERNEL32!Sleep, which crashes
23
+ // on native worker threads that have no Ruby TLS. The linker wrapper keeps those
24
+ // calls on the Windows API path.
25
+ #[unsafe(no_mangle)]
26
+ pub extern "system" fn __wrap_Sleep(milliseconds: u32) {
27
+ // SAFETY: SleepEx is a Win32 API. Passing alertable=false matches Sleep's
28
+ // behavior, and any u32 duration is accepted by the API.
29
+ unsafe {
30
+ SleepEx(milliseconds, 0);
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,2 @@
1
+ /// Alias for form parameters.
2
+ pub type Form = crate::client::param::Params;
@@ -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,146 @@
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};
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, wreq_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
+ /// Read the next body chunk, converting stream errors into Ruby errors.
42
+ pub fn next(&self) -> Result<Option<Bytes>, Error> {
43
+ rt::try_block_on(
44
+ async {
45
+ match self.0.lock().await.as_mut().next().await {
46
+ Some(Ok(data)) => Ok(Some(data)),
47
+ Some(Err(err)) => Err(err),
48
+ None => Ok(None),
49
+ }
50
+ },
51
+ wreq_error_to_magnus,
52
+ )
53
+ }
54
+ }
55
+
56
+ // ===== impl BodySender =====
57
+
58
+ impl BodySender {
59
+ /// Ruby: `Wreq::Sender.new(capacity = 8)`
60
+ pub fn new(args: &[Value]) -> Self {
61
+ let capacity: usize = if let Some(v) = args.first() {
62
+ usize::try_convert(*v).unwrap_or(8)
63
+ } else {
64
+ 8
65
+ };
66
+
67
+ let (tx, rx) = mpsc::channel(capacity);
68
+ BodySender(RwLock::new(InnerBodySender {
69
+ tx: Some(tx),
70
+ rx: Some(rx),
71
+ }))
72
+ }
73
+
74
+ /// Ruby: `push(data)` where data is String or bytes
75
+ pub fn push(rb_self: &Self, data: RString) -> Result<(), Error> {
76
+ let bytes = data.to_bytes();
77
+ let inner = rb_self.0.read().unwrap();
78
+ if let Some(ref tx) = inner.tx {
79
+ rt::try_block_on(tx.send(bytes), mpsc_send_error_to_magnus)?;
80
+ }
81
+ Ok(())
82
+ }
83
+
84
+ /// Ruby: `close` to close the sender
85
+ pub fn close(&self) {
86
+ let mut inner = self.0.write().unwrap();
87
+ inner.tx.take();
88
+ inner.rx.take();
89
+ }
90
+ }
91
+
92
+ impl TryFrom<&BodySender> for ReceiverStream<Bytes> {
93
+ type Error = magnus::Error;
94
+
95
+ fn try_from(sender: &BodySender) -> Result<Self, Self::Error> {
96
+ sender
97
+ .0
98
+ .write()
99
+ .unwrap()
100
+ .rx
101
+ .take()
102
+ .map(ReceiverStream::new)
103
+ .ok_or_else(memory_error)
104
+ }
105
+ }
106
+
107
+ /// A wrapper around [`tokio::sync::mpsc::Receiver`] that implements [`Stream`].
108
+ pub struct ReceiverStream<T> {
109
+ inner: mpsc::Receiver<T>,
110
+ }
111
+
112
+ impl<T> ReceiverStream<T> {
113
+ /// Create a new [`ReceiverStream`].
114
+ #[inline]
115
+ pub fn new(recv: mpsc::Receiver<T>) -> Self {
116
+ Self { inner: recv }
117
+ }
118
+ }
119
+
120
+ impl<T> Stream for ReceiverStream<T> {
121
+ type Item = T;
122
+
123
+ #[inline]
124
+ fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
125
+ self.inner.poll_recv(cx)
126
+ }
127
+
128
+ /// Returns the bounds of the stream based on the underlying receiver.
129
+ ///
130
+ /// For open channels, it returns `(receiver.len(), None)`.
131
+ ///
132
+ /// For closed channels, it returns `(receiver.len(), Some(used_capacity))`
133
+ /// where `used_capacity` is calculated as `receiver.max_capacity() -
134
+ /// receiver.capacity()`. This accounts for any [`Permit`] that is still
135
+ /// able to send a message.
136
+ ///
137
+ /// [`Permit`]: struct@tokio::sync::mpsc::Permit
138
+ fn size_hint(&self) -> (usize, Option<usize>) {
139
+ if self.inner.is_closed() {
140
+ let used_capacity = self.inner.max_capacity() - self.inner.capacity();
141
+ (self.inner.len(), Some(used_capacity))
142
+ } else {
143
+ (self.inner.len(), None)
144
+ }
145
+ }
146
+ }
@@ -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
+ }
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ /// Alias for query parameters.
2
+ pub type Query = super::param::Params;