mirror of
https://github.com/YGGverse/ggemini.git
synced 2026-03-31 17:15:31 +00:00
reorganize input format: make constructors lazy, parse members on get
This commit is contained in:
parent
161142c809
commit
a32eccf5cb
6 changed files with 243 additions and 85 deletions
|
|
@ -1,12 +1,17 @@
|
||||||
|
pub mod default;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod sensitive;
|
||||||
|
|
||||||
|
pub use default::Default;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
|
pub use sensitive::Sensitive;
|
||||||
|
|
||||||
const DEFAULT: (u8, &str) = (10, "Input");
|
const CODE: u8 = b'1';
|
||||||
const SENSITIVE: (u8, &str) = (11, "Sensitive input");
|
|
||||||
|
|
||||||
|
/// [Input expected](https://geminiprotocol.net/docs/protocol-specification.gmi#input-expected)
|
||||||
pub enum Input {
|
pub enum Input {
|
||||||
Default { message: Option<String> },
|
Default(Default),
|
||||||
Sensitive { message: Option<String> },
|
Sensitive(Sensitive),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
|
|
@ -14,97 +19,63 @@ impl Input {
|
||||||
|
|
||||||
/// 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::Sensitive(
|
||||||
|
Sensitive::from_utf8(buffer).map_err(Error::Sensitive)?,
|
||||||
|
)),
|
||||||
|
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::Sensitive { .. } => SENSITIVE,
|
|
||||||
}
|
|
||||||
.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::Sensitive { message } => message,
|
Self::Sensitive(sensitive) => sensitive.message(),
|
||||||
}
|
|
||||||
.as_deref()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Input {
|
pub fn as_str(&self) -> &str {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
match self {
|
match self {
|
||||||
Self::Default { .. } => DEFAULT,
|
Self::Default(default) => default.as_str(),
|
||||||
Self::Sensitive { .. } => SENSITIVE,
|
Self::Sensitive(sensitive) => sensitive.as_str(),
|
||||||
}
|
|
||||||
.1
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for Input {
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
type Err = Error;
|
match self {
|
||||||
fn from_str(header: &str) -> Result<Self, Self::Err> {
|
Self::Default(default) => default.as_bytes(),
|
||||||
if let Some(postfix) = header.strip_prefix("10") {
|
Self::Sensitive(sensitive) => sensitive.as_bytes(),
|
||||||
return Ok(Self::Default {
|
|
||||||
message: message(postfix),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if let Some(postfix) = header.strip_prefix("11") {
|
|
||||||
return Ok(Self::Sensitive {
|
|
||||||
message: message(postfix),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(Error::Protocol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tools
|
|
||||||
|
|
||||||
fn message(value: &str) -> Option<String> {
|
|
||||||
let value = value.trim();
|
|
||||||
if value.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(value.to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_str() {
|
fn test() {
|
||||||
use std::str::FromStr;
|
fn t(source: &str, message: Option<&str>) {
|
||||||
|
let bytes = source.as_bytes();
|
||||||
// 10
|
let input = Input::from_utf8(bytes).unwrap();
|
||||||
let default = Input::from_str("10 Default\r\n").unwrap();
|
assert_eq!(input.message(), message);
|
||||||
assert_eq!(default.message(), Some("Default"));
|
assert_eq!(input.as_str(), source);
|
||||||
assert_eq!(default.to_code(), DEFAULT.0);
|
assert_eq!(input.as_bytes(), bytes);
|
||||||
assert_eq!(default.to_string(), DEFAULT.1);
|
}
|
||||||
|
// 10
|
||||||
let default = Input::from_str("10\r\n").unwrap();
|
t("10 Default\r\n", Some("Default"));
|
||||||
assert_eq!(default.message(), None);
|
t("10\r\n", None);
|
||||||
assert_eq!(default.to_code(), DEFAULT.0);
|
// 11
|
||||||
assert_eq!(default.to_string(), DEFAULT.1);
|
t("11 Sensitive\r\n", Some("Sensitive"));
|
||||||
|
t("11\r\n", None);
|
||||||
// 11
|
|
||||||
let sensitive = Input::from_str("11 Sensitive\r\n").unwrap();
|
|
||||||
assert_eq!(sensitive.message(), Some("Sensitive"));
|
|
||||||
assert_eq!(sensitive.to_code(), SENSITIVE.0);
|
|
||||||
assert_eq!(sensitive.to_string(), SENSITIVE.1);
|
|
||||||
|
|
||||||
let sensitive = Input::from_str("11\r\n").unwrap();
|
|
||||||
assert_eq!(sensitive.message(), None);
|
|
||||||
assert_eq!(sensitive.to_code(), SENSITIVE.0);
|
|
||||||
assert_eq!(sensitive.to_string(), SENSITIVE.1);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
61
src/client/connection/response/input/default.rs
Normal file
61
src/client/connection/response/input/default.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
pub mod error;
|
||||||
|
pub use error::Error;
|
||||||
|
|
||||||
|
const CODE: &[u8] = b"10";
|
||||||
|
|
||||||
|
/// Hold header `String` for [10](https://geminiprotocol.net/docs/protocol-specification.gmi#status-10) 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())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
self.0.as_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
// ok
|
||||||
|
let default = Default::from_utf8("10 Default\r\n".as_bytes()).unwrap();
|
||||||
|
assert_eq!(default.message(), Some("Default"));
|
||||||
|
assert_eq!(default.as_str(), "10 Default\r\n");
|
||||||
|
|
||||||
|
let default = Default::from_utf8("10\r\n".as_bytes()).unwrap();
|
||||||
|
assert_eq!(default.message(), None);
|
||||||
|
assert_eq!(default.as_str(), "10\r\n");
|
||||||
|
|
||||||
|
// err
|
||||||
|
assert!(Default::from_utf8("12 Fail\r\n".as_bytes()).is_err());
|
||||||
|
assert!(Default::from_utf8("22 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());
|
||||||
|
}
|
||||||
24
src/client/connection/response/input/default/error.rs
Normal file
24
src/client/connection/response/input/default/error.rs
Normal file
|
|
@ -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,23 +1,40 @@
|
||||||
use std::{
|
use std::fmt::{Display, Formatter, Result};
|
||||||
fmt::{Display, Formatter, Result},
|
|
||||||
str::Utf8Error,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
Default(super::default::Error),
|
||||||
|
FirstByte(u8),
|
||||||
Protocol,
|
Protocol,
|
||||||
Utf8Error(Utf8Error),
|
SecondByte(u8),
|
||||||
|
Sensitive(super::sensitive::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::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::Protocol => {
|
Self::Protocol => {
|
||||||
write!(f, "Protocol error")
|
write!(f, "Protocol error")
|
||||||
}
|
}
|
||||||
|
Self::SecondByte(b) => {
|
||||||
|
write!(f, "Unexpected second byte: {b}")
|
||||||
|
}
|
||||||
|
Self::Sensitive(e) => {
|
||||||
|
write!(f, "Sensitive parse error: {e}")
|
||||||
|
}
|
||||||
|
Self::UndefinedFirstByte => {
|
||||||
|
write!(f, "Undefined first byte")
|
||||||
|
}
|
||||||
|
Self::UndefinedSecondByte => {
|
||||||
|
write!(f, "Undefined second byte")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
61
src/client/connection/response/input/sensitive.rs
Normal file
61
src/client/connection/response/input/sensitive.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
pub mod error;
|
||||||
|
pub use error::Error;
|
||||||
|
|
||||||
|
const CODE: &[u8] = b"11";
|
||||||
|
|
||||||
|
/// Hold header `String` for [11](https://geminiprotocol.net/docs/protocol-specification.gmi#status-11-sensitive-input) status code
|
||||||
|
/// * this response type does not contain body data
|
||||||
|
/// * the header member is closed to require valid construction
|
||||||
|
pub struct Sensitive(String);
|
||||||
|
|
||||||
|
impl Sensitive {
|
||||||
|
// 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())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
self.0.as_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
// ok
|
||||||
|
let sensitive = Sensitive::from_utf8("11 Sensitive\r\n".as_bytes()).unwrap();
|
||||||
|
assert_eq!(sensitive.message(), Some("Sensitive"));
|
||||||
|
assert_eq!(sensitive.as_str(), "11 Sensitive\r\n");
|
||||||
|
|
||||||
|
let sensitive = Sensitive::from_utf8("11\r\n".as_bytes()).unwrap();
|
||||||
|
assert_eq!(sensitive.message(), None);
|
||||||
|
assert_eq!(sensitive.as_str(), "11\r\n");
|
||||||
|
|
||||||
|
// err
|
||||||
|
assert!(Sensitive::from_utf8("12 Fail\r\n".as_bytes()).is_err());
|
||||||
|
assert!(Sensitive::from_utf8("22 Fail\r\n".as_bytes()).is_err());
|
||||||
|
assert!(Sensitive::from_utf8("Fail\r\n".as_bytes()).is_err());
|
||||||
|
assert!(Sensitive::from_utf8("Fail".as_bytes()).is_err());
|
||||||
|
}
|
||||||
24
src/client/connection/response/input/sensitive/error.rs
Normal file
24
src/client/connection/response/input/sensitive/error.rs
Normal file
|
|
@ -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