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, Split, 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<'writer, 'target, L: std::borrow::BorrowMut<usize>>(
79    out: &'writer mut Slice<'target, L>,
80    limit: usize,
81    body: impl Encode,
82) -> Result<Split<'writer, 'target, L>, storage::BuildError<consume::Error>> {
83    let mut split = out
84        .try_split(0, limit)
85        .map_err(storage::BuildError::Build)?;
86    body.encode(&mut split.body())
87        .map_err(storage::BuildError::Build)?;
88    Ok(split)
89}
90
91use std::borrow::Cow;
92
93use headers::{List, Name};
94
95pub struct Response<'field, B = [u8; 0], T = (), S = Code, N = Name<Cow<'field, str>>> {
96    pub(crate) status: S,
97    pub(crate) headers: List<'field, N>,
98    pub(crate) trailers: T,
99    pub(crate) body: B,
100}
101
102impl<'field, B, T, S, N> Response<'field, B, T, S, N> {
103    pub fn status(&self) -> S
104    where
105        S: Copy,
106    {
107        self.status
108    }
109
110    pub fn headers(&self) -> &List<'field, N> {
111        &self.headers
112    }
113
114    pub fn body(&self) -> &B {
115        &self.body
116    }
117
118    pub fn body_mut(&mut self) -> &mut B {
119        &mut self.body
120    }
121
122    pub fn into_body(self) -> B {
123        self.body
124    }
125
126    pub fn with_body<U>(self, body: U) -> Response<'field, U, T, S, N> {
127        Response {
128            status: self.status,
129            headers: self.headers,
130            trailers: self.trailers,
131            body,
132        }
133    }
134}
135
136impl<'field, B, S, N> Response<'field, B, List<'field>, S, N> {
137    pub fn trailers(&self) -> &List<'field> {
138        &self.trailers
139    }
140}
141
142impl<'field> Response<'field, [u8; 0]> {
143    pub fn new(status: Code) -> Self {
144        Self {
145            status,
146            headers: List::new(),
147            trailers: (),
148            body: [],
149        }
150    }
151}
152
153impl<'field, 'req, 'd, B> dispatch::IntoOutput<'req, 'd> for Response<'field, B, (), Code>
154where
155    B: Body<'d>,
156{
157    type Output = fixed::Direct<List<'field, Name<Cow<'field, str>>>, B>;
158
159    fn into_output(self) -> Self::Output {
160        let Self {
161            status,
162            headers,
163            trailers: (),
164            body,
165        } = self;
166        fixed::Direct::new(status, headers, body)
167    }
168}
169
170const _: () = assert!(
171    core::mem::size_of::<Response<'static, [u8; 0]>>()
172        < core::mem::size_of::<Response<'static, [u8; 0], List<'static>, status::Code>>()
173);