diff --git a/Cargo.toml b/Cargo.toml index 44b6524..7e101b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ log = "0.4.14" [dev-dependencies] env_logger = "0.8.4" +maplit = "1.0.2" lazy_static = "1.4.0" [dev-dependencies.tokio] diff --git a/src/client.rs b/src/client.rs index c42a44d..47d36c2 100644 --- a/src/client.rs +++ b/src/client.rs @@ -7,7 +7,9 @@ use crate::paths::adding_files::{ DeleteFilesResponse, UnarchiveFilesRequest, UnarchiveFilesResponse, UndeleteFilesRequest, UndeleteFilesResponse, }; +use crate::paths::adding_tags::{AddTagsRequest, AddTagsResponse, CleanTagsResponse}; use crate::paths::Path; +use crate::utils::string_list_to_json_array; use reqwest::Response; use serde::de::DeserializeOwned; use serde::Serialize; @@ -165,4 +167,18 @@ impl Client { Ok(()) } + + /// Returns the list of tags as the client would see them in a human friendly order + pub async fn clean_tags(&mut self, tags: Vec) -> Result { + self.get_and_parse(&[("tags", string_list_to_json_array(tags))]) + .await + } + + /// Adds tags to files with the given hashes + pub async fn add_tags(&mut self, request: AddTagsRequest) -> Result<()> { + self.post::(request) + .await?; + + Ok(()) + } } diff --git a/src/lib.rs b/src/lib.rs index 23e149b..fe75431 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #[macro_use] extern crate serde_derive; -pub mod paths; pub mod client; mod error; +pub mod paths; +pub(crate) mod utils; diff --git a/src/paths/adding_tags.rs b/src/paths/adding_tags.rs new file mode 100644 index 0000000..5fe69ac --- /dev/null +++ b/src/paths/adding_tags.rs @@ -0,0 +1,153 @@ +use crate::paths::Path; +use std::collections::HashMap; + +#[derive(Debug, Clone, Deserialize)] +pub struct CleanTagsResponse { + pub tags: Vec, +} + +impl Path for CleanTagsResponse { + fn get_path() -> String { + String::from("add_tags/clean_tags") + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct AddTagsRequest { + pub hashes: Vec, + pub service_names_to_tags: HashMap>, + pub service_names_to_actions_to_tags: HashMap>>, +} + +pub struct AddTagsResponse; + +impl Path for AddTagsResponse { + fn get_path() -> String { + String::from("add_tags/add_tags") + } +} + +pub struct AddTagsRequestBuilder { + hashes: Vec, + service_names_to_tags: HashMap>, + service_names_to_actions_to_tags: HashMap>>, +} + +/// List of actions for a given tag +pub enum TagAction { + /// Add to a local tag service. + AddToLocalService, + + /// Delete from a local tag service. + DeleteFromLocalService, + + /// Pend to a tag repository. + PendAddToRepository, + + /// Rescind a pend from a tag repository. + RescindPendFromRepository, + + /// Petition from a tag repository. (This is special) + PetitionFromRepository, + + /// Rescind a petition from a tag repository. + RescindPetitionFromRepository, +} + +impl TagAction { + fn into_id(self) -> u8 { + match self { + TagAction::AddToLocalService => 0, + TagAction::DeleteFromLocalService => 1, + TagAction::PendAddToRepository => 2, + TagAction::RescindPendFromRepository => 3, + TagAction::PetitionFromRepository => 4, + TagAction::RescindPetitionFromRepository => 5, + } + } +} + +impl Default for AddTagsRequestBuilder { + fn default() -> Self { + Self { + hashes: vec![], + service_names_to_tags: Default::default(), + service_names_to_actions_to_tags: Default::default(), + } + } +} + +impl AddTagsRequestBuilder { + /// Adds a file hash to the request + pub fn add_hash>(mut self, hash: S) -> Self { + self.hashes.push(hash.as_ref().into()); + + self + } + + /// Adds multiple file hashes to the request + pub fn add_hashes(mut self, mut hashes: Vec) -> Self { + self.hashes.append(&mut hashes); + + self + } + + /// Adds a single tag for a given service + pub fn add_tag, S2: AsRef>(mut self, service_name: S1, tag: S2) -> Self { + if let Some(mappings) = self.service_names_to_tags.get_mut(service_name.as_ref()) { + mappings.push(tag.as_ref().into()) + } else { + self.service_names_to_tags + .insert(service_name.as_ref().into(), vec![tag.as_ref().into()]); + } + + self + } + + /// Adds multiple tags for a given service + pub fn add_tags>(mut self, service_name: S1, mut tags: Vec) -> Self { + if let Some(mappings) = self.service_names_to_tags.get_mut(service_name.as_ref()) { + mappings.append(&mut tags); + } else { + self.service_names_to_tags + .insert(service_name.as_ref().into(), tags); + } + + self + } + + /// Adds one tag for a given service with a defined action + pub fn add_tag_with_action, S2: AsRef>( + mut self, + service_name: S1, + tag: S2, + action: TagAction, + ) -> Self { + let action_id = action.into_id(); + if let Some(actions) = self + .service_names_to_actions_to_tags + .get_mut(service_name.as_ref()) + { + if let Some(tags) = actions.get_mut(&action_id.to_string()) { + tags.push(tag.as_ref().into()); + } else { + actions.insert(action_id.to_string(), vec![tag.as_ref().into()]); + } + } else { + let mut actions = HashMap::new(); + actions.insert(action_id.to_string(), vec![tag.as_ref().into()]); + self.service_names_to_actions_to_tags + .insert(service_name.as_ref().into(), actions); + } + self + } + + /// builds the request + pub fn build(self) -> AddTagsRequest { + AddTagsRequest { + hashes: self.hashes, + service_names_to_tags: self.service_names_to_tags, + service_names_to_actions_to_tags: self.service_names_to_actions_to_tags, + } + } +} diff --git a/src/paths/mod.rs b/src/paths/mod.rs index 978b799..b88fbec 100644 --- a/src/paths/mod.rs +++ b/src/paths/mod.rs @@ -1,5 +1,6 @@ pub mod access_management; pub mod adding_files; +pub mod adding_tags; pub mod common; pub trait Path { diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..729d068 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,3 @@ +pub fn string_list_to_json_array(l: Vec) -> String { + format!("[\"{}\"]", l.join("\",\"")) +} diff --git a/tests/test_adding_tags.rs b/tests/test_adding_tags.rs new file mode 100644 index 0000000..8828f49 --- /dev/null +++ b/tests/test_adding_tags.rs @@ -0,0 +1,29 @@ +use hydrus_api::paths::adding_tags::{AddTagsRequestBuilder, TagAction}; + +mod common; + +#[tokio::test] +async fn it_cleans_tags() { + let mut client = common::get_client(); + let response = client + .clean_tags(vec![ + "summer".into(), + "rain".into(), + "beach".into(), + "safe".into(), + ]) + .await + .unwrap(); + assert!(response.tags.len() > 0) +} + +#[tokio::test] +async fn it_adds_tags() { + let mut client = common::get_client(); + let request = AddTagsRequestBuilder::default() + .add_hash("0000000000000000000000000000000000000000000000000000000000000000") // valid hash, I hope no files are affected + .add_tags("my tags", vec!["beach".into(), "summer".into()]) + .add_tag_with_action("my tags", "rain", TagAction::DeleteFromLocalService) + .build(); + client.add_tags(request).await.unwrap(); +}