Skip to main content

goose_http/request/
mod.rs

1//! HTTP request representation.
2//!
3//! The concrete implementation will mirror RFC 9110 semantics, including
4//! support for request methods, target forms, and header access. This scaffold
5//! provides the basic structure needed by other modules during development.
6
7use std::time::SystemTime;
8
9use bytes::Bytes;
10
11use crate::{
12    body::Body,
13    common::{HttpVersion, Method},
14    date,
15    headers::{HeaderName, Headers, header_keys},
16};
17
18/// HTTP request target representation (RFC 9112 Section 3.2).
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum RequestTarget {
21    /// origin-form (most common): `"/path?query"`.
22    Origin(String),
23    /// absolute-form: full URI for proxies.
24    Absolute(String),
25    /// authority-form used with CONNECT: `"host:port"`.
26    Authority(String),
27    /// asterisk-form used with OPTIONS.
28    Asterisk,
29}
30
31impl RequestTarget {
32    /// Create an origin-form target.
33    pub fn origin(path: impl Into<String>) -> Self {
34        RequestTarget::Origin(path.into())
35    }
36
37    /// Returns a borrowed string representation.
38    pub fn as_str(&self) -> &str {
39        match self {
40            RequestTarget::Origin(s) | RequestTarget::Absolute(s) | RequestTarget::Authority(s) => {
41                s.as_str()
42            }
43            RequestTarget::Asterisk => "*",
44        }
45    }
46}
47
48/// Represents an HTTP request.
49#[derive(Debug, Clone)]
50pub struct Request {
51    method: Method,
52    target: RequestTarget,
53    version: HttpVersion,
54    headers: Headers,
55    body: Body,
56    payload: Bytes,
57}
58
59impl Request {
60    /// Create a new request using HTTP/1.1 as the default version.
61    pub fn new(method: Method, target: RequestTarget) -> Self {
62        Self {
63            method,
64            target,
65            version: HttpVersion::HTTP_1_1,
66            headers: Headers::new(),
67            body: Body::Empty,
68            payload: Bytes::new(),
69        }
70    }
71
72    /// Construct a request builder for incremental configuration.
73    pub fn builder(method: Method, target: RequestTarget) -> RequestBuilder {
74        RequestBuilder::new(method, target)
75    }
76
77    /// Return the request method.
78    pub fn method(&self) -> &Method {
79        &self.method
80    }
81
82    /// Return the request target.
83    pub fn target(&self) -> &RequestTarget {
84        &self.target
85    }
86
87    /// Return the HTTP version.
88    pub fn version(&self) -> HttpVersion {
89        self.version
90    }
91
92    /// Borrow the header map.
93    pub fn headers(&self) -> &Headers {
94        &self.headers
95    }
96
97    /// Retrieve the first value for the specified header name.
98    pub fn header(&self, name: impl Into<HeaderName>) -> Option<&str> {
99        self.headers.get(name)
100    }
101
102    /// Returns true if a header with the specified name is present.
103    pub fn contains_header(&self, name: impl Into<HeaderName>) -> bool {
104        self.headers.contains(name)
105    }
106
107    /// Borrow the body representation.
108    pub fn body(&self) -> &Body {
109        &self.body
110    }
111
112    /// Borrow the buffered body bytes.
113    pub fn body_bytes(&self) -> &Bytes {
114        &self.payload
115    }
116
117    /// Borrow the header map mutably.
118    pub fn headers_mut(&mut self) -> &mut Headers {
119        &mut self.headers
120    }
121
122    /// Set the HTTP version for the request.
123    pub fn set_version(&mut self, version: HttpVersion) {
124        self.version = version;
125    }
126
127    /// Set the body representation.
128    pub fn set_body(&mut self, body: Body) {
129        self.body = body;
130    }
131
132    /// Replace the buffered body bytes.
133    pub fn set_body_bytes(&mut self, bytes: impl Into<Bytes>) {
134        self.payload = bytes.into();
135    }
136
137    /// Consume the request and return the buffered body bytes.
138    pub fn into_body_bytes(self) -> Bytes {
139        self.payload
140    }
141
142    /// Remove and return the buffered body bytes, leaving an empty buffer.
143    pub fn take_body_bytes(&mut self) -> Bytes {
144        std::mem::take(&mut self.payload)
145    }
146
147    /// Whether the request includes `Expect: 100-continue` in its header section.
148    pub fn expect_100_continue(&self) -> bool {
149        self.header(header_keys::EXPECT)
150            .map(|value| {
151                value
152                    .split(',')
153                    .any(|token| token.trim().eq_ignore_ascii_case("100-continue"))
154            })
155            .unwrap_or(false)
156    }
157
158    /// Determine whether the request prefers the connection to close.
159    pub fn wants_close(&self) -> bool {
160        self.header(header_keys::CONNECTION).map_or(false, |value| {
161            value
162                .split(',')
163                .any(|token| token.trim().eq_ignore_ascii_case("close"))
164        })
165    }
166
167    /// Retrieve the If-None-Match header value trimmed of whitespace.
168    pub fn if_none_match(&self) -> Option<&str> {
169        self.header(header_keys::IF_NONE_MATCH)
170            .map(|value| value.trim())
171    }
172
173    /// Retrieve the If-Match header value trimmed of whitespace.
174    pub fn if_match(&self) -> Option<&str> {
175        self.header(header_keys::IF_MATCH).map(|value| value.trim())
176    }
177
178    /// Retrieve the If-Unmodified-Since value as `SystemTime`.
179    pub fn if_unmodified_since(&self) -> Option<SystemTime> {
180        self.header(header_keys::IF_UNMODIFIED_SINCE)
181            .and_then(|value| date::parse_http_date(value.trim()))
182    }
183
184    /// Retrieve the If-Modified-Since value as `SystemTime`.
185    pub fn if_modified_since(&self) -> Option<SystemTime> {
186        self.header(header_keys::IF_MODIFIED_SINCE)
187            .and_then(|value| date::parse_http_date(value.trim()))
188    }
189}
190
191/// Builder for `Request` values.
192#[derive(Debug, Clone)]
193pub struct RequestBuilder {
194    method: Method,
195    target: RequestTarget,
196    version: HttpVersion,
197    headers: Headers,
198    body: Body,
199    payload: Bytes,
200}
201
202impl RequestBuilder {
203    fn new(method: Method, target: RequestTarget) -> Self {
204        Self {
205            method,
206            target,
207            version: HttpVersion::HTTP_1_1,
208            headers: Headers::new(),
209            body: Body::Empty,
210            payload: Bytes::new(),
211        }
212    }
213
214    /// Override the HTTP version.
215    pub fn version(mut self, version: HttpVersion) -> Self {
216        self.version = version;
217        self
218    }
219
220    /// Insert or replace a header.
221    pub fn header(mut self, name: impl Into<HeaderName>, value: impl Into<String>) -> Self {
222        self.headers.insert(name, value);
223        self
224    }
225
226    /// Set the body representation.
227    pub fn body(mut self, body: Body) -> Self {
228        self.body = body;
229        self
230    }
231
232    /// Buffer body bytes that will be attached to the request.
233    pub fn body_bytes(mut self, bytes: impl Into<Bytes>) -> Self {
234        self.payload = bytes.into();
235        self
236    }
237
238    /// Build the request.
239    pub fn build(self) -> Request {
240        Request {
241            method: self.method,
242            target: self.target,
243            version: self.version,
244            headers: self.headers,
245            body: self.body,
246            payload: self.payload,
247        }
248    }
249}