use anyhow::{bail, Result}; pub const CODE: &[u8] = b"20"; /// [Success](https://geminiprotocol.net/docs/protocol-specification.gmi#success) pub struct Default<'a> { pub mime: String, pub data: &'a [u8], } impl<'a> Default<'a> { /// Build `Self` from UTF-8 header bytes /// * expected buffer includes leading status code, message, CRLF pub fn from_bytes(buffer: &'a [u8]) -> Result { use crate::Header; use regex::Regex; use std::str::from_utf8; 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 { mime: match Regex::new(r"^([^\/]+\/[^\s;]+)")?.captures(from_utf8(&h[3..])?) { Some(c) => match c.get(1) { Some(m) => m.as_str().to_string(), None => bail!("Content type required"), }, None => bail!("Could not parse content type"), }, data: &buffer[h.len() + 2..], }) } /// Convert `Self` into UTF-8 bytes presentation pub fn into_bytes(self) -> Vec { let mut bytes = Vec::with_capacity(3 + self.mime.len() + 2 + self.data.len()); bytes.extend(CODE); bytes.push(b' '); bytes.extend(self.mime.into_bytes()); bytes.extend([b'\r', b'\n']); bytes.extend(self.data); bytes } } #[test] fn test() { let source = format!("20 text/gemini\r\ndata"); let target = Default::from_bytes(source.as_bytes()).unwrap(); assert_eq!(target.mime, "text/gemini".to_string()); assert_eq!(target.data, "data".as_bytes()); assert_eq!(target.into_bytes(), source.as_bytes()); }