diff --git a/pubky-homeserver/src/database/tables/entries.rs b/pubky-homeserver/src/database/tables/entries.rs index e708046..0d8fa35 100644 --- a/pubky-homeserver/src/database/tables/entries.rs +++ b/pubky-homeserver/src/database/tables/entries.rs @@ -20,7 +20,7 @@ pub type EntriesTable = Database; pub const ENTRIES_TABLE: &str = "entries"; -const MAX_LIST_LIMIT: i32 = 100; +const MAX_LIST_LIMIT: u16 = 100; impl DB { pub fn put_entry( @@ -90,7 +90,7 @@ impl DB { public_key: &PublicKey, prefix: &str, reverse: bool, - limit: Option, + limit: Option, cursor: Option, ) -> anyhow::Result> { let db = self.tables.entries; diff --git a/pubky-homeserver/src/routes/public.rs b/pubky-homeserver/src/routes/public.rs index 9e4a6b4..26d270e 100644 --- a/pubky-homeserver/src/routes/public.rs +++ b/pubky-homeserver/src/routes/public.rs @@ -88,7 +88,7 @@ pub async fn get( public_key, path.as_str(), params.contains_key("reverse"), - params.get("limit").and_then(|l| l.parse::().ok()), + params.get("limit").and_then(|l| l.parse::().ok()), params.get("cursor").map(|cursor| cursor.into()), )?; diff --git a/pubky/src/error.rs b/pubky/src/error.rs index e27814b..40eca3b 100644 --- a/pubky/src/error.rs +++ b/pubky/src/error.rs @@ -28,6 +28,9 @@ pub enum Error { #[error("Recovery file encrypted secret key should be 32 bytes, got {0}")] RecoverFileInvalidSecretKeyLength(usize), + #[error("Could not convert the passed type into a Url")] + InvalidUrl, + // === Transparent === #[error(transparent)] Dns(#[from] SimpleDnsError), diff --git a/pubky/src/native.rs b/pubky/src/native.rs index 6fc3e72..8fb6ee0 100644 --- a/pubky/src/native.rs +++ b/pubky/src/native.rs @@ -12,7 +12,10 @@ use url::Url; use crate::{ error::Result, - shared::recovery_file::{create_recovery_file, decrypt_recovery_file}, + shared::{ + list_builder::ListBuilder, + recovery_file::{create_recovery_file, decrypt_recovery_file}, + }, PubkyClient, }; @@ -104,6 +107,10 @@ impl PubkyClient { self.inner_delete(url).await } + pub fn list>(&self, url: T) -> Result { + self.inner_list(url) + } + // === Helpers === /// Create a recovery file of the `keypair`, containing the secret key encrypted diff --git a/pubky/src/shared/list_builder.rs b/pubky/src/shared/list_builder.rs new file mode 100644 index 0000000..a1a1330 --- /dev/null +++ b/pubky/src/shared/list_builder.rs @@ -0,0 +1,73 @@ +use reqwest::{Method, Response, StatusCode}; +use url::Url; + +use crate::{error::Result, PubkyClient}; + +#[derive(Debug)] +pub struct ListBuilder<'a> { + url: Url, + reverse: bool, + limit: Option, + cursor: Option<&'a str>, + client: &'a PubkyClient, +} + +impl<'a> ListBuilder<'a> { + pub fn new(client: &'a PubkyClient, url: Url) -> Self { + Self { + client, + url, + limit: None, + cursor: None, + reverse: false, + } + } + + pub fn reverse(mut self, reverse: bool) -> Self { + self.reverse = reverse; + self + } + + pub fn limit(mut self, limit: u16) -> Self { + self.limit = limit.into(); + self + } + + pub fn cursor(mut self, cursor: &'a str) -> Self { + self.cursor = cursor.into(); + self + } + + pub async fn send(self) -> Result> { + let mut url = self.client.pubky_to_http(self.url).await?; + + let mut query = url.query_pairs_mut(); + query.append_key_only("list"); + + if self.reverse { + query.append_key_only("reverse"); + } + + if let Some(limit) = self.limit { + query.append_pair("limit", &limit.to_string()); + } + + if let Some(cursor) = self.cursor { + query.append_pair("cursor", cursor); + } + + drop(query); + + let response = self.client.request(Method::GET, url).send().await?; + + response.error_for_status_ref()?; + + // TODO: bail on too large files. + let bytes = response.bytes().await?; + + Ok(String::from_utf8_lossy(&bytes) + .lines() + .map(String::from) + .collect()) + } +} diff --git a/pubky/src/shared/mod.rs b/pubky/src/shared/mod.rs index 49f11bd..550cc6e 100644 --- a/pubky/src/shared/mod.rs +++ b/pubky/src/shared/mod.rs @@ -1,4 +1,5 @@ pub mod auth; +pub mod list_builder; pub mod pkarr; pub mod public; pub mod recovery_file; diff --git a/pubky/src/shared/public.rs b/pubky/src/shared/public.rs index 4782a6c..e1eaed8 100644 --- a/pubky/src/shared/public.rs +++ b/pubky/src/shared/public.rs @@ -9,7 +9,7 @@ use crate::{ PubkyClient, }; -use super::pkarr::Endpoint; +use super::{list_builder::ListBuilder, pkarr::Endpoint}; impl PubkyClient { pub async fn inner_put>(&self, url: T, content: &[u8]) -> Result<()> { @@ -53,49 +53,15 @@ impl PubkyClient { Ok(()) } - pub async fn list>( - &self, - url: T, - reverse: bool, - limit: Option, - cursor: Option<&str>, - ) -> Result> { - let mut url = self.pubky_to_http(url).await?; - - let mut query = url.query_pairs_mut(); - query.append_key_only("list"); - - if reverse { - query.append_key_only("reverse"); - } - - if let Some(limit) = limit { - query.append_pair("limit", &limit.to_string()); - } - - if let Some(cursor) = cursor { - query.append_pair("cursor", cursor); - } - - drop(query); - - let response = self.request(Method::GET, url).send().await?; - - response.error_for_status_ref()?; - - // TODO: bail on too large files. - let bytes = response.bytes().await?; - - Ok(String::from_utf8_lossy(&bytes) - .lines() - .map(String::from) - .collect()) + pub fn inner_list>(&self, url: T) -> Result { + Ok(ListBuilder::new( + self, + url.try_into().map_err(|_| Error::InvalidUrl)?, + )) } - async fn pubky_to_http>(&self, url: T) -> Result { - let mut original_url: Url = url - .try_into() - .map_err(|e| Error::Generic("Invalid Url".to_string()))?; + pub(crate) async fn pubky_to_http>(&self, url: T) -> Result { + let mut original_url: Url = url.try_into().map_err(|_| Error::InvalidUrl)?; if original_url.scheme() != "pubky" { return Ok(original_url); @@ -272,7 +238,7 @@ mod tests { { let url = format!("pubky://{}/pub/example.com/", keypair.public_key()); - let list = client.list(url.as_str(), false, None, None).await.unwrap(); + let list = client.list(url.as_str()).unwrap().send().await.unwrap(); assert_eq!( list, @@ -289,7 +255,10 @@ mod tests { { let url = format!("pubky://{}/pub/example.com/", keypair.public_key()); let list = client - .list(url.as_str(), false, Some(2), None) + .list(url.as_str()) + .unwrap() + .limit(2) + .send() .await .unwrap(); @@ -306,7 +275,11 @@ mod tests { { let url = format!("pubky://{}/pub/example.com/", keypair.public_key()); let list = client - .list(url.as_str(), false, Some(2), Some("a.txt")) + .list(url.as_str()) + .unwrap() + .limit(2) + .cursor("a.txt") + .send() .await .unwrap(); @@ -323,15 +296,14 @@ mod tests { { let url = format!("pubky://{}/pub/example.com/", keypair.public_key()); let list = client - .list( - url.as_str(), - false, - Some(2), - Some(&format!( - "pubky://{}/pub/example.com/a.txt", - keypair.public_key() - )), - ) + .list(url.as_str()) + .unwrap() + .limit(2) + .cursor(&format!( + "pubky://{}/pub/example.com/a.txt", + keypair.public_key() + )) + .send() .await .unwrap(); @@ -347,7 +319,13 @@ mod tests { { let url = format!("pubky://{}/pub/example.com/", keypair.public_key()); - let list = client.list(url.as_str(), true, None, None).await.unwrap(); + let list = client + .list(url.as_str()) + .unwrap() + .reverse(true) + .send() + .await + .unwrap(); assert_eq!( list, @@ -364,7 +342,11 @@ mod tests { { let url = format!("pubky://{}/pub/example.com/", keypair.public_key()); let list = client - .list(url.as_str(), true, Some(2), None) + .list(url.as_str()) + .unwrap() + .reverse(true) + .limit(2) + .send() .await .unwrap(); @@ -381,7 +363,12 @@ mod tests { { let url = format!("pubky://{}/pub/example.com/", keypair.public_key()); let list = client - .list(url.as_str(), true, Some(2), Some("d.txt")) + .list(url.as_str()) + .unwrap() + .reverse(true) + .limit(2) + .cursor("d.txt") + .send() .await .unwrap();