wreq-rb 0.5.0 → 0.5.1

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 (150) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +1922 -397
  3. data/LICENSE +203 -0
  4. data/README.md +19 -15
  5. data/ext/wreq_rb/Cargo.toml +4 -6
  6. data/ext/wreq_rb/src/client.rs +41 -48
  7. data/lib/wreq-rb/version.rb +1 -1
  8. data/patches/0001-add-transfer-size-tracking.patch +76 -67
  9. data/vendor/wreq/Cargo.toml +119 -71
  10. data/vendor/wreq/README.md +25 -20
  11. data/vendor/wreq/bench/http1.rs +25 -0
  12. data/vendor/wreq/bench/http1_over_tls.rs +25 -0
  13. data/vendor/wreq/bench/http2.rs +25 -0
  14. data/vendor/wreq/bench/http2_over_tls.rs +25 -0
  15. data/vendor/wreq/bench/support/bench.rs +91 -0
  16. data/vendor/wreq/bench/support/client.rs +217 -0
  17. data/vendor/wreq/bench/support/server.rs +188 -0
  18. data/vendor/wreq/bench/support.rs +56 -0
  19. data/vendor/wreq/examples/cert_store.rs +4 -4
  20. data/vendor/wreq/examples/{emulation.rs → emulate.rs} +2 -2
  21. data/vendor/wreq/examples/http2_websocket.rs +2 -2
  22. data/vendor/wreq/examples/keylog.rs +3 -3
  23. data/vendor/wreq/examples/{request_with_emulation.rs → request_with_emulate.rs} +2 -2
  24. data/vendor/wreq/examples/rt.rs +23 -0
  25. data/vendor/wreq/src/client/body.rs +23 -61
  26. data/vendor/wreq/src/client/emulate.rs +119 -0
  27. data/vendor/wreq/src/client/{http/future.rs → future.rs} +11 -32
  28. data/vendor/wreq/src/client/{http → layer}/client/pool.rs +66 -61
  29. data/vendor/wreq/src/client/{http → layer}/client.rs +416 -270
  30. data/vendor/wreq/src/client/layer/config.rs +27 -6
  31. data/vendor/wreq/src/client/layer/decoder.rs +9 -4
  32. data/vendor/wreq/src/client/layer/redirect/future.rs +6 -3
  33. data/vendor/wreq/src/client/layer/redirect.rs +4 -5
  34. data/vendor/wreq/src/client/layer/retry.rs +8 -5
  35. data/vendor/wreq/src/client/layer/timeout/body.rs +15 -6
  36. data/vendor/wreq/src/client/layer/timeout/future.rs +23 -18
  37. data/vendor/wreq/src/client/layer/timeout.rs +24 -74
  38. data/vendor/wreq/src/client/layer.rs +1 -2
  39. data/vendor/wreq/src/client/multipart.rs +137 -154
  40. data/vendor/wreq/src/client/request.rs +202 -118
  41. data/vendor/wreq/src/client/response.rs +46 -45
  42. data/vendor/wreq/src/client/upgrade.rs +15 -0
  43. data/vendor/wreq/src/client/ws.rs +73 -25
  44. data/vendor/wreq/src/client.rs +1655 -17
  45. data/vendor/wreq/src/config.rs +11 -11
  46. data/vendor/wreq/src/{client/conn → conn}/connector.rs +139 -137
  47. data/vendor/wreq/src/conn/descriptor.rs +143 -0
  48. data/vendor/wreq/src/conn/http.rs +484 -0
  49. data/vendor/wreq/src/conn/net/io.rs +75 -0
  50. data/vendor/wreq/src/conn/net/tcp/compio.rs +71 -0
  51. data/vendor/wreq/src/conn/net/tcp/tokio.rs +57 -0
  52. data/vendor/wreq/src/conn/net/tcp.rs +561 -0
  53. data/vendor/wreq/src/conn/net/uds/compio.rs +60 -0
  54. data/vendor/wreq/src/{client/conn/uds.rs → conn/net/uds/tokio.rs} +18 -12
  55. data/vendor/wreq/src/conn/net/uds.rs +11 -0
  56. data/vendor/wreq/src/conn/net.rs +130 -0
  57. data/vendor/wreq/src/{client/conn → conn}/proxy/socks.rs +2 -9
  58. data/vendor/wreq/src/{client/conn → conn}/proxy/tunnel.rs +21 -56
  59. data/vendor/wreq/src/conn/tls_info.rs +47 -0
  60. data/vendor/wreq/src/{client/conn.rs → conn.rs} +202 -54
  61. data/vendor/wreq/src/cookie.rs +302 -142
  62. data/vendor/wreq/src/dns/gai/compio.rs +77 -0
  63. data/vendor/wreq/src/dns/gai/tokio.rs +90 -0
  64. data/vendor/wreq/src/dns/gai.rs +14 -164
  65. data/vendor/wreq/src/dns/hickory.rs +16 -23
  66. data/vendor/wreq/src/dns/resolve.rs +7 -41
  67. data/vendor/wreq/src/dns.rs +90 -7
  68. data/vendor/wreq/src/error.rs +57 -31
  69. data/vendor/wreq/src/ext.rs +25 -0
  70. data/vendor/wreq/src/group.rs +211 -0
  71. data/vendor/wreq/src/header.rs +100 -112
  72. data/vendor/wreq/src/lib.rs +124 -73
  73. data/vendor/wreq/src/proxy.rs +6 -20
  74. data/vendor/wreq/src/redirect.rs +1 -1
  75. data/vendor/wreq/src/rt.rs +208 -0
  76. data/vendor/wreq/src/sync.rs +97 -98
  77. data/vendor/wreq/src/tls/compress.rs +124 -0
  78. data/vendor/wreq/src/tls/conn/ext.rs +54 -45
  79. data/vendor/wreq/src/tls/conn/service.rs +14 -18
  80. data/vendor/wreq/src/tls/conn.rs +169 -241
  81. data/vendor/wreq/src/tls/keylog.rs +68 -5
  82. data/vendor/wreq/src/tls/session.rs +205 -0
  83. data/vendor/wreq/src/tls/{x509 → trust}/identity.rs +4 -21
  84. data/vendor/wreq/src/tls/{x509/parser.rs → trust/parse.rs} +1 -1
  85. data/vendor/wreq/src/tls/{x509 → trust}/store.rs +42 -81
  86. data/vendor/wreq/src/tls/{x509.rs → trust.rs} +8 -2
  87. data/vendor/wreq/src/tls.rs +489 -25
  88. data/vendor/wreq/src/trace.rs +0 -12
  89. data/vendor/wreq/src/util.rs +1 -1
  90. data/vendor/wreq/tests/badssl.rs +10 -10
  91. data/vendor/wreq/tests/client.rs +3 -9
  92. data/vendor/wreq/tests/cookie.rs +6 -8
  93. data/vendor/wreq/tests/{emulation.rs → emulate.rs} +130 -22
  94. data/vendor/wreq/tests/multipart.rs +43 -1
  95. data/vendor/wreq/tests/proxy.rs +1 -1
  96. data/vendor/wreq/tests/support/layer.rs +1 -0
  97. metadata +49 -71
  98. data/patches/0002-add-cancel-connections.patch +0 -181
  99. data/vendor/wreq/src/client/conn/conn.rs +0 -231
  100. data/vendor/wreq/src/client/conn/http.rs +0 -1023
  101. data/vendor/wreq/src/client/conn/tls_info.rs +0 -98
  102. data/vendor/wreq/src/client/core/body/incoming.rs +0 -485
  103. data/vendor/wreq/src/client/core/body/length.rs +0 -118
  104. data/vendor/wreq/src/client/core/body.rs +0 -34
  105. data/vendor/wreq/src/client/core/common/buf.rs +0 -149
  106. data/vendor/wreq/src/client/core/common/rewind.rs +0 -141
  107. data/vendor/wreq/src/client/core/common/watch.rs +0 -76
  108. data/vendor/wreq/src/client/core/common.rs +0 -3
  109. data/vendor/wreq/src/client/core/conn/http1.rs +0 -342
  110. data/vendor/wreq/src/client/core/conn/http2.rs +0 -307
  111. data/vendor/wreq/src/client/core/conn.rs +0 -11
  112. data/vendor/wreq/src/client/core/dispatch.rs +0 -299
  113. data/vendor/wreq/src/client/core/error.rs +0 -435
  114. data/vendor/wreq/src/client/core/ext.rs +0 -201
  115. data/vendor/wreq/src/client/core/http1.rs +0 -178
  116. data/vendor/wreq/src/client/core/http2.rs +0 -483
  117. data/vendor/wreq/src/client/core/proto/h1/conn.rs +0 -988
  118. data/vendor/wreq/src/client/core/proto/h1/decode.rs +0 -1170
  119. data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +0 -684
  120. data/vendor/wreq/src/client/core/proto/h1/encode.rs +0 -580
  121. data/vendor/wreq/src/client/core/proto/h1/io.rs +0 -879
  122. data/vendor/wreq/src/client/core/proto/h1/role.rs +0 -694
  123. data/vendor/wreq/src/client/core/proto/h1.rs +0 -104
  124. data/vendor/wreq/src/client/core/proto/h2/client.rs +0 -650
  125. data/vendor/wreq/src/client/core/proto/h2/ping.rs +0 -539
  126. data/vendor/wreq/src/client/core/proto/h2.rs +0 -379
  127. data/vendor/wreq/src/client/core/proto/headers.rs +0 -138
  128. data/vendor/wreq/src/client/core/proto.rs +0 -58
  129. data/vendor/wreq/src/client/core/rt/bounds.rs +0 -57
  130. data/vendor/wreq/src/client/core/rt/timer.rs +0 -150
  131. data/vendor/wreq/src/client/core/rt/tokio.rs +0 -99
  132. data/vendor/wreq/src/client/core/rt.rs +0 -25
  133. data/vendor/wreq/src/client/core/upgrade.rs +0 -267
  134. data/vendor/wreq/src/client/core.rs +0 -16
  135. data/vendor/wreq/src/client/emulation.rs +0 -161
  136. data/vendor/wreq/src/client/http/client/error.rs +0 -142
  137. data/vendor/wreq/src/client/http/client/exec.rs +0 -29
  138. data/vendor/wreq/src/client/http/client/extra.rs +0 -77
  139. data/vendor/wreq/src/client/http/client/util.rs +0 -104
  140. data/vendor/wreq/src/client/http.rs +0 -1629
  141. data/vendor/wreq/src/client/layer/config/options.rs +0 -156
  142. data/vendor/wreq/src/client/layer/cookie.rs +0 -161
  143. data/vendor/wreq/src/hash.rs +0 -143
  144. data/vendor/wreq/src/tls/conn/cache.rs +0 -123
  145. data/vendor/wreq/src/tls/conn/cert_compression.rs +0 -125
  146. data/vendor/wreq/src/tls/keylog/handle.rs +0 -64
  147. data/vendor/wreq/src/tls/options.rs +0 -464
  148. /data/vendor/wreq/src/client/{http → layer}/client/lazy.rs +0 -0
  149. /data/vendor/wreq/src/{client/conn → conn}/proxy.rs +0 -0
  150. /data/vendor/wreq/src/{client/conn → conn}/verbose.rs +0 -0
@@ -1,8 +1,9 @@
1
1
  use std::{error::Error as StdError, fmt, io};
2
2
 
3
3
  use http::Uri;
4
+ use wreq_proto::ext::ReasonPhrase;
4
5
 
5
- use crate::{StatusCode, client::ext::ReasonPhrase, util::Escape};
6
+ use crate::{StatusCode, util::Escape};
6
7
 
7
8
  /// A `Result` alias where the `Err` case is `wreq::Error`.
8
9
  pub type Result<T> = std::result::Result<T, Error>;
@@ -25,8 +26,22 @@ struct Inner {
25
26
  uri: Option<Uri>,
26
27
  }
27
28
 
29
+ #[derive(Debug)]
30
+ enum Kind {
31
+ Builder,
32
+ Request,
33
+ Tls,
34
+ Redirect,
35
+ Status(StatusCode, Option<ReasonPhrase>),
36
+ Body,
37
+ Decode,
38
+ Upgrade,
39
+ #[cfg(feature = "ws")]
40
+ WebSocket,
41
+ }
42
+
28
43
  impl Error {
29
- pub(crate) fn new<E>(kind: Kind, source: Option<E>) -> Error
44
+ fn new<E>(kind: Kind, source: Option<E>) -> Error
30
45
  where
31
46
  E: Into<BoxError>,
32
47
  {
@@ -39,43 +54,53 @@ impl Error {
39
54
  }
40
55
  }
41
56
 
57
+ #[inline]
42
58
  pub(crate) fn builder<E: Into<BoxError>>(e: E) -> Error {
43
59
  Error::new(Kind::Builder, Some(e))
44
60
  }
45
61
 
62
+ #[inline]
46
63
  pub(crate) fn body<E: Into<BoxError>>(e: E) -> Error {
47
64
  Error::new(Kind::Body, Some(e))
48
65
  }
49
66
 
67
+ #[inline]
50
68
  pub(crate) fn tls<E: Into<BoxError>>(e: E) -> Error {
51
69
  Error::new(Kind::Tls, Some(e))
52
70
  }
53
71
 
72
+ #[inline]
54
73
  pub(crate) fn decode<E: Into<BoxError>>(e: E) -> Error {
55
74
  Error::new(Kind::Decode, Some(e))
56
75
  }
57
76
 
77
+ #[inline]
58
78
  pub(crate) fn request<E: Into<BoxError>>(e: E) -> Error {
59
79
  Error::new(Kind::Request, Some(e))
60
80
  }
61
81
 
82
+ #[inline]
62
83
  pub(crate) fn redirect<E: Into<BoxError>>(e: E, uri: Uri) -> Error {
63
84
  Error::new(Kind::Redirect, Some(e)).with_uri(uri)
64
85
  }
65
86
 
87
+ #[inline]
66
88
  pub(crate) fn upgrade<E: Into<BoxError>>(e: E) -> Error {
67
89
  Error::new(Kind::Upgrade, Some(e))
68
90
  }
69
91
 
92
+ #[inline]
70
93
  #[cfg(feature = "ws")]
71
94
  pub(crate) fn websocket<E: Into<BoxError>>(e: E) -> Error {
72
95
  Error::new(Kind::WebSocket, Some(e))
73
96
  }
74
97
 
98
+ #[inline]
75
99
  pub(crate) fn status_code(uri: Uri, status: StatusCode, reason: Option<ReasonPhrase>) -> Error {
76
100
  Error::new(Kind::Status(status, reason), None::<Error>).with_uri(uri)
77
101
  }
78
102
 
103
+ #[inline]
79
104
  pub(crate) fn uri_bad_scheme(uri: Uri) -> Error {
80
105
  Error::new(Kind::Builder, Some(BadScheme)).with_uri(uri)
81
106
  }
@@ -101,6 +126,7 @@ impl Error {
101
126
  /// }
102
127
  /// # }
103
128
  /// ```
129
+ #[inline]
104
130
  pub fn uri(&self) -> Option<&Uri> {
105
131
  self.inner.uri.as_ref()
106
132
  }
@@ -110,11 +136,13 @@ impl Error {
110
136
  /// This is useful if you need to remove sensitive information from the URI
111
137
  /// (e.g. an API key in the query), but do not want to remove the URI
112
138
  /// entirely.
139
+ #[inline]
113
140
  pub fn uri_mut(&mut self) -> Option<&mut Uri> {
114
141
  self.inner.uri.as_mut()
115
142
  }
116
143
 
117
144
  /// Add a uri related to this error (overwriting any existing)
145
+ #[inline]
118
146
  pub fn with_uri(mut self, uri: Uri) -> Self {
119
147
  self.inner.uri = Some(uri);
120
148
  self
@@ -122,22 +150,26 @@ impl Error {
122
150
 
123
151
  /// Strip the related uri from this error (if, for example, it contains
124
152
  /// sensitive information)
153
+ #[inline]
125
154
  pub fn without_uri(mut self) -> Self {
126
155
  self.inner.uri = None;
127
156
  self
128
157
  }
129
158
 
130
159
  /// Returns true if the error is from a type Builder.
160
+ #[inline]
131
161
  pub fn is_builder(&self) -> bool {
132
162
  matches!(self.inner.kind, Kind::Builder)
133
163
  }
134
164
 
135
165
  /// Returns true if the error is from a `RedirectPolicy`.
166
+ #[inline]
136
167
  pub fn is_redirect(&self) -> bool {
137
168
  matches!(self.inner.kind, Kind::Redirect)
138
169
  }
139
170
 
140
171
  /// Returns true if the error is from `Response::error_for_status`.
172
+ #[inline]
141
173
  pub fn is_status(&self) -> bool {
142
174
  matches!(self.inner.kind, Kind::Status(_, _))
143
175
  }
@@ -151,7 +183,7 @@ impl Error {
151
183
  return true;
152
184
  }
153
185
 
154
- if let Some(core_err) = err.downcast_ref::<crate::client::CoreError>() {
186
+ if let Some(core_err) = err.downcast_ref::<wreq_proto::Error>() {
155
187
  if core_err.is_timeout() {
156
188
  return true;
157
189
  }
@@ -170,13 +202,14 @@ impl Error {
170
202
  }
171
203
 
172
204
  /// Returns true if the error is related to the request
205
+ #[inline]
173
206
  pub fn is_request(&self) -> bool {
174
207
  matches!(self.inner.kind, Kind::Request)
175
208
  }
176
209
 
177
210
  /// Returns true if the error is related to connect
178
211
  pub fn is_connect(&self) -> bool {
179
- use crate::client::Error;
212
+ use crate::client::layer::client::Error;
180
213
 
181
214
  let mut source = self.source();
182
215
 
@@ -195,7 +228,7 @@ impl Error {
195
228
 
196
229
  /// Returns true if the error is related to proxy connect
197
230
  pub fn is_proxy_connect(&self) -> bool {
198
- use crate::client::Error;
231
+ use crate::client::layer::client::Error;
199
232
 
200
233
  let mut source = self.source();
201
234
 
@@ -229,27 +262,32 @@ impl Error {
229
262
  }
230
263
 
231
264
  /// Returns true if the error is related to the request or response body
265
+ #[inline]
232
266
  pub fn is_body(&self) -> bool {
233
267
  matches!(self.inner.kind, Kind::Body)
234
268
  }
235
269
 
236
270
  /// Returns true if the error is related to TLS
271
+ #[inline]
237
272
  pub fn is_tls(&self) -> bool {
238
273
  matches!(self.inner.kind, Kind::Tls)
239
274
  }
240
275
 
241
276
  /// Returns true if the error is related to decoding the response's body
277
+ #[inline]
242
278
  pub fn is_decode(&self) -> bool {
243
279
  matches!(self.inner.kind, Kind::Decode)
244
280
  }
245
281
 
246
282
  /// Returns true if the error is related to upgrading the connection
283
+ #[inline]
247
284
  pub fn is_upgrade(&self) -> bool {
248
285
  matches!(self.inner.kind, Kind::Upgrade)
249
286
  }
250
287
 
251
- #[cfg(feature = "ws")]
252
288
  /// Returns true if the error is related to WebSocket operations
289
+ #[inline]
290
+ #[cfg(feature = "ws")]
253
291
  pub fn is_websocket(&self) -> bool {
254
292
  matches!(self.inner.kind, Kind::WebSocket)
255
293
  }
@@ -329,7 +367,7 @@ impl fmt::Display for Error {
329
367
  f,
330
368
  "{prefix} ({} {})",
331
369
  code.as_str(),
332
- Escape::new(reason.as_bytes())
370
+ Escape::new(reason.as_ref())
333
371
  )?;
334
372
  } else {
335
373
  write!(f, "{prefix} ({code})")?;
@@ -350,25 +388,12 @@ impl fmt::Display for Error {
350
388
  }
351
389
 
352
390
  impl StdError for Error {
391
+ #[inline]
353
392
  fn source(&self) -> Option<&(dyn StdError + 'static)> {
354
393
  self.inner.source.as_ref().map(|e| &**e as _)
355
394
  }
356
395
  }
357
396
 
358
- #[derive(Debug)]
359
- pub(crate) enum Kind {
360
- Builder,
361
- Request,
362
- Tls,
363
- Redirect,
364
- Status(StatusCode, Option<ReasonPhrase>),
365
- Body,
366
- Decode,
367
- Upgrade,
368
- #[cfg(feature = "ws")]
369
- WebSocket,
370
- }
371
-
372
397
  #[derive(Debug)]
373
398
  pub(crate) struct TimedOut;
374
399
 
@@ -380,38 +405,39 @@ pub(crate) struct ProxyConnect(pub(crate) BoxError);
380
405
 
381
406
  // ==== impl TimedOut ====
382
407
 
408
+ impl StdError for TimedOut {}
409
+
383
410
  impl fmt::Display for TimedOut {
384
411
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
385
412
  f.write_str("operation timed out")
386
413
  }
387
414
  }
388
415
 
389
- impl StdError for TimedOut {}
390
-
391
416
  // ==== impl BadScheme ====
392
417
 
418
+ impl StdError for BadScheme {}
419
+
393
420
  impl fmt::Display for BadScheme {
394
421
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
395
422
  f.write_str("URI scheme is not allowed")
396
423
  }
397
424
  }
398
425
 
399
- impl StdError for BadScheme {}
400
-
401
426
  // ==== impl ProxyConnect ====
402
427
 
403
- impl fmt::Display for ProxyConnect {
404
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
405
- write!(f, "proxy connect error: {}", self.0)
406
- }
407
- }
408
-
409
428
  impl StdError for ProxyConnect {
429
+ #[inline]
410
430
  fn source(&self) -> Option<&(dyn StdError + 'static)> {
411
431
  Some(&*self.0)
412
432
  }
413
433
  }
414
434
 
435
+ impl fmt::Display for ProxyConnect {
436
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
437
+ write!(f, "proxy connect error: {}", self.0)
438
+ }
439
+ }
440
+
415
441
  #[cfg(test)]
416
442
  mod tests {
417
443
  use super::*;
@@ -25,6 +25,31 @@ const USERINFO: &AsciiSet = &PATH
25
25
  .add(b'^')
26
26
  .add(b'|');
27
27
 
28
+ macro_rules! impl_into_shared {
29
+ ($(#[$meta:meta])* $vis:vis trait $name:ident => $target:path) => {
30
+ $(#[$meta])*
31
+ $vis trait $name {
32
+ #[doc = concat!("Converts this type into a shared [`", stringify!($target), "`].")]
33
+ fn into_shared(self) -> Arc<dyn $target>;
34
+ }
35
+
36
+ impl $name for Arc<dyn $target> {
37
+ #[inline]
38
+ fn into_shared(self) -> Arc<dyn $target> { self }
39
+ }
40
+
41
+ impl<R: $target + 'static> $name for Arc<R> {
42
+ #[inline]
43
+ fn into_shared(self) -> Arc<dyn $target> { self }
44
+ }
45
+
46
+ impl<R: $target + 'static> $name for R {
47
+ #[inline]
48
+ fn into_shared(self) -> Arc<dyn $target> { Arc::new(self) }
49
+ }
50
+ };
51
+ }
52
+
28
53
  /// Extension trait for http::Response objects
29
54
  ///
30
55
  /// Provides methods to extract URI information from HTTP responses
@@ -0,0 +1,211 @@
1
+ //! # Request Grouping Mechanism
2
+ //!
3
+ //! This module provides the [`Group`] structure, which defines the logical boundaries
4
+ //! for categorizing and segregating outbound requests.
5
+ //!
6
+ //! ## Concept
7
+ //! A `Group` acts as a multi-dimensional identity for a request. In complex networking
8
+ //! stack environments, two requests targeting the same destination may belong to
9
+ //! distinct logical groups due to different metadata, security contexts, or
10
+ //! routing requirements.
11
+ //!
12
+ //! ## Logical Segregation
13
+ //! By assigning requests to different groups, the system ensures:
14
+ //! 1. **Contextual Isolation**: Requests are processed and dispatched within their defined logical
15
+ //! partitions.
16
+ //! 2. **Deterministic Identity**: The internal `BTreeMap` ensures that the identity of a group is
17
+ //! stable and invariant to the order in which grouping criteria are applied.
18
+ //! 3. **Resource Affinity**: Resource management (such as connection pooling) respects these
19
+ //! boundaries, ensuring that resources are never leaked across different request groups.
20
+
21
+ use std::collections::BTreeMap;
22
+
23
+ use http::{Uri, Version};
24
+ use name::GroupId;
25
+
26
+ use crate::{conn::net::SocketBindOptions, proxy::Matcher};
27
+
28
+ macro_rules! impl_group_variants {
29
+ ($($name:ident $(($ty:ty))?,)*) => {
30
+ #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
31
+ enum GroupKey {
32
+ $($name,)*
33
+ }
34
+
35
+ #[derive(Debug, Clone, Hash, PartialEq, Eq)]
36
+ enum GroupVariant {
37
+ $($name $(($ty))?,)*
38
+ }
39
+ }
40
+ }
41
+
42
+ impl_group_variants! {
43
+ Request(Group),
44
+ Emulate(Group),
45
+ Named(GroupId),
46
+ Uri(Uri),
47
+ Version(Version),
48
+ Proxy(Matcher),
49
+ SocketBind(Option<SocketBindOptions>),
50
+ }
51
+
52
+ /// A logical identifier for request grouping.
53
+ ///
54
+ /// `Group` encapsulates the criteria that define a request's execution context.
55
+ /// Requests with non-identical `Group` states are treated as belonging to
56
+ /// different logical partitions, preventing unintended interaction or
57
+ /// resource sharing between them.
58
+ #[derive(Debug, Default, Clone, Hash, PartialEq, Eq)]
59
+ pub struct Group(BTreeMap<GroupKey, GroupVariant>);
60
+
61
+ impl Group {
62
+ /// Creates a new [`Group`] with a custom string or numeric identifier.
63
+ #[inline]
64
+ pub fn new<N: Into<GroupId>>(name: N) -> Self {
65
+ Group(BTreeMap::from([(
66
+ GroupKey::Named,
67
+ GroupVariant::Named(name.into()),
68
+ )]))
69
+ }
70
+
71
+ /// Groups the request by a specific target [`Uri`].
72
+ #[inline]
73
+ pub(crate) fn uri(&mut self, uri: Uri) -> &mut Self {
74
+ self.extend(GroupKey::Uri, GroupVariant::Uri(uri))
75
+ }
76
+
77
+ /// Groups the request by its required HTTP [`Version`].
78
+ #[inline]
79
+ pub(crate) fn version(&mut self, version: Option<Version>) -> &mut Self {
80
+ self.extend(GroupKey::Version, version.map(GroupVariant::Version))
81
+ }
82
+
83
+ /// Groups the request based on its proxy [`Matcher`] criteria.
84
+ #[inline]
85
+ pub(crate) fn proxy(&mut self, proxy: Option<Matcher>) -> &mut Self {
86
+ self.extend(GroupKey::Proxy, proxy.map(GroupVariant::Proxy))
87
+ }
88
+
89
+ /// Groups the request by its resolved socket bind options.
90
+ #[inline]
91
+ pub(crate) fn socket_bind(&mut self, opts: Option<SocketBindOptions>) -> &mut Self {
92
+ self.extend(GroupKey::SocketBind, GroupVariant::SocketBind(opts))
93
+ }
94
+
95
+ /// Creates a nested request group.
96
+ #[inline]
97
+ pub(crate) fn request(&mut self, group: Group) -> &mut Self {
98
+ self.extend(GroupKey::Request, GroupVariant::Request(group))
99
+ }
100
+
101
+ /// Groups the request by its emulation-layer characteristics.
102
+ #[inline]
103
+ pub(crate) fn emulate(&mut self, group: Group) -> &mut Self {
104
+ self.extend(GroupKey::Emulate, GroupVariant::Emulate(group))
105
+ }
106
+
107
+ #[inline]
108
+ fn extend<T: Into<Option<GroupVariant>>>(&mut self, id: GroupKey, entry: T) -> &mut Self {
109
+ if let Some(entry) = entry.into() {
110
+ self.0.insert(id, entry);
111
+ }
112
+ self
113
+ }
114
+ }
115
+
116
+ impl From<u64> for Group {
117
+ #[inline]
118
+ fn from(value: u64) -> Self {
119
+ Group::new(value)
120
+ }
121
+ }
122
+
123
+ impl From<&'static str> for Group {
124
+ #[inline]
125
+ fn from(value: &'static str) -> Self {
126
+ Group::new(value)
127
+ }
128
+ }
129
+
130
+ impl From<String> for Group {
131
+ #[inline]
132
+ fn from(value: String) -> Self {
133
+ Group::new(value)
134
+ }
135
+ }
136
+
137
+ impl From<Box<str>> for Group {
138
+ #[inline]
139
+ fn from(value: Box<str>) -> Self {
140
+ Group::new(value)
141
+ }
142
+ }
143
+
144
+ mod name {
145
+
146
+ /// A group identifier that can be a string or a numeric tag.
147
+ #[derive(Debug, Clone, PartialEq, Eq, Hash)]
148
+ pub enum GroupId {
149
+ Borrowed(&'static str),
150
+ Owned(Box<str>),
151
+ Number(u64),
152
+ }
153
+
154
+ impl From<&'static str> for GroupId {
155
+ #[inline]
156
+ fn from(value: &'static str) -> Self {
157
+ Self::Borrowed(value)
158
+ }
159
+ }
160
+
161
+ impl From<String> for GroupId {
162
+ #[inline]
163
+ fn from(value: String) -> Self {
164
+ Self::Owned(value.into_boxed_str())
165
+ }
166
+ }
167
+
168
+ impl From<Box<str>> for GroupId {
169
+ #[inline]
170
+ fn from(value: Box<str>) -> Self {
171
+ Self::Owned(value)
172
+ }
173
+ }
174
+
175
+ impl From<u64> for GroupId {
176
+ #[inline]
177
+ fn from(value: u64) -> Self {
178
+ Self::Number(value)
179
+ }
180
+ }
181
+ }
182
+
183
+ #[cfg(test)]
184
+ mod tests {
185
+ use std::hash::{DefaultHasher, Hash, Hasher};
186
+
187
+ use super::*;
188
+
189
+ #[test]
190
+ fn test_group_identity_invariance() {
191
+ let mut g1 = Group::default();
192
+ g1.extend(GroupKey::Named, GroupVariant::Named("worker".into()));
193
+ g1.extend(GroupKey::Version, GroupVariant::Version(Version::HTTP_2));
194
+
195
+ let mut g2 = Group::default();
196
+ g2.extend(GroupKey::Version, GroupVariant::Version(Version::HTTP_2));
197
+ g2.extend(GroupKey::Named, GroupVariant::Named("worker".into()));
198
+
199
+ let mut h1 = DefaultHasher::new();
200
+ g1.hash(&mut h1);
201
+
202
+ let mut h2 = DefaultHasher::new();
203
+ g2.hash(&mut h2);
204
+
205
+ assert_eq!(
206
+ h1.finish(),
207
+ h2.finish(),
208
+ "Request groups must maintain identical hashes regardless of criteria insertion order"
209
+ );
210
+ }
211
+ }