oddity-rtsp-protocol 0.2.0

RTSP protocol implementation for oddity-rtsp-server.
Documentation
use std::fmt;

use super::{
    message::{
        status_to_code, status_to_reason, Bytes, Headers, Message, Status, StatusCategory,
        StatusCode, Version,
    },
    request::Request,
    rtp_info::RtpInfo,
};

#[derive(Clone, Debug)]
pub struct Response {
    pub version: Version,
    pub status: StatusCode,
    pub reason: String,
    pub headers: Headers,
    pub body: Option<Bytes>,
}

impl Message for Response {
    type Metadata = ResponseMetadata;

    fn new(metadata: ResponseMetadata, headers: Headers, body: Option<Bytes>) -> Self {
        Self {
            version: metadata.version,
            status: metadata.status,
            reason: metadata.reason,
            headers,
            body,
        }
    }
}

impl Response {
    pub fn ok() -> ResponseBuilder {
        ResponseBuilder::ok()
    }

    pub fn error(status: Status) -> ResponseBuilder {
        ResponseBuilder::error(status)
    }

    pub fn status(&self) -> StatusCategory {
        match self.status {
            s if s >= 600 => StatusCategory::Unknown,
            s if s >= 500 => StatusCategory::ServerError,
            s if s >= 400 => StatusCategory::ClientError,
            s if s >= 300 => StatusCategory::Redirection,
            s if s >= 200 => StatusCategory::Success,
            s if s >= 100 => StatusCategory::Informational,
            _ => StatusCategory::Unknown,
        }
    }
}

impl fmt::Display for Response {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "Version: {}, Status Code: {}, Reason Phrase: {}",
            self.version, self.status, &self.reason
        )?;

        if !self.headers.is_empty() {
            writeln!(f, "\nHeaders:")?;
            for (var, val) in &self.headers {
                writeln!(f, " - {}: {}", &var, &val)?;
            }
        }

        if let Some(body) = &self.body {
            writeln!(f, "[{} bytes]", body.len())?;
        }

        Ok(())
    }
}

#[derive(Clone, Debug)]
pub struct ResponseMetadata {
    version: Version,
    status: StatusCode,
    reason: String,
}

impl ResponseMetadata {
    pub(super) fn new(version: Version, status: StatusCode, reason: String) -> Self {
        Self {
            version,
            status,
            reason,
        }
    }
}

pub struct ResponseBuilder {
    response: Response,
}

impl ResponseBuilder {
    pub fn from_status(status: Status) -> ResponseBuilder {
        ResponseBuilder {
            response: Response {
                version: Default::default(),
                status: status_to_code(status),
                reason: status_to_reason(status).to_string(),
                headers: Default::default(),
                body: Default::default(),
            },
        }
    }

    pub fn ok() -> ResponseBuilder {
        Self::from_status(Status::Ok)
    }

    pub fn error(status: Status) -> ResponseBuilder {
        Self::from_status(status)
    }

    pub fn with_cseq_of(mut self, request: &Request) -> ResponseBuilder {
        if let Some(cseq) = request.headers.get("CSeq") {
            self.response
                .headers
                .insert("CSeq".to_string(), cseq.to_string());
        }
        self
    }

    pub fn with_header(mut self, var: impl ToString, val: impl ToString) -> ResponseBuilder {
        self.response
            .headers
            .insert(var.to_string(), val.to_string());
        self
    }

    pub fn with_rtp_info(mut self, rtp_info: impl IntoIterator<Item = RtpInfo>) -> ResponseBuilder {
        self.response.headers.insert(
            "RTP-Info".to_string(),
            rtp_info
                .into_iter()
                .map(|item| item.to_string())
                .collect::<Vec<_>>()
                .join(","),
        );
        self
    }

    pub fn with_body(mut self, body: Bytes, content_type: &str) -> ResponseBuilder {
        self = self
            .with_header("Content-Length", body.len())
            .with_header("Content-Type", content_type);
        self.response.body = Some(body);
        self
    }

    pub fn with_sdp(self, contents: String) -> ResponseBuilder {
        self.with_body(Bytes::from(contents), "application/sdp")
    }

    pub fn build(self) -> Response {
        self.response
    }
}