implement high-level getters, add comments, improve tests

This commit is contained in:
yggverse 2025-03-25 07:36:48 +02:00
parent 46da3a031a
commit 8ee088270f
3 changed files with 86 additions and 25 deletions

View file

@ -24,14 +24,53 @@ impl Success {
Err(e) => Err(Error::Default(e)),
}
}
// Getters
/// Get header bytes for `Self` type
pub fn as_header_bytes(&self) -> &[u8] {
match self {
Self::Default(default) => default.header.as_bytes(),
}
}
/// Get header string for `Self` type
pub fn as_header_str(&self) -> &str {
match self {
Self::Default(default) => default.header.as_str(),
}
}
/// Get parsed MIME for `Self` type
///
/// * high-level method, useful to skip extra match case constructions;
/// * at this moment, Gemini protocol has only one status code in this scope,\
/// this method would be deprecated in future, use on your own risk!
pub fn mime(&self) -> Result<String, Error> {
match self {
Self::Default(default) => default
.header
.mime()
.map_err(|e| Error::Default(default::Error::Header(e))),
}
}
}
#[test]
fn test() {
match Success::from_utf8("20 text/gemini; charset=utf-8; lang=en\r\n".as_bytes()).unwrap() {
Success::Default(default) => {
assert_eq!(default.header.mime().unwrap(), "text/gemini");
assert_eq!(default.content, None)
let r = "20 text/gemini; charset=utf-8; lang=en\r\n";
let b = r.as_bytes();
let s = Success::from_utf8(b).unwrap();
match s {
Success::Default(ref d) => {
assert_eq!(d.header.mime().unwrap(), "text/gemini");
assert!(d.content.is_empty())
}
}
assert_eq!(s.as_header_bytes(), b);
assert_eq!(s.as_header_str(), r);
assert_eq!(s.mime().unwrap(), "text/gemini");
assert!(Success::from_utf8("40 text/gemini; charset=utf-8; lang=en\r\n".as_bytes()).is_err())
}

View file

@ -11,13 +11,18 @@ pub const CODE: &[u8] = b"20";
/// * this response type MAY contain body data
/// * the header has closed members to require valid construction
pub struct Default {
/// Formatted header holder with additional API
pub header: Header,
pub content: Option<Vec<u8>>,
/// Default success response MAY include body data
/// * if the `Request` constructed with `Mode::HeaderOnly` flag,\
/// this value wants to be processed manually, using external application logic (specific for content-type)
pub content: Vec<u8>,
}
impl Default {
// Constructors
/// Parse `Self` from buffer contains header bytes
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
if !buffer.starts_with(CODE) {
return Err(Error::Code);
@ -25,9 +30,9 @@ impl Default {
let header = Header::from_utf8(buffer).map_err(Error::Header)?;
Ok(Self {
content: buffer
.get(header.len() + 1..)
.get(header.as_bytes().len()..)
.filter(|s| !s.is_empty())
.map(|v| v.to_vec()),
.map_or(Vec::new(), |v| v.to_vec()),
header,
})
}
@ -35,8 +40,12 @@ impl Default {
#[test]
fn test() {
let default =
Default::from_utf8("20 text/gemini; charset=utf-8; lang=en\r\n".as_bytes()).unwrap();
assert_eq!(default.header.mime().unwrap(), "text/gemini");
assert_eq!(default.content, None)
let d = Default::from_utf8("20 text/gemini; charset=utf-8; lang=en\r\n".as_bytes()).unwrap();
assert_eq!(d.header.mime().unwrap(), "text/gemini");
assert!(d.content.is_empty());
let d =
Default::from_utf8("20 text/gemini; charset=utf-8; lang=en\r\ndata".as_bytes()).unwrap();
assert_eq!(d.header.mime().unwrap(), "text/gemini");
assert_eq!(d.content.len(), 4);
}

View file

@ -1,19 +1,22 @@
pub mod error;
pub use error::Error;
pub struct Header(Vec<u8>);
pub struct Header(String);
impl Header {
// Constructors
/// Parse `Self` from buffer contains header bytes
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
if !buffer.starts_with(super::CODE) {
return Err(Error::Code);
}
Ok(Self(
crate::client::connection::response::header_bytes(buffer)
.map_err(Error::Header)?
.to_vec(),
std::str::from_utf8(
crate::client::connection::response::header_bytes(buffer).map_err(Error::Header)?,
)
.map_err(Error::Utf8Error)?
.to_string(),
))
}
@ -23,7 +26,7 @@ impl Header {
pub fn mime(&self) -> Result<String, Error> {
glib::Regex::split_simple(
r"^\d{2}\s([^\/]+\/[^\s;]+)",
std::str::from_utf8(&self.0).map_err(Error::Utf8Error)?,
&self.0,
glib::RegexCompileFlags::DEFAULT,
glib::RegexMatchFlags::DEFAULT,
)
@ -33,15 +36,25 @@ impl Header {
.map_or(Err(Error::Mime), |s| Ok(s.to_lowercase()))
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Get header bytes of `Self`
pub fn as_bytes(&self) -> &[u8] {
&self.0
self.0.as_bytes()
}
/// Get header string of `Self`
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
#[test]
fn test() {
let s = "20 text/gemini; charset=utf-8; lang=en\r\n";
let b = s.as_bytes();
let h = Header::from_utf8(b).unwrap();
assert_eq!(h.mime().unwrap(), "text/gemini");
assert_eq!(h.as_bytes(), b);
assert_eq!(h.as_str(), s);
assert!(Header::from_utf8("21 text/gemini; charset=utf-8; lang=en\r\n".as_bytes()).is_err());
}