mirror of
https://github.com/YGGverse/ggemini.git
synced 2026-04-01 01:25:32 +00:00
update response namespace
This commit is contained in:
parent
70fc128c29
commit
4767929050
19 changed files with 59 additions and 50 deletions
|
|
@ -2,12 +2,20 @@ use std::fmt::{Display, Formatter, Result};
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Response(crate::client::connection::response::Error),
|
||||
Stream(glib::Error),
|
||||
TlsClientConnection(glib::Error),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Stream(e) => {
|
||||
write!(f, "TLS client connection error: {e}")
|
||||
}
|
||||
Self::Response(e) => {
|
||||
write!(f, "Response error: {e}")
|
||||
}
|
||||
Self::TlsClientConnection(e) => {
|
||||
write!(f, "TLS client connection error: {e}")
|
||||
}
|
||||
|
|
|
|||
37
src/client/connection/response.rs
Normal file
37
src/client/connection/response.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
//! Read and parse Gemini response as Object
|
||||
|
||||
pub mod data;
|
||||
pub mod error;
|
||||
pub mod meta;
|
||||
|
||||
pub use error::Error;
|
||||
pub use meta::Meta;
|
||||
|
||||
use super::Connection;
|
||||
use gio::Cancellable;
|
||||
use glib::Priority;
|
||||
|
||||
pub struct Response {
|
||||
pub connection: Connection,
|
||||
pub meta: Meta,
|
||||
}
|
||||
|
||||
impl Response {
|
||||
// Constructors
|
||||
|
||||
/// Create new `Self` from given `Connection`
|
||||
/// * useful for manual [IOStream](https://docs.gtk.org/gio/class.IOStream.html) handle (based on `Meta` bytes pre-parsed)
|
||||
pub fn from_connection_async(
|
||||
connection: Connection,
|
||||
priority: Priority,
|
||||
cancellable: Cancellable,
|
||||
callback: impl FnOnce(Result<Self, Error>) + 'static,
|
||||
) {
|
||||
Meta::from_stream_async(connection.stream(), priority, cancellable, |result| {
|
||||
callback(match result {
|
||||
Ok(meta) => Ok(Self { connection, meta }),
|
||||
Err(e) => Err(Error::Meta(e)),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
7
src/client/connection/response/data.rs
Normal file
7
src/client/connection/response/data.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
//! Gemini response could have different MIME type for data.
|
||||
//! Use one of components below to parse response according to content type expected.
|
||||
//!
|
||||
//! * MIME type could be detected using `client::response::Meta` parser
|
||||
|
||||
pub mod text;
|
||||
pub use text::Text;
|
||||
111
src/client/connection/response/data/text.rs
Normal file
111
src/client/connection/response/data/text.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
//! Tools for Text-based response
|
||||
|
||||
pub mod error;
|
||||
pub use error::Error;
|
||||
|
||||
// Local dependencies
|
||||
use gio::{
|
||||
prelude::{IOStreamExt, InputStreamExt},
|
||||
Cancellable, IOStream,
|
||||
};
|
||||
use glib::{object::IsA, GString, Priority};
|
||||
|
||||
// Default limits
|
||||
pub const BUFFER_CAPACITY: usize = 0x400; // 1024
|
||||
pub const BUFFER_MAX_SIZE: usize = 0xfffff; // 1M
|
||||
|
||||
/// Container for text-based response data
|
||||
pub struct Text {
|
||||
pub data: GString,
|
||||
}
|
||||
|
||||
impl Default for Text {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Text {
|
||||
// Constructors
|
||||
|
||||
/// Create new `Self`
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data: GString::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new `Self` from string
|
||||
pub fn from_string(data: &str) -> Self {
|
||||
Self { data: data.into() }
|
||||
}
|
||||
|
||||
/// Create new `Self` from UTF-8 buffer
|
||||
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
||||
match GString::from_utf8(buffer.into()) {
|
||||
Ok(data) => Ok(Self::from_string(&data)),
|
||||
Err(e) => Err(Error::Decode(e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Asynchronously create new `Self` from [IOStream](https://docs.gtk.org/gio/class.IOStream.html)
|
||||
pub fn from_stream_async(
|
||||
stream: impl IsA<IOStream>,
|
||||
priority: Priority,
|
||||
cancellable: Cancellable,
|
||||
on_complete: impl FnOnce(Result<Self, Error>) + 'static,
|
||||
) {
|
||||
read_all_from_stream_async(
|
||||
Vec::with_capacity(BUFFER_CAPACITY),
|
||||
stream,
|
||||
cancellable,
|
||||
priority,
|
||||
|result| match result {
|
||||
Ok(buffer) => on_complete(Self::from_utf8(&buffer)),
|
||||
Err(e) => on_complete(Err(e)),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Tools
|
||||
|
||||
/// Asynchronously read all bytes from [IOStream](https://docs.gtk.org/gio/class.IOStream.html)
|
||||
///
|
||||
/// Return UTF-8 buffer collected
|
||||
/// * require `IOStream` reference to keep `Connection` active in async thread
|
||||
pub fn read_all_from_stream_async(
|
||||
mut buffer: Vec<u8>,
|
||||
stream: impl IsA<IOStream>,
|
||||
cancelable: Cancellable,
|
||||
priority: Priority,
|
||||
callback: impl FnOnce(Result<Vec<u8>, Error>) + 'static,
|
||||
) {
|
||||
stream.input_stream().read_bytes_async(
|
||||
BUFFER_CAPACITY,
|
||||
priority,
|
||||
Some(&cancelable.clone()),
|
||||
move |result| match result {
|
||||
Ok(bytes) => {
|
||||
// No bytes were read, end of stream
|
||||
if bytes.len() == 0 {
|
||||
return callback(Ok(buffer));
|
||||
}
|
||||
|
||||
// Validate overflow
|
||||
if buffer.len() + bytes.len() > BUFFER_MAX_SIZE {
|
||||
return callback(Err(Error::BufferOverflow));
|
||||
}
|
||||
|
||||
// Save chunks to buffer
|
||||
for &byte in bytes.iter() {
|
||||
buffer.push(byte);
|
||||
}
|
||||
|
||||
// Continue bytes reading
|
||||
read_all_from_stream_async(buffer, stream, cancelable, priority, callback);
|
||||
}
|
||||
Err(e) => callback(Err(Error::InputStream(e))),
|
||||
},
|
||||
);
|
||||
}
|
||||
24
src/client/connection/response/data/text/error.rs
Normal file
24
src/client/connection/response/data/text/error.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
BufferOverflow,
|
||||
Decode(std::string::FromUtf8Error),
|
||||
InputStream(glib::Error),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::BufferOverflow => {
|
||||
write!(f, "Buffer overflow")
|
||||
}
|
||||
Self::Decode(e) => {
|
||||
write!(f, "Decode error: {e}")
|
||||
}
|
||||
Self::InputStream(e) => {
|
||||
write!(f, "Input stream read error: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/client/connection/response/error.rs
Normal file
20
src/client/connection/response/error.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Meta(super::meta::Error),
|
||||
Stream,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Meta(e) => {
|
||||
write!(f, "Meta read error: {e}")
|
||||
}
|
||||
Self::Stream => {
|
||||
write!(f, "I/O stream error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
147
src/client/connection/response/meta.rs
Normal file
147
src/client/connection/response/meta.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
//! Components for reading and parsing meta bytes from response:
|
||||
//! * [Gemini status code](https://geminiprotocol.net/docs/protocol-specification.gmi#status-codes)
|
||||
//! * meta data (for interactive statuses like 10, 11, 30 etc)
|
||||
//! * MIME type
|
||||
|
||||
pub mod data;
|
||||
pub mod error;
|
||||
pub mod mime;
|
||||
pub mod status;
|
||||
|
||||
pub use data::Data;
|
||||
pub use error::Error;
|
||||
pub use mime::Mime;
|
||||
pub use status::Status;
|
||||
|
||||
use gio::{
|
||||
prelude::{IOStreamExt, InputStreamExtManual},
|
||||
Cancellable, IOStream,
|
||||
};
|
||||
use glib::{object::IsA, Priority};
|
||||
|
||||
pub const MAX_LEN: usize = 0x400; // 1024
|
||||
|
||||
pub struct Meta {
|
||||
pub status: Status,
|
||||
pub data: Option<Data>,
|
||||
pub mime: Option<Mime>,
|
||||
// @TODO
|
||||
// charset: Option<Charset>,
|
||||
// language: Option<Language>,
|
||||
}
|
||||
|
||||
impl Meta {
|
||||
// Constructors
|
||||
|
||||
/// Create new `Self` from UTF-8 buffer
|
||||
/// * supports entire response or just meta slice
|
||||
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
||||
// Calculate buffer length once
|
||||
let len = buffer.len();
|
||||
|
||||
// Parse meta bytes only
|
||||
match buffer.get(..if len > MAX_LEN { MAX_LEN } else { len }) {
|
||||
Some(slice) => {
|
||||
// Parse data
|
||||
let data = Data::from_utf8(slice);
|
||||
|
||||
if let Err(e) = data {
|
||||
return Err(Error::Data(e));
|
||||
}
|
||||
|
||||
// MIME
|
||||
|
||||
let mime = Mime::from_utf8(slice);
|
||||
|
||||
if let Err(e) = mime {
|
||||
return Err(Error::Mime(e));
|
||||
}
|
||||
|
||||
// Status
|
||||
|
||||
let status = Status::from_utf8(slice);
|
||||
|
||||
if let Err(e) = status {
|
||||
return Err(Error::Status(e));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
data: data.unwrap(),
|
||||
mime: mime.unwrap(),
|
||||
status: status.unwrap(),
|
||||
})
|
||||
}
|
||||
None => Err(Error::Protocol),
|
||||
}
|
||||
}
|
||||
|
||||
/// Asynchronously create new `Self` from [IOStream](https://docs.gtk.org/gio/class.IOStream.html)
|
||||
pub fn from_stream_async(
|
||||
stream: impl IsA<IOStream>,
|
||||
priority: Priority,
|
||||
cancellable: Cancellable,
|
||||
on_complete: impl FnOnce(Result<Self, Error>) + 'static,
|
||||
) {
|
||||
read_from_stream_async(
|
||||
Vec::with_capacity(MAX_LEN),
|
||||
stream,
|
||||
cancellable,
|
||||
priority,
|
||||
|result| match result {
|
||||
Ok(buffer) => on_complete(Self::from_utf8(&buffer)),
|
||||
Err(e) => on_complete(Err(e)),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Tools
|
||||
|
||||
/// Asynchronously read all meta bytes from [IOStream](https://docs.gtk.org/gio/class.IOStream.html)
|
||||
///
|
||||
/// Return UTF-8 buffer collected
|
||||
/// * require `IOStream` reference to keep `Connection` active in async thread
|
||||
pub fn read_from_stream_async(
|
||||
mut buffer: Vec<u8>,
|
||||
stream: impl IsA<IOStream>,
|
||||
cancellable: Cancellable,
|
||||
priority: Priority,
|
||||
on_complete: impl FnOnce(Result<Vec<u8>, Error>) + 'static,
|
||||
) {
|
||||
stream.input_stream().read_async(
|
||||
vec![0],
|
||||
priority,
|
||||
Some(&cancellable.clone()),
|
||||
move |result| match result {
|
||||
Ok((mut bytes, size)) => {
|
||||
// Expect valid header length
|
||||
if size == 0 || buffer.len() >= MAX_LEN {
|
||||
return on_complete(Err(Error::Protocol));
|
||||
}
|
||||
|
||||
// Read next byte without record
|
||||
if bytes.contains(&b'\r') {
|
||||
return read_from_stream_async(
|
||||
buffer,
|
||||
stream,
|
||||
cancellable,
|
||||
priority,
|
||||
on_complete,
|
||||
);
|
||||
}
|
||||
|
||||
// Complete without record
|
||||
if bytes.contains(&b'\n') {
|
||||
return on_complete(Ok(buffer));
|
||||
}
|
||||
|
||||
// Record
|
||||
buffer.append(&mut bytes);
|
||||
|
||||
// Continue
|
||||
read_from_stream_async(buffer, stream, cancellable, priority, on_complete);
|
||||
}
|
||||
Err((data, e)) => on_complete(Err(Error::InputStream(data, e))),
|
||||
},
|
||||
);
|
||||
}
|
||||
1
src/client/connection/response/meta/charset.rs
Normal file
1
src/client/connection/response/meta/charset.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// @TODO
|
||||
61
src/client/connection/response/meta/data.rs
Normal file
61
src/client/connection/response/meta/data.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
//! Components for reading and parsing meta **data** bytes from response
|
||||
//! (e.g. placeholder text for 10, 11, url string for 30, 31 etc)
|
||||
|
||||
pub mod error;
|
||||
pub use error::Error;
|
||||
|
||||
use glib::GString;
|
||||
|
||||
/// Meta **data** holder
|
||||
///
|
||||
/// For example, `value` could contain:
|
||||
/// * placeholder text for 10, 11 status
|
||||
/// * URL string for 30, 31 status
|
||||
pub struct Data {
|
||||
pub value: GString,
|
||||
}
|
||||
|
||||
impl Data {
|
||||
// Constructors
|
||||
|
||||
/// Parse meta **data** from UTF-8 buffer
|
||||
/// from entire response or just header slice
|
||||
///
|
||||
/// * result could be `None` for some [status codes](https://geminiprotocol.net/docs/protocol-specification.gmi#status-codes)
|
||||
/// that does not expect any data in header
|
||||
pub fn from_utf8(buffer: &[u8]) -> Result<Option<Self>, Error> {
|
||||
// Define max buffer length for this method
|
||||
const MAX_LEN: usize = 0x400; // 1024
|
||||
|
||||
// Init bytes buffer
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(MAX_LEN);
|
||||
|
||||
// Calculate len once
|
||||
let len = buffer.len();
|
||||
|
||||
// Skip 3 bytes for status code of `MAX_LEN` expected
|
||||
match buffer.get(3..if len > MAX_LEN { MAX_LEN - 3 } else { len }) {
|
||||
Some(slice) => {
|
||||
for &byte in slice {
|
||||
// End of header
|
||||
if byte == b'\r' {
|
||||
break;
|
||||
}
|
||||
|
||||
// Continue
|
||||
bytes.push(byte);
|
||||
}
|
||||
|
||||
// Assumes the bytes are valid UTF-8
|
||||
match GString::from_utf8(bytes) {
|
||||
Ok(value) => Ok(match value.is_empty() {
|
||||
false => Some(Self { value }),
|
||||
true => None,
|
||||
}),
|
||||
Err(e) => Err(Error::Decode(e)),
|
||||
}
|
||||
}
|
||||
None => Err(Error::Protocol),
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/client/connection/response/meta/data/error.rs
Normal file
20
src/client/connection/response/meta/data/error.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Decode(std::string::FromUtf8Error),
|
||||
Protocol,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Decode(e) => {
|
||||
write!(f, "Decode error: {e}")
|
||||
}
|
||||
Self::Protocol => {
|
||||
write!(f, "Protocol error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/client/connection/response/meta/error.rs
Normal file
33
src/client/connection/response/meta/error.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Data(super::data::Error),
|
||||
InputStream(Vec<u8>, glib::Error),
|
||||
Mime(super::mime::Error),
|
||||
Protocol,
|
||||
Status(super::status::Error),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Data(e) => {
|
||||
write!(f, "Data error: {e}")
|
||||
}
|
||||
Self::InputStream(_, e) => {
|
||||
// @TODO
|
||||
write!(f, "Input stream error: {e}")
|
||||
}
|
||||
Self::Mime(e) => {
|
||||
write!(f, "MIME error: {e}")
|
||||
}
|
||||
Self::Protocol => {
|
||||
write!(f, "Protocol error")
|
||||
}
|
||||
Self::Status(e) => {
|
||||
write!(f, "Status error: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/client/connection/response/meta/language.rs
Normal file
1
src/client/connection/response/meta/language.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// @TODO
|
||||
142
src/client/connection/response/meta/mime.rs
Normal file
142
src/client/connection/response/meta/mime.rs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
//! MIME type parser for different data types:
|
||||
//!
|
||||
//! * UTF-8 buffer with entire response or just with meta slice (that include entire **header**)
|
||||
//! * String (that include **header**)
|
||||
//! * [Uri](https://docs.gtk.org/glib/struct.Uri.html) (that include **extension**)
|
||||
//! * `std::Path` (that include **extension**)
|
||||
|
||||
pub mod error;
|
||||
pub use error::Error;
|
||||
|
||||
use glib::{GString, Uri};
|
||||
use std::path::Path;
|
||||
|
||||
/// https://geminiprotocol.net/docs/gemtext-specification.gmi#media-type-parameters
|
||||
#[derive(Debug)]
|
||||
pub enum Mime {
|
||||
// Text
|
||||
TextGemini,
|
||||
TextPlain,
|
||||
// Image
|
||||
ImageGif,
|
||||
ImageJpeg,
|
||||
ImagePng,
|
||||
ImageSvg,
|
||||
ImageWebp,
|
||||
// Audio
|
||||
AudioFlac,
|
||||
AudioMpeg,
|
||||
AudioOgg,
|
||||
} // @TODO
|
||||
|
||||
impl Mime {
|
||||
/// Create new `Self` from UTF-8 buffer (that includes **header**)
|
||||
///
|
||||
/// * result could be `None` for some [status codes](https://geminiprotocol.net/docs/protocol-specification.gmi#status-codes)
|
||||
/// that does not expect MIME type in header
|
||||
/// * includes `Self::from_string` parser,
|
||||
/// it means that given buffer should contain some **header** (not filepath or any other type of strings)
|
||||
pub fn from_utf8(buffer: &[u8]) -> Result<Option<Self>, Error> {
|
||||
// Define max buffer length for this method
|
||||
const MAX_LEN: usize = 0x400; // 1024
|
||||
|
||||
// Calculate buffer length once
|
||||
let len = buffer.len();
|
||||
|
||||
// Parse meta bytes only
|
||||
match buffer.get(..if len > MAX_LEN { MAX_LEN } else { len }) {
|
||||
Some(value) => match GString::from_utf8(value.into()) {
|
||||
Ok(string) => Self::from_string(string.as_str()),
|
||||
Err(e) => Err(Error::Decode(e)),
|
||||
},
|
||||
None => Err(Error::Protocol),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new `Self` from `std::Path` that includes file **extension**
|
||||
pub fn from_path(path: &Path) -> Result<Self, Error> {
|
||||
match path.extension().and_then(|extension| extension.to_str()) {
|
||||
// Text
|
||||
Some("gmi" | "gemini") => Ok(Self::TextGemini),
|
||||
Some("txt") => Ok(Self::TextPlain),
|
||||
|
||||
// Image
|
||||
Some("gif") => Ok(Self::ImageGif),
|
||||
Some("jpeg" | "jpg") => Ok(Self::ImageJpeg),
|
||||
Some("png") => Ok(Self::ImagePng),
|
||||
Some("svg") => Ok(Self::ImageSvg),
|
||||
Some("webp") => Ok(Self::ImageWebp),
|
||||
|
||||
// Audio
|
||||
Some("flac") => Ok(Self::AudioFlac),
|
||||
Some("mp3") => Ok(Self::AudioMpeg),
|
||||
Some("oga" | "ogg" | "opus" | "spx") => Ok(Self::AudioOgg),
|
||||
_ => Err(Error::Undefined),
|
||||
} // @TODO extension to lowercase
|
||||
}
|
||||
|
||||
/// Create new `Self` from string that includes **header**
|
||||
///
|
||||
/// **Return**
|
||||
///
|
||||
/// * `None` if MIME type not found
|
||||
/// * `Error::Undefined` if status code 2* and type not found in `Mime` enum
|
||||
pub fn from_string(value: &str) -> Result<Option<Self>, Error> {
|
||||
// Text
|
||||
if value.contains("text/gemini") {
|
||||
return Ok(Some(Self::TextGemini));
|
||||
}
|
||||
|
||||
if value.contains("text/plain") {
|
||||
return Ok(Some(Self::TextPlain));
|
||||
}
|
||||
|
||||
// Image
|
||||
if value.contains("image/gif") {
|
||||
return Ok(Some(Self::ImageGif));
|
||||
}
|
||||
|
||||
if value.contains("image/jpeg") {
|
||||
return Ok(Some(Self::ImageJpeg));
|
||||
}
|
||||
|
||||
if value.contains("image/png") {
|
||||
return Ok(Some(Self::ImagePng));
|
||||
}
|
||||
|
||||
if value.contains("image/svg+xml") {
|
||||
return Ok(Some(Self::ImageSvg));
|
||||
}
|
||||
|
||||
if value.contains("image/webp") {
|
||||
return Ok(Some(Self::ImageWebp));
|
||||
}
|
||||
|
||||
// Audio
|
||||
if value.contains("audio/flac") {
|
||||
return Ok(Some(Self::AudioFlac));
|
||||
}
|
||||
|
||||
if value.contains("audio/mpeg") {
|
||||
return Ok(Some(Self::AudioMpeg));
|
||||
}
|
||||
|
||||
if value.contains("audio/ogg") {
|
||||
return Ok(Some(Self::AudioOgg));
|
||||
}
|
||||
|
||||
// Some type exist, but not defined yet (on status code is 2*)
|
||||
if value.starts_with("2") && value.contains("/") {
|
||||
return Err(Error::Undefined);
|
||||
}
|
||||
|
||||
// Done
|
||||
Ok(None) // may be empty (status code ^2*)
|
||||
}
|
||||
|
||||
/// Create new `Self` from [Uri](https://docs.gtk.org/glib/struct.Uri.html)
|
||||
/// that includes file **extension**
|
||||
pub fn from_uri(uri: &Uri) -> Result<Self, Error> {
|
||||
Self::from_path(Path::new(&uri.to_string()))
|
||||
}
|
||||
}
|
||||
24
src/client/connection/response/meta/mime/error.rs
Normal file
24
src/client/connection/response/meta/mime/error.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Decode(std::string::FromUtf8Error),
|
||||
Protocol,
|
||||
Undefined,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Decode(e) => {
|
||||
write!(f, "Decode error: {e}")
|
||||
}
|
||||
Self::Protocol => {
|
||||
write!(f, "Protocol error")
|
||||
}
|
||||
Self::Undefined => {
|
||||
write!(f, "Undefined error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/client/connection/response/meta/status.rs
Normal file
82
src/client/connection/response/meta/status.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
//! Parser and holder tools for
|
||||
//! [Status code](https://geminiprotocol.net/docs/protocol-specification.gmi#status-codes)
|
||||
|
||||
pub mod error;
|
||||
pub use error::Error;
|
||||
|
||||
use glib::GString;
|
||||
|
||||
/// Holder for [status code](https://geminiprotocol.net/docs/protocol-specification.gmi#status-codes)
|
||||
#[derive(Debug)]
|
||||
pub enum Status {
|
||||
// Input
|
||||
Input = 10,
|
||||
SensitiveInput = 11,
|
||||
// Success
|
||||
Success = 20,
|
||||
// Redirect
|
||||
Redirect = 30,
|
||||
PermanentRedirect = 31,
|
||||
// Temporary failure
|
||||
TemporaryFailure = 40,
|
||||
ServerUnavailable = 41,
|
||||
CgiError = 42,
|
||||
ProxyError = 43,
|
||||
SlowDown = 44,
|
||||
// Permanent failure
|
||||
PermanentFailure = 50,
|
||||
NotFound = 51,
|
||||
ResourceGone = 52,
|
||||
ProxyRequestRefused = 53,
|
||||
BadRequest = 59,
|
||||
// Client certificates
|
||||
CertificateRequest = 60,
|
||||
CertificateUnauthorized = 61,
|
||||
CertificateInvalid = 62,
|
||||
}
|
||||
|
||||
impl Status {
|
||||
/// Create new `Self` from UTF-8 buffer
|
||||
///
|
||||
/// * includes `Self::from_string` parser, it means that given buffer should contain some **header**
|
||||
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
||||
match buffer.get(0..2) {
|
||||
Some(value) => match GString::from_utf8(value.to_vec()) {
|
||||
Ok(string) => Self::from_string(string.as_str()),
|
||||
Err(e) => Err(Error::Decode(e)),
|
||||
},
|
||||
None => Err(Error::Protocol),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new `Self` from string that includes **header**
|
||||
pub fn from_string(code: &str) -> Result<Self, Error> {
|
||||
match code {
|
||||
// Input
|
||||
"10" => Ok(Self::Input),
|
||||
"11" => Ok(Self::SensitiveInput),
|
||||
// Success
|
||||
"20" => Ok(Self::Success),
|
||||
// Redirect
|
||||
"30" => Ok(Self::Redirect),
|
||||
"31" => Ok(Self::PermanentRedirect),
|
||||
// Temporary failure
|
||||
"40" => Ok(Self::TemporaryFailure),
|
||||
"41" => Ok(Self::ServerUnavailable),
|
||||
"42" => Ok(Self::CgiError),
|
||||
"43" => Ok(Self::ProxyError),
|
||||
"44" => Ok(Self::SlowDown),
|
||||
// Permanent failure
|
||||
"50" => Ok(Self::PermanentFailure),
|
||||
"51" => Ok(Self::NotFound),
|
||||
"52" => Ok(Self::ResourceGone),
|
||||
"53" => Ok(Self::ProxyRequestRefused),
|
||||
"59" => Ok(Self::BadRequest),
|
||||
// Client certificates
|
||||
"60" => Ok(Self::CertificateRequest),
|
||||
"61" => Ok(Self::CertificateUnauthorized),
|
||||
"62" => Ok(Self::CertificateInvalid),
|
||||
_ => Err(Error::Undefined),
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/client/connection/response/meta/status/error.rs
Normal file
24
src/client/connection/response/meta/status/error.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Decode(std::string::FromUtf8Error),
|
||||
Protocol,
|
||||
Undefined,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Decode(e) => {
|
||||
write!(f, "Decode error: {e}")
|
||||
}
|
||||
Self::Protocol => {
|
||||
write!(f, "Protocol error")
|
||||
}
|
||||
Self::Undefined => {
|
||||
write!(f, "Undefined error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue