update temporary status codes api

This commit is contained in:
yggverse 2025-03-25 05:13:56 +02:00
parent c9a59e76ee
commit ea1fb8ea66
13 changed files with 682 additions and 152 deletions

View file

@ -1,24 +1,31 @@
pub mod cgi_error;
pub mod default;
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");
const SERVER_UNAVAILABLE: (u8, &str) = (41, "Server unavailable");
const CGI_ERROR: (u8, &str) = (42, "CGI error");
const PROXY_ERROR: (u8, &str) = (43, "Proxy error");
const SLOW_DOWN: (u8, &str) = (44, "Slow down");
pub use cgi_error::CgiError;
pub use default::Default;
pub use error::Error;
pub use proxy_error::ProxyError;
pub use server_unavailable::ServerUnavailable;
pub use slow_down::SlowDown;
const CODE: u8 = b'4';
/// https://geminiprotocol.net/docs/protocol-specification.gmi#temporary-failure
pub enum Temporary {
/// 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
ServerUnavailable { message: Option<String> },
ServerUnavailable(ServerUnavailable),
/// 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
ProxyError { message: Option<String> },
ProxyError(ProxyError),
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-44-slow-down
SlowDown { message: Option<String> },
SlowDown(SlowDown),
}
impl Temporary {
@ -26,154 +33,94 @@ impl Temporary {
/// Create new `Self` from buffer include header bytes
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
use std::str::FromStr;
match std::str::from_utf8(buffer) {
Ok(header) => Self::from_str(header),
Err(e) => Err(Error::Utf8Error(e)),
match buffer.first() {
Some(b) => match *b {
CODE => match buffer.get(1) {
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
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> {
match self {
Self::Default { message } => message,
Self::ServerUnavailable { message } => message,
Self::CgiError { message } => message,
Self::ProxyError { message } => message,
Self::SlowDown { message } => message,
Self::Default(default) => default.message(),
Self::ServerUnavailable(server_unavailable) => server_unavailable.message(),
Self::CgiError(cgi_error) => cgi_error.message(),
Self::ProxyError(proxy_error) => proxy_error.message(),
Self::SlowDown(slow_down) => slow_down.message(),
}
.as_deref()
}
}
impl std::fmt::Display for Temporary {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Default { .. } => DEFAULT,
Self::ServerUnavailable { .. } => SERVER_UNAVAILABLE,
Self::CgiError { .. } => CGI_ERROR,
Self::ProxyError { .. } => PROXY_ERROR,
Self::SlowDown { .. } => SLOW_DOWN,
}
.1
)
/// 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::Default(default) => default.message_or_default(),
Self::ServerUnavailable(server_unavailable) => server_unavailable.message_or_default(),
Self::CgiError(cgi_error) => cgi_error.message_or_default(),
Self::ProxyError(proxy_error) => proxy_error.message_or_default(),
Self::SlowDown(slow_down) => slow_down.message_or_default(),
}
}
}
impl std::str::FromStr for Temporary {
type Err = Error;
fn from_str(header: &str) -> Result<Self, Self::Err> {
if let Some(postfix) = header.strip_prefix("40") {
return Ok(Self::Default {
message: message(postfix),
});
/// Get header string of `Self`
pub fn as_str(&self) -> &str {
match self {
Self::Default(default) => default.as_str(),
Self::ServerUnavailable(server_unavailable) => server_unavailable.as_str(),
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
fn message(value: &str) -> Option<String> {
let value = value.trim();
if value.is_empty() {
None
} else {
Some(value.to_string())
/// Get header bytes of `Self`
pub fn as_bytes(&self) -> &[u8] {
match self {
Self::Default(default) => default.as_bytes(),
Self::ServerUnavailable(server_unavailable) => server_unavailable.as_bytes(),
Self::CgiError(cgi_error) => cgi_error.as_bytes(),
Self::ProxyError(proxy_error) => proxy_error.as_bytes(),
Self::SlowDown(slow_down) => slow_down.as_bytes(),
}
}
}
#[test]
fn test_from_str() {
use std::str::FromStr;
// 40
let default = Temporary::from_str("40 Message\r\n").unwrap();
assert_eq!(default.message(), Some("Message"));
assert_eq!(default.to_code(), DEFAULT.0);
assert_eq!(default.to_string(), DEFAULT.1);
let default = Temporary::from_str("40\r\n").unwrap();
assert_eq!(default.message(), 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);
fn test() {
fn t(source: String, message: Option<&str>) {
let b = source.as_bytes();
let i = Temporary::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] {
t(format!("{code} Message\r\n"), Some("Message"));
t(format!("{code}\r\n"), None);
}
}