wreq 1.2.5 → 1.2.7

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.cargo/config.toml +4 -0
  3. data/Cargo.lock +135 -119
  4. data/Cargo.toml +11 -3
  5. data/Rakefile +6 -0
  6. data/crates/wreq-util/README.md +1 -1
  7. data/crates/wreq-util/examples/emulate.rs +3 -3
  8. data/crates/wreq-util/src/emulate/macros.rs +13 -11
  9. data/crates/wreq-util/src/emulate/profile/chrome/header.rs +18 -3
  10. data/crates/wreq-util/src/emulate/profile/firefox/header.rs +16 -0
  11. data/crates/wreq-util/src/emulate/profile/opera/header.rs +6 -7
  12. data/crates/wreq-util/tests/client.rs +103 -1
  13. data/docs/windows-gnu-tokio-crash.md +49 -3
  14. data/extconf.rb +4 -0
  15. data/lib/wreq.rb +70 -45
  16. data/lib/wreq_ruby/body.rb +29 -11
  17. data/lib/wreq_ruby/client.rb +88 -55
  18. data/lib/wreq_ruby/cookie.rb +79 -19
  19. data/lib/wreq_ruby/emulate.rb +74 -6
  20. data/lib/wreq_ruby/error.rb +5 -7
  21. data/lib/wreq_ruby/http.rb +74 -0
  22. data/lib/wreq_ruby/response.rb +3 -0
  23. data/script/rust_env.rb +85 -0
  24. data/src/arch.rs +22 -0
  25. data/src/client/body/form.rs +2 -0
  26. data/src/client/body/json.rs +47 -14
  27. data/src/client/body/stream.rs +147 -43
  28. data/src/client/body.rs +11 -15
  29. data/src/client/param.rs +7 -7
  30. data/src/client/req.rs +87 -47
  31. data/src/client/resp.rs +23 -24
  32. data/src/client.rs +310 -230
  33. data/src/cookie.rs +280 -87
  34. data/src/emulate.rs +85 -50
  35. data/src/error.rs +198 -41
  36. data/src/extractor.rs +16 -76
  37. data/src/header.rs +146 -23
  38. data/src/http.rs +62 -33
  39. data/src/lib.rs +26 -20
  40. data/src/macros.rs +71 -46
  41. data/src/options.rs +284 -0
  42. data/src/rt.rs +22 -11
  43. data/src/serde/de/array_deserializer.rs +48 -0
  44. data/src/serde/de/array_enumerator.rs +59 -0
  45. data/src/serde/de/deserializer.rs +307 -0
  46. data/src/serde/de/enum_deserializer.rs +47 -0
  47. data/src/serde/de/hash_deserializer.rs +89 -0
  48. data/src/serde/de/number_deserializer.rs +51 -0
  49. data/src/serde/de/variant_deserializer.rs +101 -0
  50. data/src/serde/de.rs +33 -0
  51. data/src/serde/error.rs +146 -0
  52. data/src/serde/ser/enums.rs +14 -0
  53. data/src/serde/ser/map_serializer.rs +56 -0
  54. data/src/serde/ser/seq_serializer.rs +71 -0
  55. data/src/serde/ser/serializer.rs +194 -0
  56. data/src/serde/ser/struct_serializer.rs +106 -0
  57. data/src/serde/ser/struct_variant_serializer.rs +44 -0
  58. data/src/serde/ser/tuple_variant_serializer.rs +41 -0
  59. data/src/serde/ser.rs +18 -0
  60. data/src/serde/tests.rs +237 -0
  61. data/src/serde.rs +202 -0
  62. data/test/body_sender_test.rb +246 -0
  63. data/test/cookie_test.rb +211 -10
  64. data/test/json_precision_test.rb +209 -0
  65. data/test/option_validation_test.rb +311 -0
  66. data/test/value_semantics_test.rb +238 -0
  67. data/wreq.gemspec +1 -1
  68. metadata +28 -4
  69. data/script/build_windows_gnu.ps1 +0 -264
  70. data/src/header/helper.rs +0 -112
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Profile and platform constants mirror the native enum variant names.
4
+ # standard:disable Naming/ConstantName
5
+
3
6
  module Wreq
4
7
  # Browser and client fingerprint profile enumeration backed by Rust.
5
8
  #
@@ -164,6 +167,29 @@ module Wreq
164
167
  def to_s
165
168
  end
166
169
  end
170
+
171
+ unless method_defined?(:==)
172
+ # Value-based equality.
173
+ # @param other [Object]
174
+ # @return [Boolean]
175
+ def ==(other)
176
+ end
177
+ end
178
+
179
+ unless method_defined?(:eql?)
180
+ # Strict equality for Hash key and Set member semantics.
181
+ # @param other [Object]
182
+ # @return [Boolean]
183
+ def eql?(other)
184
+ end
185
+ end
186
+
187
+ unless method_defined?(:hash)
188
+ # Hash value consistent with {#eql?} for use as Hash keys.
189
+ # @return [Integer]
190
+ def hash
191
+ end
192
+ end
167
193
  end
168
194
 
169
195
  # Operating system platform enumeration backed by Rust.
@@ -195,6 +221,36 @@ module Wreq
195
221
  def to_s
196
222
  end
197
223
  end
224
+
225
+ unless method_defined?(:to_sym)
226
+ # Returns the platform as a lowercase symbol (e.g. :windows, :linux).
227
+ # @return [Symbol]
228
+ def to_sym
229
+ end
230
+ end
231
+
232
+ unless method_defined?(:==)
233
+ # Value-based equality.
234
+ # @param other [Object]
235
+ # @return [Boolean]
236
+ def ==(other)
237
+ end
238
+ end
239
+
240
+ unless method_defined?(:eql?)
241
+ # Strict equality for Hash key and Set member semantics.
242
+ # @param other [Object]
243
+ # @return [Boolean]
244
+ def eql?(other)
245
+ end
246
+ end
247
+
248
+ unless method_defined?(:hash)
249
+ # Hash value consistent with {#eql?} for use as Hash keys.
250
+ # @return [Integer]
251
+ def hash
252
+ end
253
+ end
198
254
  end
199
255
 
200
256
  # Emulation option wrapper.
@@ -216,19 +272,31 @@ module Wreq
216
272
  #
217
273
  # @param profile [Wreq::Profile, nil] Fingerprint profile to emulate
218
274
  # @param platform [Wreq::Platform, nil] Operating system platform to emulate
219
- # @param http2 [Boolean] Whether HTTP/2 support is enabled
220
- # @param headers [Boolean] Whether default emulation headers are enabled
275
+ # @param http2 [Boolean, nil] Whether HTTP/2 emulation is enabled; defaults
276
+ # to true when omitted or nil
277
+ # @param headers [Boolean, nil] Whether default emulation headers are enabled;
278
+ # defaults to true when omitted or nil
279
+ # @return [Wreq::Emulation] Configured emulation settings
280
+ # @raise [ArgumentError] if an option is unknown or extra arguments are given
281
+ # @raise [TypeError] if the option argument is not a Hash or a value has the
282
+ # wrong Ruby type
221
283
  class Emulation
222
284
  # Native fields and methods are set by the extension.
223
285
  # This stub is for documentation only.
224
- unless method_defined?(:new)
286
+ unless singleton_methods(false).include?(:new)
225
287
  # @param profile [Wreq::Profile, nil] Fingerprint profile to emulate
226
288
  # @param platform [Wreq::Platform, nil] Operating system platform to emulate
227
- # @param http2 [Boolean] Whether HTTP/2 support is enabled
228
- # @param headers [Boolean] Whether default emulation headers are enabled
289
+ # @param http2 [Boolean, nil] Whether HTTP/2 emulation is enabled; defaults
290
+ # to true when omitted or nil
291
+ # @param headers [Boolean, nil] Whether default emulation headers are enabled;
292
+ # defaults to true when omitted or nil
229
293
  # @return [Wreq::Emulation] Configured emulation settings
230
- def self.new(profile: nil, platform: nil, http2: true, headers: true)
294
+ # @raise [ArgumentError] if an option is unknown or extra arguments are given
295
+ # @raise [TypeError] if an option or value has the wrong Ruby type
296
+ def self.new(**options)
231
297
  end
232
298
  end
233
299
  end
234
300
  end
301
+
302
+ # standard:enable Naming/ConstantName
@@ -88,7 +88,7 @@ unless defined?(Wreq)
88
88
  #
89
89
  # @example
90
90
  # begin
91
- # client = Wreq::Client.new(max_redirects: 3)
91
+ # client = Wreq::Client.new(allow_redirects: true, max_redirects: 3)
92
92
  # client.get("https://httpbin.io/redirect/10")
93
93
  # rescue Wreq::RedirectError => e
94
94
  # puts "Too many redirects: #{e.message}"
@@ -139,16 +139,14 @@ unless defined?(Wreq)
139
139
 
140
140
  # Configuration and builder errors
141
141
 
142
- # Client configuration is invalid.
142
+ # A native client or request configuration could not be built.
143
143
  #
144
- # Raised when the client is configured with invalid options.
144
+ # Raised when validated Ruby options cannot be represented by the native
145
+ # builder or request body.
145
146
  #
146
147
  # @example
147
148
  # begin
148
- # client = Wreq::Client.new(
149
- # proxy: "invalid://proxy",
150
- # timeout: -1
151
- # )
149
+ # client = Wreq::Client.new(proxy: "invalid://")
152
150
  # rescue Wreq::BuilderError => e
153
151
  # puts "Invalid configuration: #{e.message}"
154
152
  # end
@@ -25,6 +25,43 @@ module Wreq
25
25
  TRACE = nil # @return [Wreq::Method] HTTP TRACE method
26
26
  PATCH = nil # @return [Wreq::Method] HTTP PATCH method
27
27
  end
28
+
29
+ # Returns the HTTP method token (e.g. "GET", "POST").
30
+ # @return [String]
31
+ unless method_defined?(:to_s)
32
+ def to_s
33
+ end
34
+ end
35
+
36
+ # Returns the HTTP method as a lowercase symbol (e.g. :get, :post).
37
+ # @return [Symbol]
38
+ unless method_defined?(:to_sym)
39
+ def to_sym
40
+ end
41
+ end
42
+
43
+ # Value-based equality. Returns true when both represent the same HTTP method.
44
+ # @param other [Object]
45
+ # @return [Boolean]
46
+ unless method_defined?(:==)
47
+ def ==(other)
48
+ end
49
+ end
50
+
51
+ # Strict equality for Hash key and Set member semantics.
52
+ # @param other [Object]
53
+ # @return [Boolean]
54
+ unless method_defined?(:eql?)
55
+ def eql?(other)
56
+ end
57
+ end
58
+
59
+ # Hash value consistent with {#eql?} for use as Hash keys.
60
+ # @return [Integer]
61
+ unless method_defined?(:hash)
62
+ def hash
63
+ end
64
+ end
28
65
  end
29
66
 
30
67
  # HTTP version enumeration backed by Rust.
@@ -63,6 +100,21 @@ module Wreq
63
100
  def ==(other)
64
101
  end
65
102
  end
103
+
104
+ # Strict equality for Hash key and Set member semantics.
105
+ # @param other [Object]
106
+ # @return [Boolean]
107
+ unless method_defined?(:eql?)
108
+ def eql?(other)
109
+ end
110
+ end
111
+
112
+ # Hash value consistent with {#eql?} for use as Hash keys.
113
+ # @return [Integer]
114
+ unless method_defined?(:hash)
115
+ def hash
116
+ end
117
+ end
66
118
  end
67
119
 
68
120
  # HTTP status code wrapper.
@@ -141,6 +193,28 @@ module Wreq
141
193
  # @return [String] Status code as string
142
194
  def to_s
143
195
  end
196
+
197
+ # Returns the status code as an integer.
198
+ # @return [Integer] the numeric HTTP status code
199
+ def to_i
200
+ end
201
+
202
+ # Value-based equality. Only compares with other StatusCode instances.
203
+ # @param other [Object]
204
+ # @return [Boolean]
205
+ def ==(other)
206
+ end
207
+
208
+ # Strict equality for Hash key and Set member semantics.
209
+ # @param other [Object]
210
+ # @return [Boolean]
211
+ def eql?(other)
212
+ end
213
+
214
+ # Hash value consistent with {#eql?} for use as Hash keys.
215
+ # @return [Integer]
216
+ def hash
217
+ end
144
218
  end
145
219
  end
146
220
  end
@@ -127,6 +127,9 @@ unless defined?(Wreq)
127
127
 
128
128
  # Parse the response body as JSON.
129
129
  #
130
+ # Integral JSON numbers are returned as arbitrary-precision Ruby Integer
131
+ # values. Fractional and exponent-form numbers are returned as Float values.
132
+ #
130
133
  # @return [Object] Parsed JSON (Hash, Array, String, Integer, Float, Boolean, nil)
131
134
  # @raise [Wreq::DecodingError] if body is not valid JSON
132
135
  # @example
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+ require "fileutils"
5
+
6
+ module Wreq
7
+ module RustEnv
8
+ WINDOWS_GNU_TARGET = "x86_64-pc-windows-gnu"
9
+ WINDOWS_RUBY_PLATFORM = "x64-mingw-ucrt"
10
+
11
+ # Uses the UCRT toolchain shipped with the active RubyInstaller installation.
12
+ def self.activate
13
+ unless RbConfig::CONFIG["host_os"].match?(/mingw|mswin/)
14
+ FileUtils.rm_f(cargo_config_path)
15
+ return
16
+ end
17
+
18
+ ruby_platform = RbConfig::CONFIG.fetch("arch")
19
+ unless ruby_platform == WINDOWS_RUBY_PLATFORM
20
+ raise "Unsupported Windows Ruby platform: #{ruby_platform}. " \
21
+ "Use RubyInstaller UCRT (#{WINDOWS_RUBY_PLATFORM})."
22
+ end
23
+
24
+ ruby_root = RbConfig::CONFIG.fetch("prefix")
25
+ msys_root = File.join(ruby_root, "msys64")
26
+ ucrt_bin = File.join(msys_root, "ucrt64", "bin")
27
+ msys_bin = File.join(msys_root, "usr", "bin")
28
+
29
+ validate_tools(ucrt_bin, msys_bin)
30
+ write_cargo_config(ucrt_bin, msys_bin)
31
+ ENV["CARGO_BUILD_TARGET"] = WINDOWS_GNU_TARGET
32
+ ENV["LIBCLANG_PATH"] = ucrt_bin
33
+ ENV.delete("CMAKE_GENERATOR")
34
+ ENV["PATH"] = [ucrt_bin, msys_bin, ENV.fetch("PATH", nil)].compact.join(File::PATH_SEPARATOR)
35
+ end
36
+
37
+ # Reports missing RubyInstaller packages before Cargo starts compiling.
38
+ def self.validate_tools(ucrt_bin, msys_bin)
39
+ required = %w[gcc.exe g++.exe gcc-ar.exe clang.exe libclang.dll cmake.exe]
40
+ .map { |tool| File.join(ucrt_bin, tool) }
41
+ .append(File.join(msys_bin, "make.exe"))
42
+ missing = required.reject { |tool| File.file?(tool) }
43
+ unless %w[pkgconf.exe pkg-config.exe].any? { |tool| File.file?(File.join(ucrt_bin, tool)) }
44
+ missing << File.join(ucrt_bin, "pkgconf.exe")
45
+ end
46
+ return if missing.empty?
47
+
48
+ raise "Missing RubyInstaller UCRT tools: #{missing.join(", ")}. " \
49
+ "Install the Windows build dependencies documented in " \
50
+ "docs/windows-gnu-tokio-crash.md."
51
+ end
52
+
53
+ # Generates machine-local Cargo defaults that work in command lines and IDEs.
54
+ def self.write_cargo_config(ucrt_bin, msys_bin)
55
+ config = <<~TOML
56
+ # Generated by script/rust_env.rb for the active RubyInstaller.
57
+
58
+ [build]
59
+ target = "#{WINDOWS_GNU_TARGET}"
60
+
61
+ [target.#{WINDOWS_GNU_TARGET}]
62
+ linker = "#{File.join(ucrt_bin, "gcc.exe")}"
63
+
64
+ [env]
65
+ CC_x86_64_pc_windows_gnu = { value = "#{File.join(ucrt_bin, "gcc.exe")}", force = true }
66
+ CXX_x86_64_pc_windows_gnu = { value = "#{File.join(ucrt_bin, "g++.exe")}", force = true }
67
+ AR_x86_64_pc_windows_gnu = { value = "#{File.join(ucrt_bin, "gcc-ar.exe")}", force = true }
68
+ CMAKE = { value = "#{File.join(ucrt_bin, "cmake.exe")}", force = true }
69
+ CMAKE_GENERATOR = { value = "MSYS Makefiles", force = true }
70
+ CMAKE_MAKE_PROGRAM = { value = "#{File.join(msys_bin, "make.exe")}", force = true }
71
+ LIBCLANG_PATH = { value = "#{ucrt_bin}", force = true }
72
+ TOML
73
+
74
+ return if File.exist?(cargo_config_path) && File.binread(cargo_config_path) == config
75
+
76
+ FileUtils.mkdir_p(File.dirname(cargo_config_path))
77
+ File.binwrite(cargo_config_path, config)
78
+ end
79
+
80
+ # Returns the ignored Cargo configuration shared by Rake and language servers.
81
+ def self.cargo_config_path
82
+ File.expand_path("../.cargo/windows.toml", __dir__)
83
+ end
84
+ end
85
+ end
data/src/arch.rs CHANGED
@@ -4,6 +4,28 @@
4
4
  //! ABI boundaries, or OS APIs used by the Rust extension. Normal HTTP client
5
5
  //! behavior should stay in the client/runtime modules so platform workarounds
6
6
  //! do not leak into the rest of the binding.
7
+ #![allow(unsafe_code)]
8
+
9
+ /// Whether the native client exposes TCP user-timeout configuration.
10
+ pub(crate) const SUPPORTS_TCP_USER_TIMEOUT: bool = cfg!(any(
11
+ target_os = "android",
12
+ target_os = "fuchsia",
13
+ target_os = "linux"
14
+ ));
15
+
16
+ /// Whether the native client exposes network-interface binding.
17
+ pub(crate) const SUPPORTS_INTERFACE: bool = cfg!(any(
18
+ target_os = "android",
19
+ target_os = "fuchsia",
20
+ target_os = "illumos",
21
+ target_os = "ios",
22
+ target_os = "linux",
23
+ target_os = "macos",
24
+ target_os = "solaris",
25
+ target_os = "tvos",
26
+ target_os = "visionos",
27
+ target_os = "watchos",
28
+ ));
7
29
 
8
30
  #[cfg(all(target_os = "windows", target_env = "gnu"))]
9
31
  mod windows_gnu {
@@ -1,2 +1,4 @@
1
+ //! Form request body conversion.
2
+
1
3
  /// Alias for form parameters.
2
4
  pub type Form = crate::client::param::Params;
@@ -1,16 +1,49 @@
1
- use indexmap::IndexMap;
2
- use serde::{Deserialize, Serialize};
1
+ //! JSON conversion at the Ruby request and response boundary.
2
+ //!
3
+ //! Request values are converted from Ruby into an owned [`Json`] tree before
4
+ //! network I/O. Response bodies are deserialized into the same tree by wreq and
5
+ //! then converted back into Ruby values.
6
+ //!
7
+ //! The underlying `serde_json` configuration preserves object insertion order
8
+ //! and arbitrary-size integer tokens in both directions.
3
9
 
4
- /// 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>),
10
+ use ::serde::{Deserialize, Serialize};
11
+ use magnus::{Error, Ruby, TryConvert, Value};
12
+
13
+ use crate::{error::json_serialization_error, serde::deserialize_json};
14
+
15
+ /// An owned JSON tree shared by request and response conversion.
16
+ ///
17
+ /// This wrapper keeps the configured `serde_json::Value` representation private
18
+ /// so callers use the same precision and ordering behavior in both directions.
19
+ #[derive(Deserialize, Serialize)]
20
+ #[serde(transparent)]
21
+ pub struct Json(serde_json::Value);
22
+
23
+ /// Convert supported Ruby request values into an owned JSON tree.
24
+ ///
25
+ /// Supported values are `Hash`, `Array`, `String`, `Symbol`, `Integer`, finite
26
+ /// `Float`, booleans, and `nil`. Hash keys must be strings or symbols. Arrays
27
+ /// and hashes are limited to 100 nesting levels, which also bounds cyclic input.
28
+ /// Unsupported values are reported as `Wreq::BuilderError` before network I/O.
29
+ impl TryConvert for Json {
30
+ fn try_convert(value: Value) -> Result<Self, Error> {
31
+ let ruby = Ruby::get_with(value);
32
+ deserialize_json(&ruby, value)
33
+ .map(Self)
34
+ .map_err(|error| json_serialization_error(&ruby, error))
35
+ }
36
+ }
37
+
38
+ #[cfg(test)]
39
+ mod tests {
40
+ use super::Json;
41
+
42
+ #[test]
43
+ fn preserves_number_precision_and_object_order() {
44
+ let source = br#"{"second":115792089237316195423570985008687907853269984665640564039457584007913129639936,"first":1}"#;
45
+ let json: Json = serde_json::from_slice(source).unwrap();
46
+
47
+ assert_eq!(source, serde_json::to_vec(&json).unwrap().as_slice());
48
+ }
16
49
  }