diff --git a/src/client/connection.rs b/src/client/connection.rs index fdda6dc..eae8d45 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -6,13 +6,15 @@ pub use error::Error; pub use request::{Gemini, Request, Titan}; pub use response::Response; +// Local dependencies + use gio::{ - prelude::{IOStreamExt, OutputStreamExt, TlsConnectionExt}, + prelude::{IOStreamExt, OutputStreamExtManual, TlsConnectionExt}, Cancellable, IOStream, NetworkAddress, SocketConnection, TlsCertificate, TlsClientConnection, }; use glib::{ object::{Cast, ObjectExt}, - Bytes, Priority, + Priority, }; pub struct Connection { @@ -58,24 +60,8 @@ impl Connection { cancellable: Cancellable, callback: impl FnOnce(Result) + 'static, ) { - self.bytes_request_async(&request.to_bytes(), priority, cancellable, callback); - } - - /// Low-level shared method to send raw bytes array over - /// [Gemini](https://geminiprotocol.net/docs/protocol-specification.gmi) or - /// [Titan](gemini://transjovian.org/titan/page/The%20Titan%20Specification) protocol - /// * bytes array should include formatted header according to protocol selected - /// * for high-level requests see `gemini_request_async` and `titan_request_async` methods - /// * to construct multi-protocol request with single function, use `request_async` method - pub fn bytes_request_async( - self, - request: &Bytes, - priority: Priority, - cancellable: Cancellable, - callback: impl FnOnce(Result) + 'static, - ) { - self.stream().output_stream().write_bytes_async( - request, + self.stream().output_stream().write_async( + request.header().into_bytes(), priority, Some(&cancellable.clone()), move |result| match result { @@ -88,7 +74,7 @@ impl Connection { }) }) } - Err(e) => callback(Err(Error::Stream(e))), + Err((b, e)) => callback(Err(Error::Request((b, e)))), }, ); } diff --git a/src/client/connection/error.rs b/src/client/connection/error.rs index 2948f0f..fe667e6 100644 --- a/src/client/connection/error.rs +++ b/src/client/connection/error.rs @@ -2,16 +2,16 @@ use std::fmt::{Display, Formatter, Result}; #[derive(Debug)] pub enum Error { + Request((Vec, glib::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::Request((_, e)) => { + write!(f, "Request error: {e}") } Self::Response(e) => { write!(f, "Response error: {e}") diff --git a/src/client/connection/request.rs b/src/client/connection/request.rs index f1d8506..790374e 100644 --- a/src/client/connection/request.rs +++ b/src/client/connection/request.rs @@ -7,7 +7,6 @@ pub use gemini::Gemini; pub use titan::Titan; use gio::NetworkAddress; -use glib::{Bytes, Uri}; /// Single `Request` implementation for different protocols pub enum Request { @@ -16,30 +15,13 @@ pub enum Request { } impl Request { - // Constructors - - /// Create new `Self` for [Gemini protocol](https://geminiprotocol.net) - pub fn gemini(uri: Uri) -> Self { - Self::Gemini(Gemini { uri }) - } - - /// Create new `Self` for [Titan protocol](gemini://transjovian.org/titan/page/The%20Titan%20Specification) - pub fn titan(uri: Uri, data: Vec, mime: Option, token: Option) -> Self { - Self::Titan(Titan { - uri, - data, - mime, - token, - }) - } - // Getters - /// Get [Bytes](https://docs.gtk.org/glib/struct.Bytes.html) for `Self` - pub fn to_bytes(&self) -> Bytes { + /// Get header string for `Self` + pub fn header(&self) -> String { match self { - Self::Gemini(ref request) => request.to_bytes(), - Self::Titan(ref request) => request.to_bytes(), + Self::Gemini(ref this) => this.header(), + Self::Titan(ref this) => this.header(), } } diff --git a/src/client/connection/request/gemini.rs b/src/client/connection/request/gemini.rs index 78ec59b..322d58b 100644 --- a/src/client/connection/request/gemini.rs +++ b/src/client/connection/request/gemini.rs @@ -1,4 +1,4 @@ -use glib::{Bytes, Uri}; +use glib::Uri; /// [Gemini](https://geminiprotocol.net/docs/protocol-specification.gmi) protocol enum object for `Request` pub struct Gemini { @@ -8,8 +8,8 @@ pub struct Gemini { impl Gemini { // Getters - /// Copy `Self` to [Bytes](https://docs.gtk.org/glib/struct.Bytes.html) - pub fn to_bytes(&self) -> Bytes { - Bytes::from(format!("{}\r\n", self.uri).as_bytes()) + /// Get header string for `Self` + pub fn header(&self) -> String { + format!("{}\r\n", self.uri) } } diff --git a/src/client/connection/request/titan.rs b/src/client/connection/request/titan.rs index 504c8cb..333a7ff 100644 --- a/src/client/connection/request/titan.rs +++ b/src/client/connection/request/titan.rs @@ -3,7 +3,7 @@ use glib::{Bytes, Uri, UriHideFlags}; /// [Titan](gemini://transjovian.org/titan/page/The%20Titan%20Specification) protocol enum object for `Request` pub struct Titan { pub uri: Uri, - pub data: Vec, + pub data: Bytes, pub mime: Option, pub token: Option, } @@ -11,8 +11,8 @@ pub struct Titan { impl Titan { // Getters - /// Copy `Self` to [Bytes](https://docs.gtk.org/glib/struct.Bytes.html) - pub fn to_bytes(&self) -> Bytes { + /// Get header string for `Self` + pub fn header(&self) -> String { // Calculate data size let size = self.data.len(); @@ -31,13 +31,6 @@ impl Titan { header.push_str(&format!("?{query}")); } header.push_str("\r\n"); - - // Build request - let mut bytes: Vec = Vec::with_capacity(size + 1024); // @TODO - bytes.extend(header.into_bytes()); - bytes.extend(&self.data); - - // Wrap result - Bytes::from(&bytes) + header } } diff --git a/tests/integration.rs b/tests/integration.rs index 0b38c7a..8205495 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -7,10 +7,10 @@ use ggemini::client::connection::Request; fn client_connection_request_gemini() { const REQUEST: &str = "gemini://geminiprotocol.net/"; assert_eq!( - std::str::from_utf8( - &Request::gemini(Uri::parse(REQUEST, UriFlags::NONE).unwrap()).to_bytes() - ) - .unwrap(), + Request::Gemini(ggemini::client::connection::Gemini { + uri: Uri::parse(REQUEST, UriFlags::NONE).unwrap() + }) + .header(), format!("{REQUEST}\r\n") ); } @@ -20,23 +20,21 @@ fn client_connection_request_titan() { const DATA: &[u8] = &[1, 2, 3]; const MIME: &str = "plain/text"; const TOKEN: &str = "token"; - const ARGUMENT: &str = "argument"; - const REQUEST: &str = "titan://geminiprotocol.net/raw/Test"; assert_eq!( - std::str::from_utf8( - &Request::titan( - Uri::parse(&format!("{REQUEST}?argument={ARGUMENT}"), UriFlags::NONE).unwrap(), - DATA.to_vec(), - Some(MIME.to_string()), - Some(TOKEN.to_string()), + Request::Titan(ggemini::client::connection::Titan { + uri: Uri::parse( + "titan://geminiprotocol.net/raw/Test?key=value", + UriFlags::NONE ) - .to_bytes() - ) - .unwrap(), + .unwrap(), + data: Bytes::from(DATA), + mime: Some(MIME.to_string()), + token: Some(TOKEN.to_string()) + }) + .header(), format!( - "{REQUEST};size={};mime={MIME};token={TOKEN}?argument={ARGUMENT}\r\n{}", + "titan://geminiprotocol.net/raw/Test;size={};mime={MIME};token={TOKEN}?key=value\r\n", DATA.len(), - std::str::from_utf8(DATA).unwrap(), ) ); }