Skip to main content

eggserve_core/primitives/
request.rs

1//! Canonical request envelope.
2//!
3//! [`Request`] combines the request head, body, and connection metadata
4//! into a single transport-independent value. This is the input type
5//! for the [`Service`](crate::server::Service) trait.
6
7use crate::primitives::connection_info::ConnectionInfo;
8use crate::primitives::request_body::RequestBody;
9use crate::primitives::request_head::RequestHead;
10
11/// A canonical, transport-independent HTTP request.
12///
13/// Combines the immutable request head, the bounded request body, and
14/// connection metadata into a single value. The runtime constructs
15/// `Request` instances from incoming connections; services receive
16/// them by value.
17///
18/// # Hyper independence
19///
20/// No Hyper type appears in this struct or its public API. The body
21/// stream is opaque behind [`RequestBody`].
22///
23/// # One-shot body
24///
25/// The body can only be consumed once, either via
26/// [`read_all`](RequestBody::read_all) or by streaming chunks.
27/// After consumption, the body is in the `Complete` state.
28#[derive(Debug)]
29pub struct Request {
30    head: RequestHead,
31    body: RequestBody,
32    connection: ConnectionInfo,
33}
34
35impl Request {
36    /// Create a new request envelope.
37    ///
38    /// Prefer using the runtime adapter (Hyper → canonical conversion)
39    /// for production use. This constructor is for tests and downstream
40    /// code that already has validated components.
41    pub fn new(head: RequestHead, body: RequestBody, connection: ConnectionInfo) -> Self {
42        Self {
43            head,
44            body,
45            connection,
46        }
47    }
48
49    /// Returns the immutable request head.
50    pub fn head(&self) -> &RequestHead {
51        &self.head
52    }
53
54    /// Returns a reference to the request body.
55    pub fn body(&self) -> &RequestBody {
56        &self.body
57    }
58
59    /// Consume the request, returning the head and body separately.
60    ///
61    /// This is useful for services that need to pass the head to one
62    /// code path and the body to another.
63    pub fn into_parts(self) -> (RequestHead, RequestBody, ConnectionInfo) {
64        (self.head, self.body, self.connection)
65    }
66
67    /// Consume the request, returning the body.
68    ///
69    /// The request head and connection info are discarded.
70    pub fn into_body(self) -> RequestBody {
71        self.body
72    }
73
74    /// Returns a reference to the connection metadata.
75    pub fn connection(&self) -> &ConnectionInfo {
76        &self.connection
77    }
78
79    /// Deconstruct the request into head and a body-bearing tuple.
80    ///
81    /// Returns `(head, body)`.
82    pub fn into_head_and_body(self) -> (RequestHead, RequestBody) {
83        (self.head, self.body)
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use crate::primitives::connection_info::{ConnectionInfo, Scheme};
91    use crate::primitives::header_block::HeaderBlock;
92    use crate::primitives::method::Method;
93    use crate::primitives::request_body::RequestBody;
94    use crate::primitives::request_target::RequestTarget;
95    use crate::primitives::version::HttpVersion;
96    use std::net::SocketAddr;
97
98    fn test_connection() -> ConnectionInfo {
99        ConnectionInfo {
100            local_addr: "127.0.0.1:8000".parse::<SocketAddr>().unwrap(),
101            remote_addr: "127.0.0.1:12345".parse::<SocketAddr>().unwrap(),
102            scheme: Scheme::Http,
103            tls: None,
104        }
105    }
106
107    fn test_head() -> RequestHead {
108        RequestHead::new(
109            Method::get(),
110            RequestTarget::parse("/test").unwrap(),
111            HttpVersion::Http11,
112            HeaderBlock::new(),
113        )
114    }
115
116    #[test]
117    fn request_construction() {
118        let req = Request::new(test_head(), RequestBody::empty(), test_connection());
119        assert_eq!(req.head().method().as_str(), "GET");
120        assert!(
121            req.body().is_complete()
122                || req.body().state() == crate::primitives::request_body::BodyState::Unread
123        );
124    }
125
126    #[test]
127    fn request_into_parts() {
128        let req = Request::new(test_head(), RequestBody::empty(), test_connection());
129        let (head, _body, conn) = req.into_parts();
130        assert_eq!(head.method().as_str(), "GET");
131        // body is empty
132        assert_eq!(conn.scheme, Scheme::Http);
133    }
134
135    #[test]
136    fn request_into_body() {
137        let req = Request::new(test_head(), RequestBody::empty(), test_connection());
138        let body = req.into_body();
139        assert!(body.declared_length().is_none() || body.declared_length() == Some(0));
140    }
141
142    #[test]
143    fn request_connection() {
144        let req = Request::new(test_head(), RequestBody::empty(), test_connection());
145        assert_eq!(req.connection().scheme, Scheme::Http);
146    }
147}