implement titan and gemini requests in single file

This commit is contained in:
yggverse 2025-01-27 20:42:17 +02:00
parent 0cb5ff9cbc
commit 6da4c2ed52
5 changed files with 87 additions and 112 deletions

View file

@ -53,9 +53,9 @@ use ggemini::client::{
fn main() -> ExitCode { fn main() -> ExitCode {
Client::new().request_async( Client::new().request_async(
Request::gemini( // or `Request::titan` Request::Gemini { // or `Request::Titan`
Uri::parse("gemini://geminiprotocol.net/", UriFlags::NONE).unwrap(), uri: Uri::parse("gemini://geminiprotocol.net/", UriFlags::NONE).unwrap(),
), },
Priority::DEFAULT, Priority::DEFAULT,
Cancellable::new(), Cancellable::new(),
None, // optional `GTlsCertificate` None, // optional `GTlsCertificate`

View file

@ -3,7 +3,7 @@ pub mod request;
pub mod response; pub mod response;
pub use error::Error; pub use error::Error;
pub use request::{Gemini, Request, Titan}; pub use request::Request;
pub use response::Response; pub use response::Response;
// Local dependencies // Local dependencies
@ -67,7 +67,7 @@ impl Connection {
Some(&cancellable.clone()), Some(&cancellable.clone()),
move |result| match result { move |result| match result {
Ok(_) => match request { Ok(_) => match request {
Request::Gemini(..) => { Request::Gemini { .. } => {
Response::from_connection_async(self, priority, cancellable, |result| { Response::from_connection_async(self, priority, cancellable, |result| {
callback(match result { callback(match result {
Ok(response) => Ok(response), Ok(response) => Ok(response),
@ -75,8 +75,8 @@ impl Connection {
}) })
}) })
} }
Request::Titan(this) => output_stream.write_bytes_async( Request::Titan { data, .. } => output_stream.write_bytes_async(
&this.data, &data,
priority, priority,
Some(&cancellable.clone()), Some(&cancellable.clone()),
move |result| match result { move |result| match result {

View file

@ -1,20 +1,24 @@
pub mod error; pub mod error;
pub mod gemini;
pub mod titan;
pub use error::Error; pub use error::Error;
pub use gemini::Gemini;
pub use titan::Titan;
// Local dependencies // Local dependencies
use gio::NetworkAddress; use gio::NetworkAddress;
use glib::Uri; use glib::{Bytes, Uri, UriHideFlags};
/// Single `Request` implementation for different protocols /// Single `Request` implementation for different protocols
pub enum Request { pub enum Request {
Gemini(Gemini), Gemini {
Titan(Titan), uri: Uri,
},
Titan {
uri: Uri,
data: Bytes,
/// MIME type is optional attribute by Titan protocol specification,
/// but server MAY reject the request without `mime` value provided.
mime: Option<String>,
token: Option<String>,
},
} }
impl Request { impl Request {
@ -23,16 +27,38 @@ impl Request {
/// Generate header string for `Self` /// Generate header string for `Self`
pub fn header(&self) -> String { pub fn header(&self) -> String {
match self { match self {
Self::Gemini(ref this) => this.header(), Self::Gemini { uri } => format!("{uri}\r\n"),
Self::Titan(ref this) => this.header(), Self::Titan {
uri,
data,
mime,
token,
} => {
let mut header = format!(
"{};size={}",
uri.to_string_partial(UriHideFlags::QUERY),
data.len()
);
if let Some(ref mime) = mime {
header.push_str(&format!(";mime={mime}"));
}
if let Some(ref token) = token {
header.push_str(&format!(";token={token}"));
}
if let Some(query) = uri.query() {
header.push_str(&format!("?{query}"));
}
header.push_str("\r\n");
header
}
} }
} }
/// Get reference to `Self` [Uri](https://docs.gtk.org/glib/struct.Uri.html) /// Get reference to `Self` [Uri](https://docs.gtk.org/glib/struct.Uri.html)
pub fn uri(&self) -> &Uri { pub fn uri(&self) -> &Uri {
match self { match self {
Self::Gemini(ref this) => &this.uri, Self::Gemini { uri } => uri,
Self::Titan(ref this) => &this.uri, Self::Titan { uri, .. } => uri,
} }
} }
@ -44,3 +70,45 @@ impl Request {
} }
} }
} }
#[test]
fn test_gemini_header() {
use glib::UriFlags;
const REQUEST: &str = "gemini://geminiprotocol.net/";
assert_eq!(
Request::Gemini {
uri: Uri::parse(REQUEST, UriFlags::NONE).unwrap()
}
.header(),
format!("{REQUEST}\r\n")
);
}
#[test]
fn test_titan_header() {
use glib::UriFlags;
const DATA: &[u8] = &[1, 2, 3];
const MIME: &str = "plain/text";
const TOKEN: &str = "token";
assert_eq!(
Request::Titan {
uri: Uri::parse(
"titan://geminiprotocol.net/raw/path?key=value",
UriFlags::NONE
)
.unwrap(),
data: Bytes::from(DATA),
mime: Some(MIME.to_string()),
token: Some(TOKEN.to_string())
}
.header(),
format!(
"titan://geminiprotocol.net/raw/path;size={};mime={MIME};token={TOKEN}?key=value\r\n",
DATA.len(),
)
);
}

View file

@ -1,30 +0,0 @@
use glib::Uri;
/// [Gemini](https://geminiprotocol.net/docs/protocol-specification.gmi) protocol enum object for `Request`
pub struct Gemini {
pub uri: Uri,
}
impl Gemini {
// Getters
/// Get header string for `Self`
pub fn header(&self) -> String {
format!("{}\r\n", self.uri)
}
}
#[test]
fn header() {
use super::{super::Request, Gemini};
use glib::UriFlags;
const REQUEST: &str = "gemini://geminiprotocol.net/";
assert_eq!(
Request::Gemini(Gemini {
uri: Uri::parse(REQUEST, UriFlags::NONE).unwrap()
})
.header(),
format!("{REQUEST}\r\n")
);
}

View file

@ -1,63 +0,0 @@
use glib::{Bytes, Uri, UriHideFlags};
/// Formatted [Titan](gemini://transjovian.org/titan/page/The%20Titan%20Specification) `Request`
pub struct Titan {
pub uri: Uri,
pub data: Bytes,
/// MIME type is optional attribute by Titan protocol specification,
/// but server MAY reject the request without `mime` value provided.
pub mime: Option<String>,
pub token: Option<String>,
}
impl Titan {
// Getters
/// Get header string for `Self`
pub fn header(&self) -> String {
let mut header = format!(
"{};size={}",
self.uri.to_string_partial(UriHideFlags::QUERY),
self.data.len()
);
if let Some(ref mime) = self.mime {
header.push_str(&format!(";mime={mime}"));
}
if let Some(ref token) = self.token {
header.push_str(&format!(";token={token}"));
}
if let Some(query) = self.uri.query() {
header.push_str(&format!("?{query}"));
}
header.push_str("\r\n");
header
}
}
#[test]
fn header() {
use super::{super::Request, Titan};
use glib::UriFlags;
const DATA: &[u8] = &[1, 2, 3];
const MIME: &str = "plain/text";
const TOKEN: &str = "token";
assert_eq!(
Request::Titan(Titan {
uri: Uri::parse(
"titan://geminiprotocol.net/raw/path?key=value",
UriFlags::NONE
)
.unwrap(),
data: Bytes::from(DATA),
mime: Some(MIME.to_string()),
token: Some(TOKEN.to_string())
})
.header(),
format!(
"titan://geminiprotocol.net/raw/path;size={};mime={MIME};token={TOKEN}?key=value\r\n",
DATA.len(),
)
);
}