mirror of
https://github.com/YGGverse/ggemini.git
synced 2026-03-31 09:05:45 +00:00
update permanent status codes api
This commit is contained in:
parent
0c75da793f
commit
e96ff688b3
14 changed files with 644 additions and 158 deletions
|
|
@ -21,7 +21,7 @@ impl Failure {
|
|||
/// Create new `Self` from buffer include header bytes
|
||||
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
||||
match buffer.first() {
|
||||
Some(byte) => match byte {
|
||||
Some(b) => match b {
|
||||
b'4' => match Temporary::from_utf8(buffer) {
|
||||
Ok(input) => Ok(Self::Temporary(input)),
|
||||
Err(e) => Err(Error::Temporary(e)),
|
||||
|
|
@ -30,7 +30,7 @@ impl Failure {
|
|||
Ok(failure) => Ok(Self::Permanent(failure)),
|
||||
Err(e) => Err(Error::Permanent(e)),
|
||||
},
|
||||
_ => Err(Error::Code),
|
||||
b => Err(Error::Code(*b)),
|
||||
},
|
||||
None => Err(Error::Protocol),
|
||||
}
|
||||
|
|
@ -38,13 +38,6 @@ impl Failure {
|
|||
|
||||
// Getters
|
||||
|
||||
pub fn to_code(&self) -> u8 {
|
||||
match self {
|
||||
Self::Permanent(permanent) => permanent.to_code(),
|
||||
Self::Temporary(temporary) => temporary.to_code(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Permanent(permanent) => permanent.message(),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter, Result};
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Code,
|
||||
Code(u8),
|
||||
Permanent(super::permanent::Error),
|
||||
Protocol,
|
||||
Temporary(super::temporary::Error),
|
||||
|
|
@ -11,8 +11,8 @@ pub enum Error {
|
|||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Code => {
|
||||
write!(f, "Code group error")
|
||||
Self::Code(b) => {
|
||||
write!(f, "Unexpected status code byte: {b}")
|
||||
}
|
||||
Self::Permanent(e) => {
|
||||
write!(f, "Permanent failure group error: {e}")
|
||||
|
|
|
|||
|
|
@ -1,24 +1,31 @@
|
|||
pub mod bad_request;
|
||||
pub mod default;
|
||||
pub mod error;
|
||||
pub use error::Error;
|
||||
pub mod gone;
|
||||
pub mod not_found;
|
||||
pub mod proxy_request_refused;
|
||||
|
||||
const DEFAULT: (u8, &str) = (50, "Unspecified");
|
||||
const NOT_FOUND: (u8, &str) = (51, "Not found");
|
||||
const GONE: (u8, &str) = (52, "Gone");
|
||||
const PROXY_REQUEST_REFUSED: (u8, &str) = (53, "Proxy request refused");
|
||||
const BAD_REQUEST: (u8, &str) = (59, "bad-request");
|
||||
pub use bad_request::BadRequest;
|
||||
pub use default::Default;
|
||||
pub use error::Error;
|
||||
pub use gone::Gone;
|
||||
pub use not_found::NotFound;
|
||||
pub use proxy_request_refused::ProxyRequestRefused;
|
||||
|
||||
const CODE: u8 = b'5';
|
||||
|
||||
/// https://geminiprotocol.net/docs/protocol-specification.gmi#permanent-failure
|
||||
pub enum Permanent {
|
||||
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-50
|
||||
Default { message: Option<String> },
|
||||
Default(Default),
|
||||
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-51-not-found
|
||||
NotFound { message: Option<String> },
|
||||
NotFound(NotFound),
|
||||
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-52-gone
|
||||
Gone { message: Option<String> },
|
||||
Gone(Gone),
|
||||
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-53-proxy-request-refused
|
||||
ProxyRequestRefused { message: Option<String> },
|
||||
ProxyRequestRefused(ProxyRequestRefused),
|
||||
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-59-bad-request
|
||||
BadRequest { message: Option<String> },
|
||||
BadRequest(BadRequest),
|
||||
}
|
||||
|
||||
impl Permanent {
|
||||
|
|
@ -26,154 +33,105 @@ impl Permanent {
|
|||
|
||||
/// 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::NotFound(
|
||||
NotFound::from_utf8(buffer).map_err(Error::NotFound)?,
|
||||
)),
|
||||
b'2' => Ok(Self::Gone(Gone::from_utf8(buffer).map_err(Error::Gone)?)),
|
||||
b'3' => Ok(Self::ProxyRequestRefused(
|
||||
ProxyRequestRefused::from_utf8(buffer)
|
||||
.map_err(Error::ProxyRequestRefused)?,
|
||||
)),
|
||||
b'9' => Ok(Self::BadRequest(
|
||||
BadRequest::from_utf8(buffer).map_err(Error::BadRequest)?,
|
||||
)),
|
||||
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::NotFound { .. } => NOT_FOUND,
|
||||
Self::Gone { .. } => GONE,
|
||||
Self::ProxyRequestRefused { .. } => PROXY_REQUEST_REFUSED,
|
||||
Self::BadRequest { .. } => BAD_REQUEST,
|
||||
}
|
||||
.0
|
||||
}
|
||||
|
||||
pub fn message(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Default { message } => message,
|
||||
Self::NotFound { message } => message,
|
||||
Self::Gone { message } => message,
|
||||
Self::ProxyRequestRefused { message } => message,
|
||||
Self::BadRequest { message } => message,
|
||||
Self::Default(default) => default.message(),
|
||||
Self::NotFound(not_found) => not_found.message(),
|
||||
Self::Gone(gone) => gone.message(),
|
||||
Self::ProxyRequestRefused(proxy_request_refused) => proxy_request_refused.message(),
|
||||
Self::BadRequest(bad_request) => bad_request.message(),
|
||||
}
|
||||
.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Permanent {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
/// 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,
|
||||
Self::NotFound { .. } => NOT_FOUND,
|
||||
Self::Gone { .. } => GONE,
|
||||
Self::ProxyRequestRefused { .. } => PROXY_REQUEST_REFUSED,
|
||||
Self::BadRequest { .. } => BAD_REQUEST,
|
||||
Self::Default(default) => default.message_or_default(),
|
||||
Self::NotFound(not_found) => not_found.message_or_default(),
|
||||
Self::Gone(gone) => gone.message_or_default(),
|
||||
Self::ProxyRequestRefused(proxy_request_refused) => {
|
||||
proxy_request_refused.message_or_default()
|
||||
}
|
||||
Self::BadRequest(bad_request) => bad_request.message_or_default(),
|
||||
}
|
||||
.1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Permanent {
|
||||
type Err = Error;
|
||||
fn from_str(header: &str) -> Result<Self, Self::Err> {
|
||||
if let Some(postfix) = header.strip_prefix("50") {
|
||||
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::NotFound(not_found) => not_found.as_str(),
|
||||
Self::Gone(gone) => gone.as_str(),
|
||||
Self::ProxyRequestRefused(proxy_request_refused) => proxy_request_refused.as_str(),
|
||||
Self::BadRequest(bad_request) => bad_request.as_str(),
|
||||
}
|
||||
if let Some(postfix) = header.strip_prefix("51") {
|
||||
return Ok(Self::NotFound {
|
||||
message: message(postfix),
|
||||
});
|
||||
}
|
||||
if let Some(postfix) = header.strip_prefix("52") {
|
||||
return Ok(Self::Gone {
|
||||
message: message(postfix),
|
||||
});
|
||||
}
|
||||
if let Some(postfix) = header.strip_prefix("53") {
|
||||
return Ok(Self::ProxyRequestRefused {
|
||||
message: message(postfix),
|
||||
});
|
||||
}
|
||||
if let Some(postfix) = header.strip_prefix("59") {
|
||||
return Ok(Self::BadRequest {
|
||||
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::NotFound(not_found) => not_found.as_bytes(),
|
||||
Self::Gone(gone) => gone.as_bytes(),
|
||||
Self::ProxyRequestRefused(proxy_request_refused) => proxy_request_refused.as_bytes(),
|
||||
Self::BadRequest(bad_request) => bad_request.as_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_str() {
|
||||
use std::str::FromStr;
|
||||
|
||||
fn test() {
|
||||
fn t(source: &str, message: Option<&str>) {
|
||||
let b = source.as_bytes();
|
||||
let i = Permanent::from_utf8(b).unwrap();
|
||||
assert_eq!(i.message(), message);
|
||||
assert_eq!(i.as_str(), source);
|
||||
assert_eq!(i.as_bytes(), b);
|
||||
}
|
||||
// 50
|
||||
let default = Permanent::from_str("50 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 = Permanent::from_str("50\r\n").unwrap();
|
||||
assert_eq!(default.message(), None);
|
||||
assert_eq!(default.to_code(), DEFAULT.0);
|
||||
assert_eq!(default.to_string(), DEFAULT.1);
|
||||
|
||||
t("50 Message\r\n", Some("Message"));
|
||||
t("50\r\n", None);
|
||||
// 51
|
||||
let not_found = Permanent::from_str("51 Message\r\n").unwrap();
|
||||
assert_eq!(not_found.message(), Some("Message"));
|
||||
assert_eq!(not_found.to_code(), NOT_FOUND.0);
|
||||
assert_eq!(not_found.to_string(), NOT_FOUND.1);
|
||||
|
||||
let not_found = Permanent::from_str("51\r\n").unwrap();
|
||||
assert_eq!(not_found.message(), None);
|
||||
assert_eq!(not_found.to_code(), NOT_FOUND.0);
|
||||
assert_eq!(not_found.to_string(), NOT_FOUND.1);
|
||||
|
||||
t("51 Message\r\n", Some("Message"));
|
||||
t("51\r\n", None);
|
||||
// 52
|
||||
let gone = Permanent::from_str("52 Message\r\n").unwrap();
|
||||
assert_eq!(gone.message(), Some("Message"));
|
||||
assert_eq!(gone.to_code(), GONE.0);
|
||||
assert_eq!(gone.to_string(), GONE.1);
|
||||
|
||||
let gone = Permanent::from_str("52\r\n").unwrap();
|
||||
assert_eq!(gone.message(), None);
|
||||
assert_eq!(gone.to_code(), GONE.0);
|
||||
assert_eq!(gone.to_string(), GONE.1);
|
||||
|
||||
t("52 Message\r\n", Some("Message"));
|
||||
t("52\r\n", None);
|
||||
// 53
|
||||
let proxy_request_refused = Permanent::from_str("53 Message\r\n").unwrap();
|
||||
assert_eq!(proxy_request_refused.message(), Some("Message"));
|
||||
assert_eq!(proxy_request_refused.to_code(), PROXY_REQUEST_REFUSED.0);
|
||||
assert_eq!(proxy_request_refused.to_string(), PROXY_REQUEST_REFUSED.1);
|
||||
|
||||
let proxy_request_refused = Permanent::from_str("53\r\n").unwrap();
|
||||
assert_eq!(proxy_request_refused.message(), None);
|
||||
assert_eq!(proxy_request_refused.to_code(), PROXY_REQUEST_REFUSED.0);
|
||||
assert_eq!(proxy_request_refused.to_string(), PROXY_REQUEST_REFUSED.1);
|
||||
|
||||
t("53 Message\r\n", Some("Message"));
|
||||
t("53\r\n", None);
|
||||
// 59
|
||||
let bad_request = Permanent::from_str("59 Message\r\n").unwrap();
|
||||
assert_eq!(bad_request.message(), Some("Message"));
|
||||
assert_eq!(bad_request.to_code(), BAD_REQUEST.0);
|
||||
assert_eq!(bad_request.to_string(), BAD_REQUEST.1);
|
||||
|
||||
let bad_request = Permanent::from_str("59\r\n").unwrap();
|
||||
assert_eq!(bad_request.message(), None);
|
||||
assert_eq!(bad_request.to_code(), BAD_REQUEST.0);
|
||||
assert_eq!(bad_request.to_string(), BAD_REQUEST.1);
|
||||
t("59 Message\r\n", Some("Message"));
|
||||
t("59\r\n", None);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
pub mod error;
|
||||
pub use error::Error;
|
||||
|
||||
/// [Bad Request](https://geminiprotocol.net/docs/protocol-specification.gmi#status-59-bad-request) error status code
|
||||
pub const CODE: &[u8] = b"59";
|
||||
|
||||
/// 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 = "Bad request";
|
||||
|
||||
/// Hold header `String` for [Bad Request](https://geminiprotocol.net/docs/protocol-specification.gmi#status-59-bad-request) error status code
|
||||
/// * this response type does not contain body data
|
||||
/// * the header member is closed to require valid construction
|
||||
pub struct BadRequest(String);
|
||||
|
||||
impl BadRequest {
|
||||
// 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 br = BadRequest::from_utf8("59 Message\r\n".as_bytes()).unwrap();
|
||||
assert_eq!(br.message(), Some("Message"));
|
||||
assert_eq!(br.message_or_default(), "Message");
|
||||
assert_eq!(br.as_str(), "59 Message\r\n");
|
||||
assert_eq!(br.as_bytes(), "59 Message\r\n".as_bytes());
|
||||
|
||||
let br = BadRequest::from_utf8("59\r\n".as_bytes()).unwrap();
|
||||
assert_eq!(br.message(), None);
|
||||
assert_eq!(br.message_or_default(), DEFAULT_MESSAGE);
|
||||
assert_eq!(br.as_str(), "59\r\n");
|
||||
assert_eq!(br.as_bytes(), "59\r\n".as_bytes());
|
||||
|
||||
// err
|
||||
assert!(BadRequest::from_utf8("13 Fail\r\n".as_bytes()).is_err());
|
||||
assert!(BadRequest::from_utf8("Fail\r\n".as_bytes()).is_err());
|
||||
assert!(BadRequest::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/permanent/default.rs
Normal file
78
src/client/connection/response/failure/permanent/default.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
pub mod error;
|
||||
pub use error::Error;
|
||||
|
||||
/// [Unspecified Permanent Error](https://geminiprotocol.net/docs/protocol-specification.gmi#status-50) status code
|
||||
pub const CODE: &[u8] = b"50";
|
||||
|
||||
/// 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 = "Permanent error";
|
||||
|
||||
/// Hold header `String` for [Unspecified Permanent Error](https://geminiprotocol.net/docs/protocol-specification.gmi#status-50) 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("50 Message\r\n".as_bytes()).unwrap();
|
||||
assert_eq!(d.message(), Some("Message"));
|
||||
assert_eq!(d.message_or_default(), "Message");
|
||||
assert_eq!(d.as_str(), "50 Message\r\n");
|
||||
assert_eq!(d.as_bytes(), "50 Message\r\n".as_bytes());
|
||||
|
||||
let d = Default::from_utf8("50\r\n".as_bytes()).unwrap();
|
||||
assert_eq!(d.message(), None);
|
||||
assert_eq!(d.message_or_default(), DEFAULT_MESSAGE);
|
||||
assert_eq!(d.as_str(), "50\r\n");
|
||||
assert_eq!(d.as_bytes(), "50\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::{
|
||||
fmt::{Display, Formatter, Result},
|
||||
str::Utf8Error,
|
||||
};
|
||||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Code,
|
||||
Utf8Error(Utf8Error),
|
||||
BadRequest(super::bad_request::Error),
|
||||
Default(super::default::Error),
|
||||
FirstByte(u8),
|
||||
Gone(super::gone::Error),
|
||||
NotFound(super::not_found::Error),
|
||||
ProxyRequestRefused(super::proxy_request_refused::Error),
|
||||
SecondByte(u8),
|
||||
UndefinedFirstByte,
|
||||
UndefinedSecondByte,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Code => {
|
||||
write!(f, "Status code error")
|
||||
Self::BadRequest(e) => {
|
||||
write!(f, "BadRequest parse error: {e}")
|
||||
}
|
||||
Self::Utf8Error(e) => {
|
||||
write!(f, "UTF-8 error: {e}")
|
||||
Self::Default(e) => {
|
||||
write!(f, "Default parse error: {e}")
|
||||
}
|
||||
Self::FirstByte(b) => {
|
||||
write!(f, "Unexpected first byte: {b}")
|
||||
}
|
||||
Self::Gone(e) => {
|
||||
write!(f, "Gone parse error: {e}")
|
||||
}
|
||||
Self::NotFound(e) => {
|
||||
write!(f, "NotFound parse error: {e}")
|
||||
}
|
||||
Self::ProxyRequestRefused(e) => {
|
||||
write!(f, "ProxyRequestRefused parse error: {e}")
|
||||
}
|
||||
Self::SecondByte(b) => {
|
||||
write!(f, "Unexpected second byte: {b}")
|
||||
}
|
||||
Self::UndefinedFirstByte => {
|
||||
write!(f, "Undefined first byte")
|
||||
}
|
||||
Self::UndefinedSecondByte => {
|
||||
write!(f, "Undefined second byte")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
78
src/client/connection/response/failure/permanent/gone.rs
Normal file
78
src/client/connection/response/failure/permanent/gone.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
pub mod error;
|
||||
pub use error::Error;
|
||||
|
||||
/// [Server Gone Error](https://geminiprotocol.net/docs/protocol-specification.gmi#status-52-gone) status code
|
||||
pub const CODE: &[u8] = b"52";
|
||||
|
||||
/// 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 = "Resource gone";
|
||||
|
||||
/// Hold header `String` for [Server Gone Error](https://geminiprotocol.net/docs/protocol-specification.gmi#status-52-gone) status code
|
||||
/// * this response type does not contain body data
|
||||
/// * the header member is closed to require valid construction
|
||||
pub struct Gone(String);
|
||||
|
||||
impl Gone {
|
||||
// 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 g = Gone::from_utf8("52 Message\r\n".as_bytes()).unwrap();
|
||||
assert_eq!(g.message(), Some("Message"));
|
||||
assert_eq!(g.message_or_default(), "Message");
|
||||
assert_eq!(g.as_str(), "52 Message\r\n");
|
||||
assert_eq!(g.as_bytes(), "52 Message\r\n".as_bytes());
|
||||
|
||||
let g = Gone::from_utf8("52\r\n".as_bytes()).unwrap();
|
||||
assert_eq!(g.message(), None);
|
||||
assert_eq!(g.message_or_default(), DEFAULT_MESSAGE);
|
||||
assert_eq!(g.as_str(), "52\r\n");
|
||||
assert_eq!(g.as_bytes(), "52\r\n".as_bytes());
|
||||
|
||||
// err
|
||||
assert!(Gone::from_utf8("13 Fail\r\n".as_bytes()).is_err());
|
||||
assert!(Gone::from_utf8("Fail\r\n".as_bytes()).is_err());
|
||||
assert!(Gone::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,78 @@
|
|||
pub mod error;
|
||||
pub use error::Error;
|
||||
|
||||
/// [Not Found Permanent Error](https://geminiprotocol.net/docs/protocol-specification.gmi#status-51-not-found) status code
|
||||
pub const CODE: &[u8] = b"51";
|
||||
|
||||
/// 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 = "Not Found";
|
||||
|
||||
/// Hold header `String` for [Not Found Permanent Error](https://geminiprotocol.net/docs/protocol-specification.gmi#status-51-not-found) status code
|
||||
/// * this response type does not contain body data
|
||||
/// * the header member is closed to require valid construction
|
||||
pub struct NotFound(String);
|
||||
|
||||
impl NotFound {
|
||||
// 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 nf = NotFound::from_utf8("51 Message\r\n".as_bytes()).unwrap();
|
||||
assert_eq!(nf.message(), Some("Message"));
|
||||
assert_eq!(nf.message_or_default(), "Message");
|
||||
assert_eq!(nf.as_str(), "51 Message\r\n");
|
||||
assert_eq!(nf.as_bytes(), "51 Message\r\n".as_bytes());
|
||||
|
||||
let nf = NotFound::from_utf8("51\r\n".as_bytes()).unwrap();
|
||||
assert_eq!(nf.message(), None);
|
||||
assert_eq!(nf.message_or_default(), DEFAULT_MESSAGE);
|
||||
assert_eq!(nf.as_str(), "51\r\n");
|
||||
assert_eq!(nf.as_bytes(), "51\r\n".as_bytes());
|
||||
|
||||
// err
|
||||
assert!(NotFound::from_utf8("13 Fail\r\n".as_bytes()).is_err());
|
||||
assert!(NotFound::from_utf8("Fail\r\n".as_bytes()).is_err());
|
||||
assert!(NotFound::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,78 @@
|
|||
pub mod error;
|
||||
pub use error::Error;
|
||||
|
||||
/// [Proxy Request Refused](https://geminiprotocol.net/docs/protocol-specification.gmi#status-53-proxy-request-refused) permanent error status code
|
||||
pub const CODE: &[u8] = b"53";
|
||||
|
||||
/// 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 request refused";
|
||||
|
||||
/// Hold header `String` for [Proxy Request Refused](https://geminiprotocol.net/docs/protocol-specification.gmi#status-53-proxy-request-refused) permanent error status code
|
||||
/// * this response type does not contain body data
|
||||
/// * the header member is closed to require valid construction
|
||||
pub struct ProxyRequestRefused(String);
|
||||
|
||||
impl ProxyRequestRefused {
|
||||
// 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 prf = ProxyRequestRefused::from_utf8("53 Message\r\n".as_bytes()).unwrap();
|
||||
assert_eq!(prf.message(), Some("Message"));
|
||||
assert_eq!(prf.message_or_default(), "Message");
|
||||
assert_eq!(prf.as_str(), "53 Message\r\n");
|
||||
assert_eq!(prf.as_bytes(), "53 Message\r\n".as_bytes());
|
||||
|
||||
let prf = ProxyRequestRefused::from_utf8("53\r\n".as_bytes()).unwrap();
|
||||
assert_eq!(prf.message(), None);
|
||||
assert_eq!(prf.message_or_default(), DEFAULT_MESSAGE);
|
||||
assert_eq!(prf.as_str(), "53\r\n");
|
||||
assert_eq!(prf.as_bytes(), "53\r\n".as_bytes());
|
||||
|
||||
// err
|
||||
assert!(ProxyRequestRefused::from_utf8("13 Fail\r\n".as_bytes()).is_err());
|
||||
assert!(ProxyRequestRefused::from_utf8("Fail\r\n".as_bytes()).is_err());
|
||||
assert!(ProxyRequestRefused::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