Skip to main content

sark_http/payload/response/
mod.rs

1//! Response state, encoding, and ownership handoff.
2
3use o3::buffer::{
4    storage,
5    write::{Sink, Slice, consume},
6};
7
8use crate::payload::response::status::Code;
9
10mod body;
11pub mod bounded;
12pub mod dispatch;
13pub mod exact;
14pub mod fixed;
15pub mod headers;
16pub mod statics;
17pub mod status;
18
19pub use body::Body;
20
21/// Request methods whose semantics affect response content.
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23#[repr(u8)]
24pub enum Method {
25    Other,
26    Head,
27    Connect,
28}
29
30impl Method {
31    pub fn from_bytes(method: &[u8]) -> Self {
32        match method.len() {
33            4 if method == b"HEAD" => Self::Head,
34            7 if method == b"CONNECT" => Self::Connect,
35            0..=3 | 5..=6 | 8.. => Self::Other,
36            4 | 7 => Self::Other,
37        }
38    }
39}
40
41/// Protocol-independent content semantics of a response.
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub enum Content {
44    /// Content and body-length framing fields are forbidden.
45    Forbidden,
46    /// Content is omitted, but representation length metadata is permitted.
47    Suppressed,
48    /// A successful CONNECT establishes a tunnel rather than message content.
49    Tunnel,
50    /// Content must be empty, while empty-message framing is permitted.
51    Empty,
52    Allowed,
53}
54
55impl Content {
56    pub const fn new(method: Method, status: u16) -> Self {
57        if matches!(method, Method::Connect) && status >= 200 && status < 300 {
58            Self::Tunnel
59        } else if status < 200 || status == 204 {
60            Self::Forbidden
61        } else if matches!(method, Method::Head) || status == 304 {
62            Self::Suppressed
63        } else if status == 205 {
64            Self::Empty
65        } else {
66            Self::Allowed
67        }
68    }
69}
70
71/// Consumes one body into the admitted output without repeating its size check.
72pub trait Encode: Sized {
73    fn encode<W>(self, out: &mut W) -> Result<(), consume::Error>
74    where
75        W: Sink<Error = consume::Error>;
76}
77
78fn try_encode(
79    out: &mut [u8],
80    limit: usize,
81    body: impl Encode,
82) -> Result<Slice<'_>, storage::BuildError<consume::Error>> {
83    consume::Error::check(limit, out.len()).map_err(storage::BuildError::Build)?;
84    let mut writer = Slice::new(&mut out[..limit]);
85    body.encode(&mut writer)
86        .map_err(storage::BuildError::Build)?;
87    Ok(writer)
88}
89
90use std::borrow::Cow;
91
92use headers::{List, Name};
93
94pub struct Response<'field, B = [u8; 0], T = (), S = Code, N = Name<Cow<'field, str>>> {
95    pub(crate) status: S,
96    pub(crate) headers: List<'field, N>,
97    pub(crate) trailers: T,
98    pub(crate) body: B,
99}
100
101impl<'field, B, T, S, N> Response<'field, B, T, S, N> {
102    pub fn status(&self) -> S
103    where
104        S: Copy,
105    {
106        self.status
107    }
108
109    pub fn headers(&self) -> &List<'field, N> {
110        &self.headers
111    }
112
113    pub fn body(&self) -> &B {
114        &self.body
115    }
116
117    pub fn body_mut(&mut self) -> &mut B {
118        &mut self.body
119    }
120
121    pub fn into_body(self) -> B {
122        self.body
123    }
124
125    pub fn with_body<U>(self, body: U) -> Response<'field, U, T, S, N> {
126        Response {
127            status: self.status,
128            headers: self.headers,
129            trailers: self.trailers,
130            body,
131        }
132    }
133}
134
135impl<'field, B, S, N> Response<'field, B, List<'field>, S, N> {
136    pub fn trailers(&self) -> &List<'field> {
137        &self.trailers
138    }
139}
140
141impl<'field> Response<'field, [u8; 0]> {
142    pub fn new(status: Code) -> Self {
143        Self {
144            status,
145            headers: List::new(),
146            trailers: (),
147            body: [],
148        }
149    }
150}
151
152impl<'field, 'req, 'd, B> dispatch::IntoOutput<'req, 'd> for Response<'field, B, (), Code>
153where
154    B: Body<'d>,
155{
156    type Output = fixed::Direct<List<'field, Name<Cow<'field, str>>>, B>;
157
158    fn into_output(self) -> Self::Output {
159        let Self {
160            status,
161            headers,
162            trailers: (),
163            body,
164        } = self;
165        fixed::Direct::new(status, headers, body)
166    }
167}
168
169const _: () = assert!(
170    core::mem::size_of::<Response<'static, [u8; 0]>>()
171        < core::mem::size_of::<Response<'static, [u8; 0], List<'static>, status::Code>>()
172);