Skip to main content

ferrijs_fetch/
model.rs

1//! The WHATWG request/response model the engine sends and returns.
2//!
3//! [`Request`] is fully resolved before it reaches the engine (absolute
4//! URL, params appended, headers assembled, body materialized): the
5//! engine is format-agnostic. [`Response`] carries the metadata plus a
6//! single-use [`Body`].
7
8use std::time::Duration;
9
10use super::body::Body;
11use super::headers::Headers;
12use super::net_guard::NetGuard;
13
14/// How a redirect response is handled, mirroring WHATWG Fetch's
15/// `RequestInit.redirect` (`follow` | `manual` | `error`).
16#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
17pub enum RedirectMode {
18  /// Follow up to the redirect cap ([`Request::max_redirects`]), then
19  /// error (the default).
20  #[default]
21  Follow,
22  /// Do not follow: the 3xx response is returned as-is (the JS layer
23  /// turns this into an opaque-redirect `Response`).
24  Manual,
25  /// Treat any redirect as a network error.
26  Error,
27}
28
29/// WHATWG `RequestCredentials` — whether stored cookies ride the request.
30#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
31pub enum Credentials {
32  /// Never send credentials (cookies).
33  Omit,
34  /// Send credentials (the default for this engine, and what Playwright's
35  /// `request` always does — the browser context is the jar).
36  #[default]
37  SameOrigin,
38  /// Send credentials on cross-origin requests too. Same behaviour as
39  /// `SameOrigin` here since the engine has a single origin scope.
40  Include,
41}
42
43/// WHATWG `Response.type`: how the response was filtered.
44#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
45pub enum ResponseType {
46  /// A same-origin (unfiltered) response.
47  #[default]
48  Basic,
49  /// A CORS-filtered response.
50  Cors,
51  /// An opaque cross-origin `no-cors` response.
52  Opaque,
53  /// An opaque `redirect: manual` 3xx.
54  OpaqueRedirect,
55  /// A network-error response (`Response.error()`).
56  Error,
57  /// A constructed response with no filtering applied.
58  Default,
59}
60
61impl ResponseType {
62  #[must_use]
63  pub fn as_str(self) -> &'static str {
64    match self {
65      Self::Basic => "basic",
66      Self::Cors => "cors",
67      Self::Opaque => "opaque",
68      Self::OpaqueRedirect => "opaqueredirect",
69      Self::Error => "error",
70      Self::Default => "default",
71    }
72  }
73}
74
75/// Resolved peer address of a response. Mirrors Playwright's
76/// `RemoteAddr` (`{ ipAddress, port }`) returned by
77/// `apiResponse.serverAddr()` / `response.serverAddr()`.
78#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
79pub struct RemoteAddr {
80  #[serde(rename = "ipAddress")]
81  pub ip_address: String,
82  pub port: u16,
83}
84
85/// A fully-resolved request the engine can send as-is.
86#[derive(Debug)]
87pub struct Request {
88  pub method: reqwest::Method,
89  /// Absolute URL with query params already appended.
90  pub url: reqwest::Url,
91  /// Assembled request headers (content-type already set for bodies).
92  pub headers: Headers,
93  pub body: Body,
94  pub redirect: RedirectMode,
95  pub credentials: Credentials,
96  /// Redirect cap for `redirect: follow`: `Some(0)` = don't follow,
97  /// `Some(n)` = follow up to `n`, `None` = the engine default (20).
98  pub max_redirects: Option<u32>,
99  /// Retry the request on a connection reset up to this many times.
100  pub max_retries: u32,
101  pub timeout: Duration,
102  /// Resolved TLS posture for this request.
103  pub ignore_https_errors: bool,
104  /// Sandbox network policy, enforced on the initial URL, every redirect
105  /// hop, and every resolved address when active.
106  pub net_guard: Option<NetGuard>,
107}
108
109/// The engine's response: metadata plus a single-use body.
110#[derive(Debug)]
111pub struct Response {
112  pub status: u16,
113  pub status_text: String,
114  /// Final URL after any followed redirects.
115  pub url: String,
116  pub headers: Headers,
117  pub body: Body,
118  /// Whether at least one redirect hop was followed.
119  pub redirected: bool,
120  /// Whether a 3xx was returned unfollowed because `redirect: manual`.
121  pub unfollowed_redirect: bool,
122  pub server_addr: Option<RemoteAddr>,
123  pub type_: ResponseType,
124}
125
126impl Response {
127  #[must_use]
128  pub fn ok(&self) -> bool {
129    (200..300).contains(&self.status)
130  }
131}