sark-core 0.2.1

the L7 layer for dope
Documentation
use http::{HeaderName, HeaderValue, StatusCode};
use o3::buffer::{Owned, Shared};
use serde::Serialize;

use super::{Body, BodyInner, FixedResponseInner, HeaderList, IntoBody, MonoResponseInner};

#[derive(Clone)]
pub struct Response {
    pub(super) status: StatusCode,
    pub(super) headers: HeaderList,
    pub(super) wire_headers: Owned,
    pub(super) body: Body,
    pub(super) chunked_parts: Option<Vec<Shared>>,
}

impl std::fmt::Debug for Response {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Response")
            .field("status", &self.status)
            .field("headers", &self.headers)
            .field("wire_headers_len", &self.wire_headers.len())
            .field("body_len", &self.body.len())
            .field("chunked", &self.chunked_parts.is_some())
            .finish()
    }
}

impl Response {
    pub fn new(status: StatusCode) -> Self {
        Self {
            status,
            headers: HeaderList::new(),
            wire_headers: Owned::new(),
            body: Body::empty(),
            chunked_parts: None,
        }
    }

    pub fn ok() -> Self {
        Self::new(StatusCode::OK)
    }

    pub fn text(body: &str) -> Self {
        Self::text_with_status(StatusCode::OK, body)
    }

    pub fn text_with_status(status: StatusCode, body: &str) -> Self {
        let mut resp = Self::new(status);
        resp.content_type("text/plain");
        resp.set_body_str(body);
        resp
    }

    pub fn json<T: Serialize>(value: &T) -> Result<Self, serde_json::Error> {
        Self::json_with_status(StatusCode::OK, value)
    }

    pub fn json_with_status<T: Serialize>(
        status: StatusCode,
        value: &T,
    ) -> Result<Self, serde_json::Error> {
        let s = serde_json::to_string(value)?;
        let mut resp = Self::new(status);
        resp.content_type("application/json");
        resp.set_body_str(&s);
        Ok(resp)
    }

    pub fn insert_header(&mut self, name: HeaderName, value: HeaderValue) -> &mut Self {
        let _ = self.headers.insert(name, value);
        self
    }

    pub fn append_wire_header(&mut self, name: &'static str, value: &str) -> &mut Self {
        Self::assert_wire_header_name(name);
        assert!(
            !value.as_bytes().iter().any(|b| *b == b'\r' || *b == b'\n'),
            "wire header value must not contain CR/LF"
        );
        self.append_wire_header_bytes(name, value.as_bytes())
    }

    pub fn append_wire_header_static(
        &mut self,
        name: &'static str,
        value: &'static str,
    ) -> &mut Self {
        Self::assert_wire_header_name(name);
        self.append_wire_header_bytes(name, value.as_bytes())
    }

    fn append_wire_header_bytes(&mut self, name: &str, value: &[u8]) -> &mut Self {
        self.wire_headers.extend_from_slice(name.as_bytes());
        self.wire_headers.extend_from_slice(b": ");
        self.wire_headers.extend_from_slice(value);
        self.wire_headers.extend_from_slice(b"\r\n");
        self
    }

    pub fn content_type(&mut self, value: &'static str) -> &mut Self {
        let _ = self
            .headers
            .insert("content-type", HeaderValue::from_static(value));
        self
    }

    pub fn not_found() -> Self {
        Self::new(StatusCode::NOT_FOUND)
    }

    pub fn status(&self) -> StatusCode {
        self.status
    }

    pub fn set_status(&mut self, status: StatusCode) {
        self.status = status;
    }

    pub fn headers(&self) -> &HeaderList {
        &self.headers
    }

    pub fn headers_mut(&mut self) -> &mut HeaderList {
        &mut self.headers
    }

    pub fn wire_headers(&self) -> &[u8] {
        self.wire_headers.as_ref()
    }

    pub fn has_wire_headers(&self) -> bool {
        !self.wire_headers.is_empty()
    }

    pub fn body(&self) -> &[u8] {
        self.body.as_bytes()
    }

    pub fn body_is_shared(&self) -> bool {
        self.body.is_shared()
    }

    pub fn body_mut(&mut self) -> &mut Owned {
        self.body.as_owned_mut()
    }

    pub fn into_body(self) -> Owned {
        self.body.into_owned()
    }

    pub fn into_body_bytes(self) -> Shared {
        self.body.into_bytes()
    }

    pub fn set_body<B>(&mut self, body: B)
    where
        B: IntoBody<'static>,
    {
        self.body = body.into_response_body();
    }

    pub fn set_body_str(&mut self, body: &str) -> &mut Self {
        self.body = BodyInner::Owned(Owned::from(body.as_bytes()));
        self
    }

    pub fn body_str(&self) -> Option<&str> {
        std::str::from_utf8(self.body.as_bytes()).ok()
    }

    pub fn push_chunk(&mut self, data: impl Into<Shared>) {
        self.chunked_parts
            .get_or_insert_with(Vec::new)
            .push(data.into());
    }

    pub fn chunked_parts(&self) -> Option<&[Shared]> {
        self.chunked_parts.as_deref()
    }

    pub fn is_chunked(&self) -> bool {
        self.chunked_parts.is_some()
    }

    fn assert_wire_header_name(name: &str) {
        assert!(!name.is_empty(), "wire header name must not be empty");
        assert!(
            !name
                .as_bytes()
                .iter()
                .any(|b| *b == b':' || *b == b'\r' || *b == b'\n'),
            "wire header name must not contain separators"
        );
        assert!(
            !matches!(
                name,
                "date" | "server" | "content-length" | "connection" | "transfer-encoding"
            ),
            "wire header must not override managed headers: {name}"
        );
    }
}

impl From<MonoResponseInner<'static>> for Response {
    fn from(response: MonoResponseInner<'static>) -> Self {
        Self {
            status: response.status,
            headers: response.headers.map(|h| *h).unwrap_or_default(),
            wire_headers: Owned::from(response.head.into_bytes().as_ref()),
            body: BodyInner::from(response.body),
            chunked_parts: None,
        }
    }
}

impl From<FixedResponseInner<'static>> for Response {
    fn from(response: FixedResponseInner<'static>) -> Self {
        Self {
            status: response.status,
            headers: HeaderList::new(),
            wire_headers: Owned::from(response.wire_headers().as_ref()),
            body: BodyInner::Shared(response.body),
            chunked_parts: None,
        }
    }
}