From 9fe17d4c909ef04973db5069f94c0e6726bbacea Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 4 Jul 2021 13:04:05 +0200 Subject: [PATCH] Add access management functions Signed-off-by: trivernis --- .gitignore | 2 + .idea/.gitignore | 8 ++++ .idea/discord.xml | 6 +++ .idea/hydrus-api.iml | 12 ++++++ .idea/modules.xml | 8 ++++ .idea/vcs.xml | 6 +++ Cargo.toml | 16 +++++++ src/client.rs | 76 +++++++++++++++++++++++++++++++++ src/error.rs | 31 ++++++++++++++ src/lib.rs | 6 +++ src/paths/access_management.rs | 47 ++++++++++++++++++++ src/paths/common.rs | 5 +++ src/paths/mod.rs | 6 +++ tests/common.rs | 6 +++ tests/test_access_management.rs | 31 ++++++++++++++ 15 files changed, 266 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/discord.xml create mode 100644 .idea/hydrus-api.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.toml create mode 100644 src/client.rs create mode 100644 src/error.rs create mode 100644 src/lib.rs create mode 100644 src/paths/access_management.rs create mode 100644 src/paths/common.rs create mode 100644 src/paths/mod.rs create mode 100644 tests/common.rs create mode 100644 tests/test_access_management.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..cd711a0 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/hydrus-api.iml b/.idea/hydrus-api.iml new file mode 100644 index 0000000..457e3e6 --- /dev/null +++ b/.idea/hydrus-api.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c146e4b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9110227 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "hydrus-api" +version = "0.1.0" +authors = ["trivernis "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = "^1.0" +serde_derive = "^1.0" +reqwest = {version = "0.11.4", features = ["json"]} + +[dev-dependencies.tokio] +version = "1.8.0" +features = ["macros", "rt-multi-thread"] \ No newline at end of file diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..84b36bc --- /dev/null +++ b/src/client.rs @@ -0,0 +1,76 @@ +use crate::error::Result; +use crate::paths::access_management::{ + ApiVersionResponse, GetServicesResponse, SessionKeyResponse, VerifyAccessKeyResponse, +}; +use crate::paths::Path; +use serde::de::DeserializeOwned; +use serde::Serialize; + +static ACCESS_KEY_HEADER: &str = "Hydrus-Client-API-Access-Key"; + +pub struct Client { + inner: reqwest::Client, + base_url: String, + access_key: String, +} + +impl Client { + pub fn new>(url: S, access_key: S) -> Result { + Ok(Self { + inner: reqwest::Client::new(), + access_key: access_key.as_ref().to_string(), + base_url: url.as_ref().to_string(), + }) + } + + /// Starts a get request to the path associated with the return type + async fn get( + &mut self, + query: &Q, + ) -> Result { + let response: T = self + .inner + .get(format!("{}/{}", self.base_url, T::get_path())) + .header(ACCESS_KEY_HEADER, &self.access_key) + .query(query) + .send() + .await? + .json() + .await?; + Ok(response) + } + + /// Stats a post request to the path associated with the return type + async fn post(&mut self, body: B) -> Result { + let response: T = self + .inner + .post(format!("{}/{}", self.base_url, T::get_path())) + .json(&body) + .header(ACCESS_KEY_HEADER, &self.access_key) + .send() + .await? + .json() + .await?; + Ok(response) + } + + /// Returns the current API version. It's being incremented every time the API changes. + pub async fn api_version(&mut self) -> Result { + self.get(&()).await + } + + /// Creates a new session key + pub async fn session_key(&mut self) -> Result { + self.get(&()).await + } + + /// Verifies if the access key is valid and returns some information about its permissions + pub async fn verify_access_key(&mut self) -> Result { + self.get(&()).await + } + + /// Returns the list of tag and file services of the client + pub async fn get_services(&mut self) -> Result { + self.get(&()).await + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..0afba0d --- /dev/null +++ b/src/error.rs @@ -0,0 +1,31 @@ +use std::error::Error as StdError; +use std::fmt; + +pub type Result = std::result::Result; + +#[derive(Debug)] +pub enum Error { + Reqwest(reqwest::Error), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Reqwest(e) => {e.fmt(f)} + } + } +} + +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + match self { + Self::Reqwest(e) => e.source(), + } + } +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Self::Reqwest(e) + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..23e149b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +#[macro_use] +extern crate serde_derive; + +pub mod paths; +pub mod client; +mod error; diff --git a/src/paths/access_management.rs b/src/paths/access_management.rs new file mode 100644 index 0000000..586c9f5 --- /dev/null +++ b/src/paths/access_management.rs @@ -0,0 +1,47 @@ +use crate::paths::common::BasicServiceInfo; +use crate::paths::Path; +use std::collections::HashMap; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ApiVersionResponse { + pub version: u32, + pub hydrus_version: u32, +} + +impl Path for ApiVersionResponse { + fn get_path() -> String { + String::from("api_version") + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SessionKeyResponse { + pub session_key: String, +} + +impl Path for SessionKeyResponse { + fn get_path() -> String { + String::from("session_key") + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VerifyAccessKeyResponse { + pub basic_permissions: Vec, + pub human_description: String, +} + +impl Path for VerifyAccessKeyResponse { + fn get_path() -> String { + String::from("verify_access_key") + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GetServicesResponse(pub HashMap>); + +impl Path for GetServicesResponse { + fn get_path() -> String { + String::from("get_services") + } +} diff --git a/src/paths/common.rs b/src/paths/common.rs new file mode 100644 index 0000000..29b37b1 --- /dev/null +++ b/src/paths/common.rs @@ -0,0 +1,5 @@ +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BasicServiceInfo { + pub name: String, + pub service_key: String, +} \ No newline at end of file diff --git a/src/paths/mod.rs b/src/paths/mod.rs new file mode 100644 index 0000000..236809a --- /dev/null +++ b/src/paths/mod.rs @@ -0,0 +1,6 @@ +pub mod access_management; +pub mod common; + +pub trait Path { + fn get_path() -> String; +} \ No newline at end of file diff --git a/tests/common.rs b/tests/common.rs new file mode 100644 index 0000000..2b376be --- /dev/null +++ b/tests/common.rs @@ -0,0 +1,6 @@ +use hydrus_api::client::Client; +use std::env; + +pub fn get_client() -> Client { + Client::new(env::var("HYDRUS_URL").unwrap(), env::var("HYDRUS_ACCESS_KEY").unwrap()).unwrap() +} \ No newline at end of file diff --git a/tests/test_access_management.rs b/tests/test_access_management.rs new file mode 100644 index 0000000..a06313f --- /dev/null +++ b/tests/test_access_management.rs @@ -0,0 +1,31 @@ +mod common; + +#[tokio::test] +async fn it_returns_the_api_version() { + let mut client = common::get_client(); + let api_version = client.api_version().await.unwrap(); + assert!(api_version.hydrus_version > 0); + assert!(api_version.version > 0); +} + +#[tokio::test] +async fn it_returns_the_session_key() { + let mut client = common::get_client(); + let session_key = client.session_key().await.unwrap(); + assert!(session_key.session_key.len() > 0); +} + +#[tokio::test] +async fn it_verifies_the_access_key() { + let mut client = common::get_client(); + let verification_response = client.verify_access_key().await.unwrap(); + assert!(verification_response.basic_permissions.len() > 0); // needs to be configured in the client but we want at least some permissions for the test + assert!(verification_response.human_description.len() > 0); +} + +#[tokio::test] +async fn it_returns_a_list_of_services() { + let mut client = common::get_client(); + let services_response = client.get_services().await.unwrap(); + assert!(services_response.0.keys().len() > 0); +}