titanite 0.3.0

Client/Server Library for Gemini protocol with Titan support
Documentation
use anyhow::{bail, Result};

pub const CODE: &[u8] = b"31";

/// [Permanent redirect](https://geminiprotocol.net/docs/protocol-specification.gmi#status-31-permanent-redirection)
pub struct Permanent {
    pub target: String,
}

impl Permanent {
    /// Build `Self` from UTF-8 header bytes
    /// * expected buffer includes leading status code, message, CRLF
    pub fn from_bytes(buffer: &[u8]) -> Result<Self> {
        use crate::tool::Header;
        let h = buffer.header_bytes()?;
        if h.get(..2)
            .is_none_or(|c| c[0] != CODE[0] || c[1] != CODE[1])
        {
            bail!("Invalid status code")
        }
        Ok(Self {
            target: if h.is_empty() {
                bail!("Target required")
            } else {
                String::from_utf8((h[3..]).to_vec())?
            },
        })
    }

    /// Convert `Self` into UTF-8 bytes presentation
    pub fn into_bytes(self) -> Vec<u8> {
        let mut bytes = Vec::with_capacity(self.target.len() + 5);
        bytes.extend(CODE);
        bytes.push(b' ');
        bytes.extend(self.target.into_bytes());
        bytes.extend([b'\r', b'\n']);
        bytes
    }
}

#[test]
fn test() {
    let request = format!("31 target\r\n");
    let source = Permanent::from_bytes(request.as_bytes()).unwrap();

    assert_eq!(source.target, "target".to_string());
    assert_eq!(source.into_bytes(), request.as_bytes());
}