mirror of
https://github.com/YGGverse/ggemini.git
synced 2026-03-31 17:15:31 +00:00
update temporary status codes api
This commit is contained in:
parent
c9a59e76ee
commit
ea1fb8ea66
13 changed files with 682 additions and 152 deletions
|
|
@ -38,10 +38,52 @@ impl Failure {
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
|
|
||||||
|
/// Get optional message for `Self`
|
||||||
|
/// * return `None` if the message is empty
|
||||||
pub fn message(&self) -> Option<&str> {
|
pub fn message(&self) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
Self::Permanent(permanent) => permanent.message(),
|
Self::Permanent(permanent) => permanent.message(),
|
||||||
Self::Temporary(temporary) => temporary.message(),
|
Self::Temporary(temporary) => temporary.message(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get optional message for `Self`
|
||||||
|
/// * if the optional message not provided by the server, return children `DEFAULT_MESSAGE`
|
||||||
|
pub fn message_or_default(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Permanent(permanent) => permanent.message_or_default(),
|
||||||
|
Self::Temporary(temporary) => temporary.message_or_default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get header string of `Self`
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Permanent(permanent) => permanent.as_str(),
|
||||||
|
Self::Temporary(temporary) => temporary.as_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get header bytes of `Self`
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
match self {
|
||||||
|
Self::Permanent(permanent) => permanent.as_bytes(),
|
||||||
|
Self::Temporary(temporary) => temporary.as_bytes(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
fn t(source: String, message: Option<&str>) {
|
||||||
|
let b = source.as_bytes();
|
||||||
|
let i = Failure::from_utf8(b).unwrap();
|
||||||
|
assert_eq!(i.message(), message);
|
||||||
|
assert_eq!(i.as_str(), source);
|
||||||
|
assert_eq!(i.as_bytes(), b);
|
||||||
|
}
|
||||||
|
for code in [40, 41, 42, 43, 44, 50, 51, 52, 53, 59] {
|
||||||
|
t(format!("{code} Message\r\n"), Some("Message"));
|
||||||
|
t(format!("{code}\r\n"), None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,31 @@
|
||||||
|
pub mod cgi_error;
|
||||||
|
pub mod default;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub use error::Error;
|
pub mod proxy_error;
|
||||||
|
pub mod server_unavailable;
|
||||||
|
pub mod slow_down;
|
||||||
|
|
||||||
const DEFAULT: (u8, &str) = (40, "Unspecified");
|
pub use cgi_error::CgiError;
|
||||||
const SERVER_UNAVAILABLE: (u8, &str) = (41, "Server unavailable");
|
pub use default::Default;
|
||||||
const CGI_ERROR: (u8, &str) = (42, "CGI error");
|
pub use error::Error;
|
||||||
const PROXY_ERROR: (u8, &str) = (43, "Proxy error");
|
pub use proxy_error::ProxyError;
|
||||||
const SLOW_DOWN: (u8, &str) = (44, "Slow down");
|
pub use server_unavailable::ServerUnavailable;
|
||||||
|
pub use slow_down::SlowDown;
|
||||||
|
|
||||||
|
const CODE: u8 = b'4';
|
||||||
|
|
||||||
/// https://geminiprotocol.net/docs/protocol-specification.gmi#temporary-failure
|
/// https://geminiprotocol.net/docs/protocol-specification.gmi#temporary-failure
|
||||||
pub enum Temporary {
|
pub enum Temporary {
|
||||||
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-40
|
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-40
|
||||||
Default { message: Option<String> },
|
Default(Default),
|
||||||
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-41-server-unavailable
|
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-41-server-unavailable
|
||||||
ServerUnavailable { message: Option<String> },
|
ServerUnavailable(ServerUnavailable),
|
||||||
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-42-cgi-error
|
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-42-cgi-error
|
||||||
CgiError { message: Option<String> },
|
CgiError(CgiError),
|
||||||
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-43-proxy-error
|
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-43-proxy-error
|
||||||
ProxyError { message: Option<String> },
|
ProxyError(ProxyError),
|
||||||
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-44-slow-down
|
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-44-slow-down
|
||||||
SlowDown { message: Option<String> },
|
SlowDown(SlowDown),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Temporary {
|
impl Temporary {
|
||||||
|
|
@ -26,154 +33,94 @@ impl Temporary {
|
||||||
|
|
||||||
/// Create new `Self` from buffer include header bytes
|
/// Create new `Self` from buffer include header bytes
|
||||||
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
||||||
use std::str::FromStr;
|
match buffer.first() {
|
||||||
match std::str::from_utf8(buffer) {
|
Some(b) => match *b {
|
||||||
Ok(header) => Self::from_str(header),
|
CODE => match buffer.get(1) {
|
||||||
Err(e) => Err(Error::Utf8Error(e)),
|
Some(b) => match *b {
|
||||||
|
b'0' => Ok(Self::Default(
|
||||||
|
Default::from_utf8(buffer).map_err(Error::Default)?,
|
||||||
|
)),
|
||||||
|
b'1' => Ok(Self::ServerUnavailable(
|
||||||
|
ServerUnavailable::from_utf8(buffer)
|
||||||
|
.map_err(Error::ServerUnavailable)?,
|
||||||
|
)),
|
||||||
|
b'2' => Ok(Self::CgiError(
|
||||||
|
CgiError::from_utf8(buffer).map_err(Error::CgiError)?,
|
||||||
|
)),
|
||||||
|
b'3' => Ok(Self::ProxyError(
|
||||||
|
ProxyError::from_utf8(buffer).map_err(Error::ProxyError)?,
|
||||||
|
)),
|
||||||
|
b'4' => Ok(Self::SlowDown(
|
||||||
|
SlowDown::from_utf8(buffer).map_err(Error::SlowDown)?,
|
||||||
|
)),
|
||||||
|
b => Err(Error::SecondByte(b)),
|
||||||
|
},
|
||||||
|
None => Err(Error::UndefinedSecondByte),
|
||||||
|
},
|
||||||
|
b => Err(Error::FirstByte(b)),
|
||||||
|
},
|
||||||
|
None => Err(Error::UndefinedFirstByte),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
|
|
||||||
pub fn to_code(&self) -> u8 {
|
|
||||||
match self {
|
|
||||||
Self::Default { .. } => DEFAULT,
|
|
||||||
Self::ServerUnavailable { .. } => SERVER_UNAVAILABLE,
|
|
||||||
Self::CgiError { .. } => CGI_ERROR,
|
|
||||||
Self::ProxyError { .. } => PROXY_ERROR,
|
|
||||||
Self::SlowDown { .. } => SLOW_DOWN,
|
|
||||||
}
|
|
||||||
.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn message(&self) -> Option<&str> {
|
pub fn message(&self) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
Self::Default { message } => message,
|
Self::Default(default) => default.message(),
|
||||||
Self::ServerUnavailable { message } => message,
|
Self::ServerUnavailable(server_unavailable) => server_unavailable.message(),
|
||||||
Self::CgiError { message } => message,
|
Self::CgiError(cgi_error) => cgi_error.message(),
|
||||||
Self::ProxyError { message } => message,
|
Self::ProxyError(proxy_error) => proxy_error.message(),
|
||||||
Self::SlowDown { message } => message,
|
Self::SlowDown(slow_down) => slow_down.message(),
|
||||||
}
|
|
||||||
.as_deref()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Temporary {
|
/// Get optional message for `Self`
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
/// * if the optional message not provided by the server, return children `DEFAULT_MESSAGE`
|
||||||
write!(
|
pub fn message_or_default(&self) -> &str {
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
match self {
|
match self {
|
||||||
Self::Default { .. } => DEFAULT,
|
Self::Default(default) => default.message_or_default(),
|
||||||
Self::ServerUnavailable { .. } => SERVER_UNAVAILABLE,
|
Self::ServerUnavailable(server_unavailable) => server_unavailable.message_or_default(),
|
||||||
Self::CgiError { .. } => CGI_ERROR,
|
Self::CgiError(cgi_error) => cgi_error.message_or_default(),
|
||||||
Self::ProxyError { .. } => PROXY_ERROR,
|
Self::ProxyError(proxy_error) => proxy_error.message_or_default(),
|
||||||
Self::SlowDown { .. } => SLOW_DOWN,
|
Self::SlowDown(slow_down) => slow_down.message_or_default(),
|
||||||
}
|
|
||||||
.1
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for Temporary {
|
/// Get header string of `Self`
|
||||||
type Err = Error;
|
pub fn as_str(&self) -> &str {
|
||||||
fn from_str(header: &str) -> Result<Self, Self::Err> {
|
match self {
|
||||||
if let Some(postfix) = header.strip_prefix("40") {
|
Self::Default(default) => default.as_str(),
|
||||||
return Ok(Self::Default {
|
Self::ServerUnavailable(server_unavailable) => server_unavailable.as_str(),
|
||||||
message: message(postfix),
|
Self::CgiError(cgi_error) => cgi_error.as_str(),
|
||||||
});
|
Self::ProxyError(proxy_error) => proxy_error.as_str(),
|
||||||
}
|
Self::SlowDown(slow_down) => slow_down.as_str(),
|
||||||
if let Some(postfix) = header.strip_prefix("41") {
|
|
||||||
return Ok(Self::ServerUnavailable {
|
|
||||||
message: message(postfix),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if let Some(postfix) = header.strip_prefix("42") {
|
|
||||||
return Ok(Self::CgiError {
|
|
||||||
message: message(postfix),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if let Some(postfix) = header.strip_prefix("43") {
|
|
||||||
return Ok(Self::ProxyError {
|
|
||||||
message: message(postfix),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if let Some(postfix) = header.strip_prefix("44") {
|
|
||||||
return Ok(Self::SlowDown {
|
|
||||||
message: message(postfix),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(Error::Code)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tools
|
/// Get header bytes of `Self`
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
fn message(value: &str) -> Option<String> {
|
match self {
|
||||||
let value = value.trim();
|
Self::Default(default) => default.as_bytes(),
|
||||||
if value.is_empty() {
|
Self::ServerUnavailable(server_unavailable) => server_unavailable.as_bytes(),
|
||||||
None
|
Self::CgiError(cgi_error) => cgi_error.as_bytes(),
|
||||||
} else {
|
Self::ProxyError(proxy_error) => proxy_error.as_bytes(),
|
||||||
Some(value.to_string())
|
Self::SlowDown(slow_down) => slow_down.as_bytes(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_str() {
|
fn test() {
|
||||||
use std::str::FromStr;
|
fn t(source: String, message: Option<&str>) {
|
||||||
|
let b = source.as_bytes();
|
||||||
// 40
|
let i = Temporary::from_utf8(b).unwrap();
|
||||||
let default = Temporary::from_str("40 Message\r\n").unwrap();
|
assert_eq!(i.message(), message);
|
||||||
assert_eq!(default.message(), Some("Message"));
|
assert_eq!(i.as_str(), source);
|
||||||
assert_eq!(default.to_code(), DEFAULT.0);
|
assert_eq!(i.as_bytes(), b);
|
||||||
assert_eq!(default.to_string(), DEFAULT.1);
|
}
|
||||||
|
for code in [40, 41, 42, 43, 44] {
|
||||||
let default = Temporary::from_str("40\r\n").unwrap();
|
t(format!("{code} Message\r\n"), Some("Message"));
|
||||||
assert_eq!(default.message(), None);
|
t(format!("{code}\r\n"), None);
|
||||||
assert_eq!(default.to_code(), DEFAULT.0);
|
}
|
||||||
assert_eq!(default.to_string(), DEFAULT.1);
|
|
||||||
|
|
||||||
// 41
|
|
||||||
let server_unavailable = Temporary::from_str("41 Message\r\n").unwrap();
|
|
||||||
assert_eq!(server_unavailable.message(), Some("Message"));
|
|
||||||
assert_eq!(server_unavailable.to_code(), SERVER_UNAVAILABLE.0);
|
|
||||||
assert_eq!(server_unavailable.to_string(), SERVER_UNAVAILABLE.1);
|
|
||||||
|
|
||||||
let server_unavailable = Temporary::from_str("41\r\n").unwrap();
|
|
||||||
assert_eq!(server_unavailable.message(), None);
|
|
||||||
assert_eq!(server_unavailable.to_code(), SERVER_UNAVAILABLE.0);
|
|
||||||
assert_eq!(server_unavailable.to_string(), SERVER_UNAVAILABLE.1);
|
|
||||||
|
|
||||||
// 42
|
|
||||||
let cgi_error = Temporary::from_str("42 Message\r\n").unwrap();
|
|
||||||
assert_eq!(cgi_error.message(), Some("Message"));
|
|
||||||
assert_eq!(cgi_error.to_code(), CGI_ERROR.0);
|
|
||||||
assert_eq!(cgi_error.to_string(), CGI_ERROR.1);
|
|
||||||
|
|
||||||
let cgi_error = Temporary::from_str("42\r\n").unwrap();
|
|
||||||
assert_eq!(cgi_error.message(), None);
|
|
||||||
assert_eq!(cgi_error.to_code(), CGI_ERROR.0);
|
|
||||||
assert_eq!(cgi_error.to_string(), CGI_ERROR.1);
|
|
||||||
|
|
||||||
// 43
|
|
||||||
let proxy_error = Temporary::from_str("43 Message\r\n").unwrap();
|
|
||||||
assert_eq!(proxy_error.message(), Some("Message"));
|
|
||||||
assert_eq!(proxy_error.to_code(), PROXY_ERROR.0);
|
|
||||||
assert_eq!(proxy_error.to_string(), PROXY_ERROR.1);
|
|
||||||
|
|
||||||
let proxy_error = Temporary::from_str("43\r\n").unwrap();
|
|
||||||
assert_eq!(proxy_error.message(), None);
|
|
||||||
assert_eq!(proxy_error.to_code(), PROXY_ERROR.0);
|
|
||||||
assert_eq!(proxy_error.to_string(), PROXY_ERROR.1);
|
|
||||||
|
|
||||||
// 44
|
|
||||||
let slow_down = Temporary::from_str("44 Message\r\n").unwrap();
|
|
||||||
assert_eq!(slow_down.message(), Some("Message"));
|
|
||||||
assert_eq!(slow_down.to_code(), SLOW_DOWN.0);
|
|
||||||
assert_eq!(slow_down.to_string(), SLOW_DOWN.1);
|
|
||||||
|
|
||||||
let slow_down = Temporary::from_str("44\r\n").unwrap();
|
|
||||||
assert_eq!(slow_down.message(), None);
|
|
||||||
assert_eq!(slow_down.to_code(), SLOW_DOWN.0);
|
|
||||||
assert_eq!(slow_down.to_string(), SLOW_DOWN.1);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
pub mod error;
|
||||||
|
pub use error::Error;
|
||||||
|
|
||||||
|
/// [CGI Error](https://geminiprotocol.net/docs/protocol-specification.gmi#status-42-cgi-error) status code
|
||||||
|
pub const CODE: &[u8] = b"42";
|
||||||
|
|
||||||
|
/// Default message if the optional value was not provided by the server
|
||||||
|
/// * useful to skip match cases in external applications,
|
||||||
|
/// by using `super::message_or_default` method.
|
||||||
|
pub const DEFAULT_MESSAGE: &str = "CGI Error";
|
||||||
|
|
||||||
|
/// Hold header `String` for [CGI Error](https://geminiprotocol.net/docs/protocol-specification.gmi#status-42-cgi-error) status code
|
||||||
|
/// * this response type does not contain body data
|
||||||
|
/// * the header member is closed to require valid construction
|
||||||
|
pub struct CgiError(String);
|
||||||
|
|
||||||
|
impl CgiError {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
Ok(Self(
|
||||||
|
std::str::from_utf8(
|
||||||
|
crate::client::connection::response::header_bytes(buffer).map_err(Error::Header)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::Utf8Error)?
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
|
||||||
|
/// Get optional message for `Self`
|
||||||
|
/// * return `None` if the message is empty
|
||||||
|
pub fn message(&self) -> Option<&str> {
|
||||||
|
self.0.get(2..).map(|s| s.trim()).filter(|x| !x.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get optional message for `Self`
|
||||||
|
/// * if the optional message not provided by the server, return `DEFAULT_MESSAGE`
|
||||||
|
pub fn message_or_default(&self) -> &str {
|
||||||
|
self.message().unwrap_or(DEFAULT_MESSAGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get header string of `Self`
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get header bytes of `Self`
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
self.0.as_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
// ok
|
||||||
|
let ce = CgiError::from_utf8("42 Message\r\n".as_bytes()).unwrap();
|
||||||
|
assert_eq!(ce.message(), Some("Message"));
|
||||||
|
assert_eq!(ce.message_or_default(), "Message");
|
||||||
|
assert_eq!(ce.as_str(), "42 Message\r\n");
|
||||||
|
assert_eq!(ce.as_bytes(), "42 Message\r\n".as_bytes());
|
||||||
|
|
||||||
|
let ce = CgiError::from_utf8("42\r\n".as_bytes()).unwrap();
|
||||||
|
assert_eq!(ce.message(), None);
|
||||||
|
assert_eq!(ce.message_or_default(), DEFAULT_MESSAGE);
|
||||||
|
assert_eq!(ce.as_str(), "42\r\n");
|
||||||
|
assert_eq!(ce.as_bytes(), "42\r\n".as_bytes());
|
||||||
|
|
||||||
|
// err
|
||||||
|
assert!(CgiError::from_utf8("13 Fail\r\n".as_bytes()).is_err());
|
||||||
|
assert!(CgiError::from_utf8("Fail\r\n".as_bytes()).is_err());
|
||||||
|
assert!(CgiError::from_utf8("Fail".as_bytes()).is_err());
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
use std::fmt::{Display, Formatter, Result};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
Code,
|
||||||
|
Header(crate::client::connection::response::HeaderBytesError),
|
||||||
|
Utf8Error(std::str::Utf8Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||||
|
match self {
|
||||||
|
Self::Code => {
|
||||||
|
write!(f, "Unexpected status code")
|
||||||
|
}
|
||||||
|
Self::Header(e) => {
|
||||||
|
write!(f, "Header error: {e}")
|
||||||
|
}
|
||||||
|
Self::Utf8Error(e) => {
|
||||||
|
write!(f, "UTF-8 decode error: {e}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src/client/connection/response/failure/temporary/default.rs
Normal file
78
src/client/connection/response/failure/temporary/default.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
pub mod error;
|
||||||
|
pub use error::Error;
|
||||||
|
|
||||||
|
/// [Unspecified Temporary Error](https://geminiprotocol.net/docs/protocol-specification.gmi#status-40) status code
|
||||||
|
pub const CODE: &[u8] = b"40";
|
||||||
|
|
||||||
|
/// Default message if the optional value was not provided by the server
|
||||||
|
/// * useful to skip match cases in external applications,
|
||||||
|
/// by using `super::message_or_default` method.
|
||||||
|
pub const DEFAULT_MESSAGE: &str = "Temporary error";
|
||||||
|
|
||||||
|
/// Hold header `String` for [Unspecified Temporary Error](https://geminiprotocol.net/docs/protocol-specification.gmi#status-40) status code
|
||||||
|
/// * this response type does not contain body data
|
||||||
|
/// * the header member is closed to require valid construction
|
||||||
|
pub struct Default(String);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
Ok(Self(
|
||||||
|
std::str::from_utf8(
|
||||||
|
crate::client::connection::response::header_bytes(buffer).map_err(Error::Header)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::Utf8Error)?
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
|
||||||
|
/// Get optional message for `Self`
|
||||||
|
/// * return `None` if the message is empty
|
||||||
|
pub fn message(&self) -> Option<&str> {
|
||||||
|
self.0.get(2..).map(|s| s.trim()).filter(|x| !x.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get optional message for `Self`
|
||||||
|
/// * if the optional message not provided by the server, return `DEFAULT_MESSAGE`
|
||||||
|
pub fn message_or_default(&self) -> &str {
|
||||||
|
self.message().unwrap_or(DEFAULT_MESSAGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get header string of `Self`
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get header bytes of `Self`
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
self.0.as_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
// ok
|
||||||
|
let d = Default::from_utf8("40 Message\r\n".as_bytes()).unwrap();
|
||||||
|
assert_eq!(d.message(), Some("Message"));
|
||||||
|
assert_eq!(d.message_or_default(), "Message");
|
||||||
|
assert_eq!(d.as_str(), "40 Message\r\n");
|
||||||
|
assert_eq!(d.as_bytes(), "40 Message\r\n".as_bytes());
|
||||||
|
|
||||||
|
let d = Default::from_utf8("40\r\n".as_bytes()).unwrap();
|
||||||
|
assert_eq!(d.message(), None);
|
||||||
|
assert_eq!(d.message_or_default(), DEFAULT_MESSAGE);
|
||||||
|
assert_eq!(d.as_str(), "40\r\n");
|
||||||
|
assert_eq!(d.as_bytes(), "40\r\n".as_bytes());
|
||||||
|
|
||||||
|
// err
|
||||||
|
assert!(Default::from_utf8("13 Fail\r\n".as_bytes()).is_err());
|
||||||
|
assert!(Default::from_utf8("Fail\r\n".as_bytes()).is_err());
|
||||||
|
assert!(Default::from_utf8("Fail".as_bytes()).is_err());
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
use std::fmt::{Display, Formatter, Result};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
Code,
|
||||||
|
Header(crate::client::connection::response::HeaderBytesError),
|
||||||
|
Utf8Error(std::str::Utf8Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||||
|
match self {
|
||||||
|
Self::Code => {
|
||||||
|
write!(f, "Unexpected status code")
|
||||||
|
}
|
||||||
|
Self::Header(e) => {
|
||||||
|
write!(f, "Header error: {e}")
|
||||||
|
}
|
||||||
|
Self::Utf8Error(e) => {
|
||||||
|
write!(f, "UTF-8 decode error: {e}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,22 +1,47 @@
|
||||||
use std::{
|
use std::fmt::{Display, Formatter, Result};
|
||||||
fmt::{Display, Formatter, Result},
|
|
||||||
str::Utf8Error,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Code,
|
CgiError(super::cgi_error::Error),
|
||||||
Utf8Error(Utf8Error),
|
Default(super::default::Error),
|
||||||
|
FirstByte(u8),
|
||||||
|
ProxyError(super::proxy_error::Error),
|
||||||
|
SecondByte(u8),
|
||||||
|
ServerUnavailable(super::server_unavailable::Error),
|
||||||
|
SlowDown(super::slow_down::Error),
|
||||||
|
UndefinedFirstByte,
|
||||||
|
UndefinedSecondByte,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Error {
|
impl Display for Error {
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Code => {
|
Self::CgiError(e) => {
|
||||||
write!(f, "Status code error")
|
write!(f, "CgiError parse error: {e}")
|
||||||
}
|
}
|
||||||
Self::Utf8Error(e) => {
|
Self::Default(e) => {
|
||||||
write!(f, "UTF-8 error: {e}")
|
write!(f, "Default parse error: {e}")
|
||||||
|
}
|
||||||
|
Self::FirstByte(b) => {
|
||||||
|
write!(f, "Unexpected first byte: {b}")
|
||||||
|
}
|
||||||
|
Self::ProxyError(e) => {
|
||||||
|
write!(f, "ProxyError parse error: {e}")
|
||||||
|
}
|
||||||
|
Self::SecondByte(b) => {
|
||||||
|
write!(f, "Unexpected second byte: {b}")
|
||||||
|
}
|
||||||
|
Self::ServerUnavailable(e) => {
|
||||||
|
write!(f, "ServerUnavailable parse error: {e}")
|
||||||
|
}
|
||||||
|
Self::SlowDown(e) => {
|
||||||
|
write!(f, "SlowDown parse error: {e}")
|
||||||
|
}
|
||||||
|
Self::UndefinedFirstByte => {
|
||||||
|
write!(f, "Undefined first byte")
|
||||||
|
}
|
||||||
|
Self::UndefinedSecondByte => {
|
||||||
|
write!(f, "Undefined second byte")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
pub mod error;
|
||||||
|
pub use error::Error;
|
||||||
|
|
||||||
|
/// [Proxy Error](https://geminiprotocol.net/docs/protocol-specification.gmi#status-43-proxy-error) status code
|
||||||
|
pub const CODE: &[u8] = b"43";
|
||||||
|
|
||||||
|
/// Default message if the optional value was not provided by the server
|
||||||
|
/// * useful to skip match cases in external applications,
|
||||||
|
/// by using `super::message_or_default` method.
|
||||||
|
pub const DEFAULT_MESSAGE: &str = "Proxy error";
|
||||||
|
|
||||||
|
/// Hold header `String` for [Proxy Error](https://geminiprotocol.net/docs/protocol-specification.gmi#status-43-proxy-error) status code
|
||||||
|
/// * this response type does not contain body data
|
||||||
|
/// * the header member is closed to require valid construction
|
||||||
|
pub struct ProxyError(String);
|
||||||
|
|
||||||
|
impl ProxyError {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
Ok(Self(
|
||||||
|
std::str::from_utf8(
|
||||||
|
crate::client::connection::response::header_bytes(buffer).map_err(Error::Header)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::Utf8Error)?
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
|
||||||
|
/// Get optional message for `Self`
|
||||||
|
/// * return `None` if the message is empty
|
||||||
|
pub fn message(&self) -> Option<&str> {
|
||||||
|
self.0.get(2..).map(|s| s.trim()).filter(|x| !x.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get optional message for `Self`
|
||||||
|
/// * if the optional message not provided by the server, return `DEFAULT_MESSAGE`
|
||||||
|
pub fn message_or_default(&self) -> &str {
|
||||||
|
self.message().unwrap_or(DEFAULT_MESSAGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get header string of `Self`
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get header bytes of `Self`
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
self.0.as_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
// ok
|
||||||
|
let pe = ProxyError::from_utf8("43 Message\r\n".as_bytes()).unwrap();
|
||||||
|
assert_eq!(pe.message(), Some("Message"));
|
||||||
|
assert_eq!(pe.message_or_default(), "Message");
|
||||||
|
assert_eq!(pe.as_str(), "43 Message\r\n");
|
||||||
|
assert_eq!(pe.as_bytes(), "43 Message\r\n".as_bytes());
|
||||||
|
|
||||||
|
let pe = ProxyError::from_utf8("43\r\n".as_bytes()).unwrap();
|
||||||
|
assert_eq!(pe.message(), None);
|
||||||
|
assert_eq!(pe.message_or_default(), DEFAULT_MESSAGE);
|
||||||
|
assert_eq!(pe.as_str(), "43\r\n");
|
||||||
|
assert_eq!(pe.as_bytes(), "43\r\n".as_bytes());
|
||||||
|
|
||||||
|
// err
|
||||||
|
assert!(ProxyError::from_utf8("13 Fail\r\n".as_bytes()).is_err());
|
||||||
|
assert!(ProxyError::from_utf8("Fail\r\n".as_bytes()).is_err());
|
||||||
|
assert!(ProxyError::from_utf8("Fail".as_bytes()).is_err());
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
use std::fmt::{Display, Formatter, Result};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
Code,
|
||||||
|
Header(crate::client::connection::response::HeaderBytesError),
|
||||||
|
Utf8Error(std::str::Utf8Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||||
|
match self {
|
||||||
|
Self::Code => {
|
||||||
|
write!(f, "Unexpected status code")
|
||||||
|
}
|
||||||
|
Self::Header(e) => {
|
||||||
|
write!(f, "Header error: {e}")
|
||||||
|
}
|
||||||
|
Self::Utf8Error(e) => {
|
||||||
|
write!(f, "UTF-8 decode error: {e}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
pub mod error;
|
||||||
|
pub use error::Error;
|
||||||
|
|
||||||
|
/// [Server Unavailable](https://geminiprotocol.net/docs/protocol-specification.gmi#status-41-server-unavailable)
|
||||||
|
/// temporary error status code
|
||||||
|
pub const CODE: &[u8] = b"41";
|
||||||
|
|
||||||
|
/// Default message if the optional value was not provided by the server
|
||||||
|
/// * useful to skip match cases in external applications,
|
||||||
|
/// by using `super::message_or_default` method.
|
||||||
|
pub const DEFAULT_MESSAGE: &str = "Server unavailable";
|
||||||
|
|
||||||
|
/// Hold header `String` for [Server Unavailable](https://geminiprotocol.net/docs/protocol-specification.gmi#status-41-server-unavailable)
|
||||||
|
/// temporary error status code
|
||||||
|
///
|
||||||
|
/// * this response type does not contain body data
|
||||||
|
/// * the header member is closed to require valid construction
|
||||||
|
pub struct ServerUnavailable(String);
|
||||||
|
|
||||||
|
impl ServerUnavailable {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
Ok(Self(
|
||||||
|
std::str::from_utf8(
|
||||||
|
crate::client::connection::response::header_bytes(buffer).map_err(Error::Header)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::Utf8Error)?
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
|
||||||
|
/// Get optional message for `Self`
|
||||||
|
/// * return `None` if the message is empty
|
||||||
|
pub fn message(&self) -> Option<&str> {
|
||||||
|
self.0.get(2..).map(|s| s.trim()).filter(|x| !x.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get optional message for `Self`
|
||||||
|
/// * if the optional message not provided by the server, return `DEFAULT_MESSAGE`
|
||||||
|
pub fn message_or_default(&self) -> &str {
|
||||||
|
self.message().unwrap_or(DEFAULT_MESSAGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get header string of `Self`
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get header bytes of `Self`
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
self.0.as_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
// ok
|
||||||
|
let su = ServerUnavailable::from_utf8("41 Message\r\n".as_bytes()).unwrap();
|
||||||
|
assert_eq!(su.message(), Some("Message"));
|
||||||
|
assert_eq!(su.message_or_default(), "Message");
|
||||||
|
assert_eq!(su.as_str(), "41 Message\r\n");
|
||||||
|
assert_eq!(su.as_bytes(), "41 Message\r\n".as_bytes());
|
||||||
|
|
||||||
|
let su = ServerUnavailable::from_utf8("41\r\n".as_bytes()).unwrap();
|
||||||
|
assert_eq!(su.message(), None);
|
||||||
|
assert_eq!(su.message_or_default(), DEFAULT_MESSAGE);
|
||||||
|
assert_eq!(su.as_str(), "41\r\n");
|
||||||
|
assert_eq!(su.as_bytes(), "41\r\n".as_bytes());
|
||||||
|
|
||||||
|
// err
|
||||||
|
assert!(ServerUnavailable::from_utf8("13 Fail\r\n".as_bytes()).is_err());
|
||||||
|
assert!(ServerUnavailable::from_utf8("Fail\r\n".as_bytes()).is_err());
|
||||||
|
assert!(ServerUnavailable::from_utf8("Fail".as_bytes()).is_err());
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
use std::fmt::{Display, Formatter, Result};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
Code,
|
||||||
|
Header(crate::client::connection::response::HeaderBytesError),
|
||||||
|
Utf8Error(std::str::Utf8Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||||
|
match self {
|
||||||
|
Self::Code => {
|
||||||
|
write!(f, "Unexpected status code")
|
||||||
|
}
|
||||||
|
Self::Header(e) => {
|
||||||
|
write!(f, "Header error: {e}")
|
||||||
|
}
|
||||||
|
Self::Utf8Error(e) => {
|
||||||
|
write!(f, "UTF-8 decode error: {e}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
pub mod error;
|
||||||
|
pub use error::Error;
|
||||||
|
|
||||||
|
/// [Slow Down](https://geminiprotocol.net/docs/protocol-specification.gmi#status-44-slow-down)
|
||||||
|
/// temporary error status code
|
||||||
|
pub const CODE: &[u8] = b"44";
|
||||||
|
|
||||||
|
/// Default message if the optional value was not provided by the server
|
||||||
|
/// * useful to skip match cases in external applications,
|
||||||
|
/// by using `super::message_or_default` method.
|
||||||
|
pub const DEFAULT_MESSAGE: &str = "Slow down";
|
||||||
|
|
||||||
|
/// Hold header `String` for [Unspecified Temporary Error](https://geminiprotocol.net/docs/protocol-specification.gmi#status-44-slow-down)
|
||||||
|
/// temporary error status code
|
||||||
|
///
|
||||||
|
/// * this response type does not contain body data
|
||||||
|
/// * the header member is closed to require valid construction
|
||||||
|
pub struct SlowDown(String);
|
||||||
|
|
||||||
|
impl SlowDown {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
Ok(Self(
|
||||||
|
std::str::from_utf8(
|
||||||
|
crate::client::connection::response::header_bytes(buffer).map_err(Error::Header)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::Utf8Error)?
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
|
||||||
|
/// Get optional message for `Self`
|
||||||
|
/// * return `None` if the message is empty
|
||||||
|
pub fn message(&self) -> Option<&str> {
|
||||||
|
self.0.get(2..).map(|s| s.trim()).filter(|x| !x.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get optional message for `Self`
|
||||||
|
/// * if the optional message not provided by the server, return `DEFAULT_MESSAGE`
|
||||||
|
pub fn message_or_default(&self) -> &str {
|
||||||
|
self.message().unwrap_or(DEFAULT_MESSAGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get header string of `Self`
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get header bytes of `Self`
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
self.0.as_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
// ok
|
||||||
|
let sd = SlowDown::from_utf8("44 Message\r\n".as_bytes()).unwrap();
|
||||||
|
assert_eq!(sd.message(), Some("Message"));
|
||||||
|
assert_eq!(sd.message_or_default(), "Message");
|
||||||
|
assert_eq!(sd.as_str(), "44 Message\r\n");
|
||||||
|
assert_eq!(sd.as_bytes(), "44 Message\r\n".as_bytes());
|
||||||
|
|
||||||
|
let sd = SlowDown::from_utf8("44\r\n".as_bytes()).unwrap();
|
||||||
|
assert_eq!(sd.message(), None);
|
||||||
|
assert_eq!(sd.message_or_default(), DEFAULT_MESSAGE);
|
||||||
|
assert_eq!(sd.as_str(), "44\r\n");
|
||||||
|
assert_eq!(sd.as_bytes(), "44\r\n".as_bytes());
|
||||||
|
|
||||||
|
// err
|
||||||
|
assert!(SlowDown::from_utf8("13 Fail\r\n".as_bytes()).is_err());
|
||||||
|
assert!(SlowDown::from_utf8("Fail\r\n".as_bytes()).is_err());
|
||||||
|
assert!(SlowDown::from_utf8("Fail".as_bytes()).is_err());
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
use std::fmt::{Display, Formatter, Result};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
Code,
|
||||||
|
Header(crate::client::connection::response::HeaderBytesError),
|
||||||
|
Utf8Error(std::str::Utf8Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||||
|
match self {
|
||||||
|
Self::Code => {
|
||||||
|
write!(f, "Unexpected status code")
|
||||||
|
}
|
||||||
|
Self::Header(e) => {
|
||||||
|
write!(f, "Header error: {e}")
|
||||||
|
}
|
||||||
|
Self::Utf8Error(e) => {
|
||||||
|
write!(f, "UTF-8 decode error: {e}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue