mirror of
https://github.com/YGGverse/ggemini.git
synced 2026-03-31 17:15:31 +00:00
draft new api version
This commit is contained in:
parent
67d486cc4d
commit
3a9e84a3d9
19 changed files with 490 additions and 87 deletions
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ggemini"
|
||||
version = "0.10.0"
|
||||
version = "0.11.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
|
|
|
|||
128
src/client.rs
128
src/client.rs
|
|
@ -1,4 +1,128 @@
|
|||
//! Client API to interact Server using
|
||||
//! [Gemini protocol](https://geminiprotocol.net/docs/protocol-specification.gmi)
|
||||
//! High-level client API to interact with Gemini Socket Server:
|
||||
//! * https://geminiprotocol.net/docs/protocol-specification.gmi
|
||||
|
||||
pub mod connection;
|
||||
pub mod error;
|
||||
pub mod response;
|
||||
|
||||
pub use connection::Connection;
|
||||
pub use error::Error;
|
||||
pub use response::Response;
|
||||
|
||||
use gio::{
|
||||
prelude::{IOStreamExt, OutputStreamExt, SocketClientExt},
|
||||
Cancellable, NetworkAddress, SocketClient, SocketProtocol, TlsCertificate,
|
||||
};
|
||||
use glib::{Bytes, Priority, Uri};
|
||||
|
||||
pub const DEFAULT_PORT: u16 = 1965;
|
||||
pub const DEFAULT_TIMEOUT: u32 = 10;
|
||||
|
||||
pub struct Client {
|
||||
pub socket: SocketClient,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
// Constructors
|
||||
|
||||
/// Create new `Self`
|
||||
pub fn new() -> Self {
|
||||
let socket = SocketClient::new();
|
||||
|
||||
socket.set_protocol(SocketProtocol::Tcp);
|
||||
socket.set_timeout(DEFAULT_TIMEOUT);
|
||||
|
||||
Self { socket }
|
||||
}
|
||||
|
||||
// Actions
|
||||
|
||||
/// Make async request to given [Uri](https://docs.gtk.org/glib/struct.Uri.html),
|
||||
/// callback with `Result`on success or `Error` on failure.
|
||||
/// * creates new [SocketConnection](https://docs.gtk.org/gio/class.SocketConnection.html)
|
||||
/// * session management by Glib TLS Backend
|
||||
pub fn request_async(
|
||||
&self,
|
||||
uri: Uri,
|
||||
priority: Option<Priority>,
|
||||
cancellable: Option<Cancellable>,
|
||||
certificate: Option<TlsCertificate>,
|
||||
callback: impl Fn(Result<Response, Error>) + 'static,
|
||||
) {
|
||||
match network_address_for(&uri) {
|
||||
Ok(network_address) => {
|
||||
self.socket.connect_async(
|
||||
&network_address.clone(),
|
||||
match cancellable {
|
||||
Some(ref cancellable) => Some(cancellable.clone()),
|
||||
None => None::<Cancellable>,
|
||||
}
|
||||
.as_ref(),
|
||||
move |result| match result {
|
||||
Ok(connection) => {
|
||||
match Connection::from(network_address, connection, certificate) {
|
||||
Ok(result) => request_async(
|
||||
result,
|
||||
uri.to_string(),
|
||||
match priority {
|
||||
Some(priority) => priority,
|
||||
None => Priority::DEFAULT,
|
||||
},
|
||||
cancellable.unwrap(), // @TODO
|
||||
move |result| callback(result),
|
||||
),
|
||||
Err(reason) => callback(Err(Error::Connection(reason))),
|
||||
}
|
||||
}
|
||||
Err(reason) => callback(Err(Error::Connect(reason))),
|
||||
},
|
||||
);
|
||||
}
|
||||
Err(reason) => callback(Err(reason)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Private helpers
|
||||
|
||||
/// [SocketConnectable](https://docs.gtk.org/gio/iface.SocketConnectable.html) /
|
||||
/// [SNI](https://geminiprotocol.net/docs/protocol-specification.gmi#server-name-indication)
|
||||
fn network_address_for(uri: &Uri) -> Result<NetworkAddress, Error> {
|
||||
Ok(NetworkAddress::new(
|
||||
&match uri.host() {
|
||||
Some(host) => host,
|
||||
None => return Err(Error::Connectable(uri.to_string())),
|
||||
},
|
||||
if uri.port().is_positive() {
|
||||
uri.port() as u16
|
||||
} else {
|
||||
DEFAULT_PORT
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn request_async(
|
||||
connection: Connection,
|
||||
query: String,
|
||||
priority: Priority,
|
||||
cancellable: Cancellable,
|
||||
callback: impl Fn(Result<Response, Error>) + 'static,
|
||||
) {
|
||||
connection.stream().output_stream().write_bytes_async(
|
||||
&Bytes::from(format!("{query}\r\n").as_bytes()),
|
||||
priority,
|
||||
Some(&cancellable.clone()),
|
||||
move |result| match result {
|
||||
Ok(_) => Response::from_request_async(
|
||||
connection,
|
||||
Some(priority),
|
||||
Some(cancellable),
|
||||
move |result| match result {
|
||||
Ok(response) => callback(Ok(response)),
|
||||
Err(reason) => callback(Err(Error::Response(reason))),
|
||||
},
|
||||
),
|
||||
Err(reason) => callback(Err(Error::Write(reason))),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
82
src/client/connection.rs
Normal file
82
src/client/connection.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
pub mod certificate;
|
||||
pub mod error;
|
||||
|
||||
pub use certificate::Certificate;
|
||||
pub use error::Error;
|
||||
|
||||
use gio::{
|
||||
prelude::{IOStreamExt, TlsConnectionExt},
|
||||
IOStream, NetworkAddress, SocketConnection, TlsCertificate, TlsClientConnection,
|
||||
};
|
||||
use glib::object::{Cast, IsA};
|
||||
|
||||
pub struct Connection {
|
||||
pub socket_connection: SocketConnection,
|
||||
pub tls_client_connection: Option<TlsClientConnection>,
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
// Constructors
|
||||
|
||||
/// Create new `Self`
|
||||
pub fn from(
|
||||
network_address: NetworkAddress, // @TODO struct cert as sni
|
||||
socket_connection: SocketConnection,
|
||||
certificate: Option<TlsCertificate>,
|
||||
) -> Result<Self, Error> {
|
||||
if socket_connection.is_closed() {
|
||||
return Err(Error::Closed);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
socket_connection: socket_connection.clone(),
|
||||
tls_client_connection: match certificate {
|
||||
Some(certificate) => match auth(network_address, socket_connection, certificate) {
|
||||
Ok(tls_client_connection) => Some(tls_client_connection),
|
||||
Err(reason) => return Err(reason),
|
||||
},
|
||||
None => None,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
pub fn stream(&self) -> impl IsA<IOStream> {
|
||||
match self.tls_client_connection.clone() {
|
||||
Some(tls_client_connection) => tls_client_connection.upcast::<IOStream>(),
|
||||
None => self.socket_connection.clone().upcast::<IOStream>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tools
|
||||
|
||||
pub fn auth(
|
||||
server_identity: NetworkAddress, // @TODO impl IsA<SocketConnectable> ?
|
||||
socket_connection: SocketConnection,
|
||||
certificate: TlsCertificate,
|
||||
) -> Result<TlsClientConnection, Error> {
|
||||
if socket_connection.is_closed() {
|
||||
return Err(Error::Closed);
|
||||
}
|
||||
|
||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#the-use-of-tls
|
||||
match TlsClientConnection::new(&socket_connection, Some(&server_identity)) {
|
||||
Ok(tls_client_connection) => {
|
||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#client-certificates
|
||||
tls_client_connection.set_certificate(&certificate);
|
||||
|
||||
// @TODO handle exceptions
|
||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#closing-connections
|
||||
tls_client_connection.set_require_close_notify(true);
|
||||
|
||||
// @TODO host validation
|
||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#tls-server-certificate-validation
|
||||
tls_client_connection.connect_accept_certificate(move |_, _, _| true);
|
||||
|
||||
Ok(tls_client_connection)
|
||||
}
|
||||
Err(reason) => Err(Error::Tls(reason)),
|
||||
}
|
||||
}
|
||||
52
src/client/connection/certificate.rs
Normal file
52
src/client/connection/certificate.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
pub mod error;
|
||||
pub mod scope;
|
||||
|
||||
pub use error::Error;
|
||||
pub use scope::Scope;
|
||||
|
||||
use gio::{prelude::TlsCertificateExt, TlsCertificate};
|
||||
use glib::DateTime;
|
||||
|
||||
pub struct Certificate {
|
||||
tls_certificate: TlsCertificate,
|
||||
}
|
||||
|
||||
impl Certificate {
|
||||
// Constructors
|
||||
|
||||
/// Create new `Self`
|
||||
pub fn from_pem(pem: &str) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
tls_certificate: match TlsCertificate::from_pem(&pem) {
|
||||
Ok(tls_certificate) => {
|
||||
// Validate expiration time
|
||||
match DateTime::now_local() {
|
||||
Ok(now_local) => {
|
||||
match tls_certificate.not_valid_after() {
|
||||
Some(not_valid_after) => {
|
||||
if now_local > not_valid_after {
|
||||
return Err(Error::Expired(not_valid_after));
|
||||
}
|
||||
}
|
||||
None => return Err(Error::ValidAfter),
|
||||
}
|
||||
match tls_certificate.not_valid_before() {
|
||||
Some(not_valid_before) => {
|
||||
if now_local < not_valid_before {
|
||||
return Err(Error::Inactive(not_valid_before));
|
||||
}
|
||||
}
|
||||
None => return Err(Error::ValidBefore),
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(Error::DateTime),
|
||||
}
|
||||
|
||||
// Success
|
||||
tls_certificate
|
||||
}
|
||||
Err(reason) => return Err(Error::Decode(reason)),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
16
src/client/connection/error.rs
Normal file
16
src/client/connection/error.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Closed,
|
||||
Tls(glib::Error),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Closed => write!(f, "Socket connection closed"),
|
||||
Self::Tls(reason) => write!(f, "Could not create TLS connection: {reason}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/client/error.rs
Normal file
36
src/client/error.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Connectable(String),
|
||||
Connection(super::connection::Error),
|
||||
Connect(glib::Error),
|
||||
Request(glib::Error),
|
||||
Response(super::response::Error),
|
||||
Write(glib::Error),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Connectable(uri) => {
|
||||
write!(f, "Could not create connectable address for {uri}")
|
||||
}
|
||||
Self::Connection(reason) => {
|
||||
write!(f, "Connection error: {reason}")
|
||||
}
|
||||
Self::Connect(reason) => {
|
||||
write!(f, "Connect error: {reason}")
|
||||
}
|
||||
Self::Request(reason) => {
|
||||
write!(f, "Request error: {reason}")
|
||||
}
|
||||
Self::Response(reason) => {
|
||||
write!(f, "Response error: {reason}")
|
||||
}
|
||||
Self::Write(reason) => {
|
||||
write!(f, "I/O Write error: {reason}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,35 @@
|
|||
//! 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
|
||||
|
||||
pub fn from_request_async(
|
||||
connection: Connection,
|
||||
priority: Option<Priority>,
|
||||
cancellable: Option<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(reason) => Err(Error::Meta(reason)),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ pub const BUFFER_MAX_SIZE: usize = 0xfffff; // 1M
|
|||
|
||||
/// Container for text-based response data
|
||||
pub struct Text {
|
||||
data: GString,
|
||||
pub data: GString,
|
||||
}
|
||||
|
||||
impl Default for Text {
|
||||
|
|
@ -41,10 +41,10 @@ impl Text {
|
|||
}
|
||||
|
||||
/// Create new `Self` from UTF-8 buffer
|
||||
pub fn from_utf8(buffer: &[u8]) -> Result<Self, (Error, Option<&str>)> {
|
||||
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
||||
match GString::from_utf8(buffer.into()) {
|
||||
Ok(data) => Ok(Self::from_string(&data)),
|
||||
Err(_) => Err((Error::Decode, None)),
|
||||
Err(reason) => Err(Error::Decode(reason)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ impl Text {
|
|||
stream: impl IsA<IOStream>,
|
||||
priority: Option<Priority>,
|
||||
cancellable: Option<Cancellable>,
|
||||
on_complete: impl FnOnce(Result<Self, (Error, Option<&str>)>) + 'static,
|
||||
on_complete: impl FnOnce(Result<Self, Error>) + 'static,
|
||||
) {
|
||||
read_all_from_stream_async(
|
||||
Vec::with_capacity(BUFFER_CAPACITY),
|
||||
|
|
@ -72,13 +72,6 @@ impl Text {
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
/// Get reference to `Self` data
|
||||
pub fn data(&self) -> &GString {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
// Tools
|
||||
|
|
@ -92,7 +85,7 @@ pub fn read_all_from_stream_async(
|
|||
stream: impl IsA<IOStream>,
|
||||
cancelable: Option<Cancellable>,
|
||||
priority: Priority,
|
||||
callback: impl FnOnce(Result<Vec<u8>, (Error, Option<&str>)>) + 'static,
|
||||
callback: impl FnOnce(Result<Vec<u8>, Error>) + 'static,
|
||||
) {
|
||||
stream.input_stream().read_bytes_async(
|
||||
BUFFER_CAPACITY,
|
||||
|
|
@ -107,7 +100,7 @@ pub fn read_all_from_stream_async(
|
|||
|
||||
// Validate overflow
|
||||
if buffer.len() + bytes.len() > BUFFER_MAX_SIZE {
|
||||
return callback(Err((Error::BufferOverflow, None)));
|
||||
return callback(Err(Error::BufferOverflow));
|
||||
}
|
||||
|
||||
// Save chunks to buffer
|
||||
|
|
@ -118,7 +111,7 @@ pub fn read_all_from_stream_async(
|
|||
// Continue bytes reading
|
||||
read_all_from_stream_async(buffer, stream, cancelable, priority, callback);
|
||||
}
|
||||
Err(reason) => callback(Err((Error::InputStream, Some(reason.message())))),
|
||||
Err(reason) => callback(Err(Error::InputStreamRead(reason))),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,24 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
BufferOverflow,
|
||||
Decode,
|
||||
InputStream,
|
||||
Decode(std::string::FromUtf8Error),
|
||||
InputStreamRead(glib::Error),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::BufferOverflow => {
|
||||
write!(f, "Buffer overflow")
|
||||
}
|
||||
Self::Decode(reason) => {
|
||||
write!(f, "Decode error: {reason}")
|
||||
}
|
||||
Self::InputStreamRead(reason) => {
|
||||
write!(f, "Input stream read error: {reason}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
src/client/response/error.rs
Normal file
20
src/client/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(reason) => {
|
||||
write!(f, "Meta read error: {reason}")
|
||||
}
|
||||
Self::Stream => {
|
||||
write!(f, "I/O stream error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,9 +22,9 @@ use glib::{object::IsA, Priority};
|
|||
pub const MAX_LEN: usize = 0x400; // 1024
|
||||
|
||||
pub struct Meta {
|
||||
status: Status,
|
||||
data: Option<Data>,
|
||||
mime: Option<Mime>,
|
||||
pub status: Status,
|
||||
pub data: Option<Data>,
|
||||
pub mime: Option<Mime>,
|
||||
// @TODO
|
||||
// charset: Option<Charset>,
|
||||
// language: Option<Language>,
|
||||
|
|
@ -35,7 +35,7 @@ impl Meta {
|
|||
|
||||
/// Create new `Self` from UTF-8 buffer
|
||||
/// * supports entire response or just meta slice
|
||||
pub fn from_utf8(buffer: &[u8]) -> Result<Self, (Error, Option<&str>)> {
|
||||
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
||||
// Calculate buffer length once
|
||||
let len = buffer.len();
|
||||
|
||||
|
|
@ -46,13 +46,7 @@ impl Meta {
|
|||
let data = Data::from_utf8(slice);
|
||||
|
||||
if let Err(reason) = data {
|
||||
return Err((
|
||||
match reason {
|
||||
data::Error::Decode => Error::DataDecode,
|
||||
data::Error::Protocol => Error::DataProtocol,
|
||||
},
|
||||
None,
|
||||
));
|
||||
return Err(Error::Data(reason));
|
||||
}
|
||||
|
||||
// MIME
|
||||
|
|
@ -60,14 +54,7 @@ impl Meta {
|
|||
let mime = Mime::from_utf8(slice);
|
||||
|
||||
if let Err(reason) = mime {
|
||||
return Err((
|
||||
match reason {
|
||||
mime::Error::Decode => Error::MimeDecode,
|
||||
mime::Error::Protocol => Error::MimeProtocol,
|
||||
mime::Error::Undefined => Error::MimeUndefined,
|
||||
},
|
||||
None,
|
||||
));
|
||||
return Err(Error::Mime(reason));
|
||||
}
|
||||
|
||||
// Status
|
||||
|
|
@ -75,14 +62,7 @@ impl Meta {
|
|||
let status = Status::from_utf8(slice);
|
||||
|
||||
if let Err(reason) = status {
|
||||
return Err((
|
||||
match reason {
|
||||
status::Error::Decode => Error::StatusDecode,
|
||||
status::Error::Protocol => Error::StatusProtocol,
|
||||
status::Error::Undefined => Error::StatusUndefined,
|
||||
},
|
||||
None,
|
||||
));
|
||||
return Err(Error::Status(reason));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
|
|
@ -91,7 +71,7 @@ impl Meta {
|
|||
status: status.unwrap(),
|
||||
})
|
||||
}
|
||||
None => Err((Error::Protocol, None)),
|
||||
None => Err(Error::Protocol),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +80,7 @@ impl Meta {
|
|||
stream: impl IsA<IOStream>,
|
||||
priority: Option<Priority>,
|
||||
cancellable: Option<Cancellable>,
|
||||
on_complete: impl FnOnce(Result<Self, (Error, Option<&str>)>) + 'static,
|
||||
on_complete: impl FnOnce(Result<Self, Error>) + 'static,
|
||||
) {
|
||||
read_from_stream_async(
|
||||
Vec::with_capacity(MAX_LEN),
|
||||
|
|
@ -119,20 +99,6 @@ impl Meta {
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
pub fn status(&self) -> &Status {
|
||||
&self.status
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &Option<Data> {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn mime(&self) -> &Option<Mime> {
|
||||
&self.mime
|
||||
}
|
||||
}
|
||||
|
||||
// Tools
|
||||
|
|
@ -146,7 +112,7 @@ pub fn read_from_stream_async(
|
|||
stream: impl IsA<IOStream>,
|
||||
cancellable: Option<Cancellable>,
|
||||
priority: Priority,
|
||||
on_complete: impl FnOnce(Result<Vec<u8>, (Error, Option<&str>)>) + 'static,
|
||||
on_complete: impl FnOnce(Result<Vec<u8>, Error>) + 'static,
|
||||
) {
|
||||
stream.input_stream().read_async(
|
||||
vec![0],
|
||||
|
|
@ -156,7 +122,7 @@ pub fn read_from_stream_async(
|
|||
Ok((mut bytes, size)) => {
|
||||
// Expect valid header length
|
||||
if size == 0 || buffer.len() >= MAX_LEN {
|
||||
return on_complete(Err((Error::Protocol, None)));
|
||||
return on_complete(Err(Error::Protocol));
|
||||
}
|
||||
|
||||
// Read next byte without record
|
||||
|
|
@ -181,7 +147,7 @@ pub fn read_from_stream_async(
|
|||
// Continue
|
||||
read_from_stream_async(buffer, stream, cancellable, priority, on_complete);
|
||||
}
|
||||
Err((_, reason)) => on_complete(Err((Error::InputStream, Some(reason.message())))),
|
||||
Err((data, reason)) => on_complete(Err(Error::InputStreamRead(data, reason))),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use glib::GString;
|
|||
/// * placeholder text for 10, 11 status
|
||||
/// * URL string for 30, 31 status
|
||||
pub struct Data {
|
||||
value: GString,
|
||||
pub value: GString,
|
||||
}
|
||||
|
||||
impl Data {
|
||||
|
|
@ -52,16 +52,10 @@ impl Data {
|
|||
false => Some(Self { value }),
|
||||
true => None,
|
||||
}),
|
||||
Err(_) => Err(Error::Decode),
|
||||
Err(reason) => Err(Error::Decode(reason)),
|
||||
}
|
||||
}
|
||||
None => Err(Error::Protocol),
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
pub fn value(&self) -> &GString {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,20 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Decode,
|
||||
Decode(std::string::FromUtf8Error),
|
||||
Protocol,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Decode(reason) => {
|
||||
write!(f, "Decode error: {reason}")
|
||||
}
|
||||
Self::Protocol => {
|
||||
write!(f, "Protocol error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,33 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
DataDecode,
|
||||
DataProtocol,
|
||||
InputStream,
|
||||
MimeDecode,
|
||||
MimeProtocol,
|
||||
MimeUndefined,
|
||||
Data(super::data::Error),
|
||||
InputStreamRead(Vec<u8>, glib::Error),
|
||||
Mime(super::mime::Error),
|
||||
Protocol,
|
||||
StatusDecode,
|
||||
StatusProtocol,
|
||||
StatusUndefined,
|
||||
Status(super::status::Error),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Data(reason) => {
|
||||
write!(f, "Data error: {reason}")
|
||||
}
|
||||
Self::InputStreamRead(_, reason) => {
|
||||
// @TODO
|
||||
write!(f, "Input stream error: {reason}")
|
||||
}
|
||||
Self::Mime(reason) => {
|
||||
write!(f, "MIME error: {reason}")
|
||||
}
|
||||
Self::Protocol => {
|
||||
write!(f, "Protocol error")
|
||||
}
|
||||
Self::Status(reason) => {
|
||||
write!(f, "Status error: {reason}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ impl Mime {
|
|||
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(_) => Err(Error::Decode),
|
||||
Err(reason) => Err(Error::Decode(reason)),
|
||||
},
|
||||
None => Err(Error::Protocol),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,24 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Decode,
|
||||
Decode(std::string::FromUtf8Error),
|
||||
Protocol,
|
||||
Undefined,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Decode(reason) => {
|
||||
write!(f, "Decode error: {reason}")
|
||||
}
|
||||
Self::Protocol => {
|
||||
write!(f, "Protocol error")
|
||||
}
|
||||
Self::Undefined => {
|
||||
write!(f, "Undefined error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ impl Status {
|
|||
match buffer.get(0..2) {
|
||||
Some(value) => match GString::from_utf8(value.to_vec()) {
|
||||
Ok(string) => Self::from_string(string.as_str()),
|
||||
Err(_) => Err(Error::Decode),
|
||||
Err(reason) => Err(Error::Decode(reason)),
|
||||
},
|
||||
None => Err(Error::Protocol),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,24 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Decode,
|
||||
Decode(std::string::FromUtf8Error),
|
||||
Protocol,
|
||||
Undefined,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Decode(reason) => {
|
||||
write!(f, "Decode error: {reason}")
|
||||
}
|
||||
Self::Protocol => {
|
||||
write!(f, "Protocol error")
|
||||
}
|
||||
Self::Undefined => {
|
||||
write!(f, "Undefined error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,4 @@
|
|||
pub mod client;
|
||||
pub mod gio;
|
||||
|
||||
pub use client::Client;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue