mirror of
https://github.com/YGGverse/ggemini.git
synced 2026-03-31 17:15:31 +00:00
implement high-level getters, add comments, improve tests
This commit is contained in:
parent
46da3a031a
commit
8ee088270f
3 changed files with 86 additions and 25 deletions
|
|
@ -24,14 +24,53 @@ impl Success {
|
||||||
Err(e) => Err(Error::Default(e)),
|
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]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
match Success::from_utf8("20 text/gemini; charset=utf-8; lang=en\r\n".as_bytes()).unwrap() {
|
let r = "20 text/gemini; charset=utf-8; lang=en\r\n";
|
||||||
Success::Default(default) => {
|
let b = r.as_bytes();
|
||||||
assert_eq!(default.header.mime().unwrap(), "text/gemini");
|
let s = Success::from_utf8(b).unwrap();
|
||||||
assert_eq!(default.content, None)
|
|
||||||
|
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())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,18 @@ pub const CODE: &[u8] = b"20";
|
||||||
/// * this response type MAY contain body data
|
/// * this response type MAY contain body data
|
||||||
/// * the header has closed members to require valid construction
|
/// * the header has closed members to require valid construction
|
||||||
pub struct Default {
|
pub struct Default {
|
||||||
|
/// Formatted header holder with additional API
|
||||||
pub header: Header,
|
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 {
|
impl Default {
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
|
/// Parse `Self` from buffer contains header bytes
|
||||||
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
||||||
if !buffer.starts_with(CODE) {
|
if !buffer.starts_with(CODE) {
|
||||||
return Err(Error::Code);
|
return Err(Error::Code);
|
||||||
|
|
@ -25,9 +30,9 @@ impl Default {
|
||||||
let header = Header::from_utf8(buffer).map_err(Error::Header)?;
|
let header = Header::from_utf8(buffer).map_err(Error::Header)?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
content: buffer
|
content: buffer
|
||||||
.get(header.len() + 1..)
|
.get(header.as_bytes().len()..)
|
||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
.map(|v| v.to_vec()),
|
.map_or(Vec::new(), |v| v.to_vec()),
|
||||||
header,
|
header,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -35,8 +40,12 @@ impl Default {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
let default =
|
let d = Default::from_utf8("20 text/gemini; charset=utf-8; lang=en\r\n".as_bytes()).unwrap();
|
||||||
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_eq!(default.header.mime().unwrap(), "text/gemini");
|
assert!(d.content.is_empty());
|
||||||
assert_eq!(default.content, None)
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,22 @@
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
|
|
||||||
pub struct Header(Vec<u8>);
|
pub struct Header(String);
|
||||||
|
|
||||||
impl Header {
|
impl Header {
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
|
/// Parse `Self` from buffer contains header bytes
|
||||||
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
||||||
if !buffer.starts_with(super::CODE) {
|
if !buffer.starts_with(super::CODE) {
|
||||||
return Err(Error::Code);
|
return Err(Error::Code);
|
||||||
}
|
}
|
||||||
Ok(Self(
|
Ok(Self(
|
||||||
crate::client::connection::response::header_bytes(buffer)
|
std::str::from_utf8(
|
||||||
.map_err(Error::Header)?
|
crate::client::connection::response::header_bytes(buffer).map_err(Error::Header)?,
|
||||||
.to_vec(),
|
)
|
||||||
|
.map_err(Error::Utf8Error)?
|
||||||
|
.to_string(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,7 +26,7 @@ impl Header {
|
||||||
pub fn mime(&self) -> Result<String, Error> {
|
pub fn mime(&self) -> Result<String, Error> {
|
||||||
glib::Regex::split_simple(
|
glib::Regex::split_simple(
|
||||||
r"^\d{2}\s([^\/]+\/[^\s;]+)",
|
r"^\d{2}\s([^\/]+\/[^\s;]+)",
|
||||||
std::str::from_utf8(&self.0).map_err(Error::Utf8Error)?,
|
&self.0,
|
||||||
glib::RegexCompileFlags::DEFAULT,
|
glib::RegexCompileFlags::DEFAULT,
|
||||||
glib::RegexMatchFlags::DEFAULT,
|
glib::RegexMatchFlags::DEFAULT,
|
||||||
)
|
)
|
||||||
|
|
@ -33,15 +36,25 @@ impl Header {
|
||||||
.map_or(Err(Error::Mime), |s| Ok(s.to_lowercase()))
|
.map_or(Err(Error::Mime), |s| Ok(s.to_lowercase()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
/// Get header bytes of `Self`
|
||||||
self.0.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.0.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_bytes(&self) -> &[u8] {
|
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());
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue