diff --git a/src/api_core/adding_urls.rs b/src/api_core/adding_urls.rs index a92aabc..33aae0b 100644 --- a/src/api_core/adding_urls.rs +++ b/src/api_core/adding_urls.rs @@ -1,3 +1,4 @@ +use crate::api_core::common::ServiceIdentifier; use crate::api_core::Endpoint; use serde::Serialize; use std::collections::HashMap; @@ -52,7 +53,7 @@ impl Endpoint for GetUrlInfo { } } -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Default, Debug, Serialize)] pub struct AddUrlRequest { pub url: String, @@ -64,6 +65,7 @@ pub struct AddUrlRequest { pub show_destination_page: bool, pub service_names_to_additional_tags: HashMap>, + pub service_keys_to_additional_tags: HashMap>, pub filterable_tags: Vec, } @@ -73,33 +75,20 @@ pub struct AddUrlRequest { /// Example: /// ``` /// use hydrus_api::api_core::adding_urls::AddUrlRequestBuilder; +/// use hydrus_api::api_core::common::ServiceIdentifier; /// /// let request = AddUrlRequestBuilder::default() /// .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") -/// .add_tags("my tags", vec!["ark mage".to_string(), "grinning".to_string()]) +/// .add_tags(ServiceIdentifier::name("my tags"), vec!["ark mage".to_string(), "grinning".to_string()]) /// .show_destination_page(true) /// .destination_page_name("Rusty Url Import") /// .build(); /// ``` +#[derive(Default)] pub struct AddUrlRequestBuilder { inner: AddUrlRequest, } -impl Default for AddUrlRequestBuilder { - fn default() -> Self { - Self { - inner: AddUrlRequest { - url: String::new(), - destination_page_key: None, - destination_page_name: None, - show_destination_page: false, - service_names_to_additional_tags: Default::default(), - filterable_tags: vec![], - }, - } - } -} - impl AddUrlRequestBuilder { pub fn url(mut self, url: S) -> Self { self.inner.url = url.to_string(); @@ -125,17 +114,17 @@ impl AddUrlRequestBuilder { self } - pub fn add_tags>(mut self, service: S, mut tags: Vec) -> Self { - if let Some(entry) = self - .inner - .service_names_to_additional_tags - .get_mut(service.as_ref()) - { + pub fn add_tags(mut self, service_id: ServiceIdentifier, mut tags: Vec) -> Self { + let (service, mappings) = match service_id { + ServiceIdentifier::Name(name) => { + (name, &mut self.inner.service_names_to_additional_tags) + } + ServiceIdentifier::Key(key) => (key, &mut self.inner.service_keys_to_additional_tags), + }; + if let Some(entry) = mappings.get_mut(&service) { entry.append(&mut tags); } else { - self.inner - .service_names_to_additional_tags - .insert(service.as_ref().to_string(), tags); + mappings.insert(service, tags); } self diff --git a/src/api_core/client.rs b/src/api_core/client.rs index 44a3662..cab5721 100644 --- a/src/api_core/client.rs +++ b/src/api_core/client.rs @@ -11,6 +11,7 @@ use crate::api_core::adding_urls::{ AddUrl, AddUrlRequest, AddUrlResponse, AssociateUrl, AssociateUrlRequest, GetUrlFiles, GetUrlFilesResponse, GetUrlInfo, GetUrlInfoResponse, }; +use crate::api_core::client_builder::ClientBuilder; use crate::api_core::common::{FileIdentifier, FileMetadataInfo, FileRecord, OptionalStringNumber}; use crate::api_core::managing_cookies_and_http_headers::{ GetCookies, GetCookiesResponse, SetCookies, SetCookiesRequest, SetUserAgent, @@ -41,12 +42,17 @@ static ACCESS_KEY_HEADER: &str = "Hydrus-Client-API-Access-Key"; /// over the REST api. #[derive(Debug)] pub struct Client { - inner: reqwest::Client, - base_url: String, - access_key: String, + pub(crate) inner: reqwest::Client, + pub(crate) base_url: String, + pub(crate) access_key: String, } impl Client { + /// Returns a builder for the client + pub fn builder() -> ClientBuilder { + ClientBuilder::default() + } + /// Creates a new client to start requests against the hydrus api. pub fn new>(url: S, access_key: S) -> Self { Self { diff --git a/src/api_core/client_builder.rs b/src/api_core/client_builder.rs new file mode 100644 index 0000000..2ae0112 --- /dev/null +++ b/src/api_core/client_builder.rs @@ -0,0 +1,56 @@ +use crate::error::{Error, Result}; +use crate::Client; +use std::time::Duration; + +pub struct ClientBuilder { + reqwest_builder: reqwest::ClientBuilder, + base_url: String, + access_key: Option, +} + +impl Default for ClientBuilder { + fn default() -> Self { + Self { + reqwest_builder: Default::default(), + base_url: "127.0.0.1:45869".to_string(), + access_key: None, + } + } +} + +impl ClientBuilder { + /// Set the base url with port for the client api + /// The default value is `127.0.0.1:45869` + pub fn url(mut self, url: S) -> Self { + self.base_url = url.to_string(); + + self + } + + /// Sets the access key for the client. + /// The key is required + pub fn access_key(mut self, key: S) -> Self { + self.access_key = Some(key.to_string()); + + self + } + + /// Sets the default timeout for requests to the API + pub fn timeout(mut self, timeout: Duration) -> Self { + self.reqwest_builder = self.reqwest_builder.timeout(timeout); + + self + } + + /// Builds the client + pub fn build(self) -> Result { + let access_key = self + .access_key + .ok_or_else(|| Error::BuildError(String::from("missing access key")))?; + Ok(Client { + inner: self.reqwest_builder.build()?, + base_url: self.base_url, + access_key, + }) + } +} diff --git a/src/api_core/common.rs b/src/api_core/common.rs index 26809e1..9fc631a 100644 --- a/src/api_core/common.rs +++ b/src/api_core/common.rs @@ -6,6 +6,14 @@ pub struct BasicServiceInfo { pub service_key: String, } +impl BasicServiceInfo { + /// Converts the Service into into an identifier + /// that can be used for requests consuming service references + pub fn into_id(self) -> ServiceIdentifier { + ServiceIdentifier::Key(self.service_key) + } +} + #[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialOrd, PartialEq, Ord, Eq)] pub enum ServiceIdentifier { Name(String), diff --git a/src/api_core/mod.rs b/src/api_core/mod.rs index 6058fcd..bf5b68c 100644 --- a/src/api_core/mod.rs +++ b/src/api_core/mod.rs @@ -7,10 +7,12 @@ pub mod adding_files; pub mod adding_tags; pub mod adding_urls; pub mod client; +pub mod client_builder; pub mod common; pub mod managing_cookies_and_http_headers; pub mod managing_pages; pub mod searching_and_fetching_files; + pub use searching_and_fetching_files::file_sort_type; pub(crate) trait Endpoint { diff --git a/src/error.rs b/src/error.rs index 60f3ad6..7ac5198 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,6 +13,7 @@ pub enum Error { ImportFailed(String), FileNotFound(FileIdentifier), InvalidMime(String), + BuildError(String), } impl fmt::Display for Error { @@ -27,6 +28,7 @@ impl fmt::Display for Error { Self::ImportVetoed(msg) => write!(f, "File import vetoed: {}", msg), Self::FileNotFound(id) => write!(f, "File {:?} not found", id), Self::InvalidMime(mime) => write!(f, "Failed to parse invalid mime {}", mime), + Self::BuildError(error) => write!(f, "Build error {}", error), } } } diff --git a/src/wrapper/builders/import_builder.rs b/src/wrapper/builders/import_builder.rs index f9b9e6d..a99b2aa 100644 --- a/src/wrapper/builders/import_builder.rs +++ b/src/wrapper/builders/import_builder.rs @@ -1,5 +1,6 @@ use crate::api_core::adding_files::{STATUS_IMPORT_FAILED, STATUS_IMPORT_VETOED}; use crate::api_core::adding_urls::AddUrlRequestBuilder; +use crate::api_core::common::ServiceIdentifier; use crate::error::{Error, Result}; use crate::utils::tag_list_to_string_list; use crate::wrapper::hydrus_file::HydrusFile; @@ -141,7 +142,10 @@ impl UrlImportBuilder { let mut request = AddUrlRequestBuilder::default().url(&self.url); for (service, tags) in self.service_tag_mappings { - request = request.add_tags(service, tag_list_to_string_list(tags)); + request = request.add_tags( + ServiceIdentifier::name(service), + tag_list_to_string_list(tags), + ); } request = request.add_filter_tags(tag_list_to_string_list(self.filter_tags)); if let Some(page) = self.page { diff --git a/src/wrapper/hydrus_file.rs b/src/wrapper/hydrus_file.rs index d079078..fd9c8ca 100644 --- a/src/wrapper/hydrus_file.rs +++ b/src/wrapper/hydrus_file.rs @@ -1,5 +1,5 @@ use crate::api_core::adding_tags::{AddTagsRequestBuilder, TagAction}; -use crate::api_core::common::{FileIdentifier, FileMetadataInfo, FileRecord}; +use crate::api_core::common::{FileIdentifier, FileMetadataInfo, FileRecord, ServiceIdentifier}; use crate::error::{Error, Result}; use crate::utils::tag_list_to_string_list; use crate::wrapper::service::ServiceName; @@ -213,11 +213,11 @@ impl HydrusFile { } /// Adds tags for a specific service to the file - pub async fn add_tags(&mut self, service: ServiceName, tags: Vec) -> Result<()> { + pub async fn add_tags(&mut self, service: ServiceIdentifier, tags: Vec) -> Result<()> { let hash = self.hash().await?; let request = AddTagsRequestBuilder::default() .add_hash(hash) - .add_tags(service.into(), tag_list_to_string_list(tags)) + .add_tags(service, tag_list_to_string_list(tags)) .build(); self.client.add_tags(request).await @@ -226,7 +226,7 @@ impl HydrusFile { /// Allows modification of tags by using the defined tag actions pub async fn modify_tags( &mut self, - service: ServiceName, + service: ServiceIdentifier, action: TagAction, tags: Vec, ) -> Result<()> { @@ -234,11 +234,7 @@ impl HydrusFile { let mut reqwest = AddTagsRequestBuilder::default().add_hash(hash); for tag in tags { - reqwest = reqwest.add_tag_with_action( - service.clone().into(), - tag.to_string(), - action.clone(), - ); + reqwest = reqwest.add_tag_with_action(service.clone(), tag.to_string(), action.clone()); } self.client.add_tags(reqwest.build()).await diff --git a/tests/client/test_adding_urls.rs b/tests/client/test_adding_urls.rs index 8b1f9aa..b262f9d 100644 --- a/tests/client/test_adding_urls.rs +++ b/tests/client/test_adding_urls.rs @@ -1,5 +1,6 @@ use super::super::common; use hydrus_api::api_core::adding_urls::{AddUrlRequestBuilder, URL_TYPE_POST}; +use hydrus_api::api_core::common::ServiceIdentifier; #[tokio::test] async fn it_returns_files_for_an_url() { @@ -29,7 +30,7 @@ async fn it_adds_urls() { let request = AddUrlRequestBuilder::default() .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") .add_tags( - "my tags", + ServiceIdentifier::name("my tags"), vec!["ark mage".to_string(), "grinning".to_string()], ) .show_destination_page(true) diff --git a/tests/common.rs b/tests/common.rs index 78cdac3..996e6b5 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -2,6 +2,7 @@ use hydrus_api::api_core::client::Client; use hydrus_api::Hydrus; use std::env; use std::sync::{Arc, Mutex, MutexGuard}; +use std::time::Duration; pub fn setup() { lazy_static::lazy_static! { static ref SETUP_DONE: Arc> = Arc::new(Mutex::new(false)); } @@ -16,11 +17,12 @@ pub fn setup() { pub fn get_client() -> Client { setup(); - - Client::new( - env::var("HYDRUS_URL").unwrap(), - env::var("HYDRUS_ACCESS_KEY").unwrap(), - ) + Client::builder() + .url(env::var("HYDRUS_URL").unwrap()) + .access_key(env::var("HYDRUS_ACCESS_KEY").unwrap()) + .timeout(Duration::from_secs(5)) + .build() + .unwrap() } pub fn get_hydrus() -> Hydrus { diff --git a/tests/wrapper/test_files.rs b/tests/wrapper/test_files.rs index 1e40692..b4a7947 100644 --- a/tests/wrapper/test_files.rs +++ b/tests/wrapper/test_files.rs @@ -54,7 +54,7 @@ async fn it_has_tags() { async fn it_adds_tags() { let mut file = get_file().await; file.add_tags( - ServiceName::my_tags(), + ServiceName::my_tags().into(), vec!["character:megumin".into(), "ark mage".into()], ) .await @@ -65,7 +65,7 @@ async fn it_adds_tags() { async fn it_modifies_tags() { let mut file = get_file().await; file.modify_tags( - ServiceName::my_tags(), + ServiceName::my_tags().into(), TagAction::RescindPendFromRepository, vec!["ark mage".into()], ) diff --git a/tests/wrapper/test_tags.rs b/tests/wrapper/test_tags.rs index 5d0580c..ba3349f 100644 --- a/tests/wrapper/test_tags.rs +++ b/tests/wrapper/test_tags.rs @@ -7,8 +7,12 @@ use hydrus_api::wrapper::builders::tag_builder::{ }; use hydrus_api::wrapper::service::ServiceName; use hydrus_api::wrapper::tag::Tag; +use std::sync::Arc; +use tokio::sync::Mutex; async fn retrieve_single_tag(tag: Tag) -> Result<()> { + lazy_static::lazy_static! { static ref SEM: Arc> = Arc::new(Mutex::new(())); } + SEM.lock().await; let hydrus = common::get_hydrus(); hydrus.search().add_tag(tag).run().await?;