From a6c8ad8795ef88a255b9aeba00c29341ed1b15aa Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 1 Mar 2022 21:00:23 +0100 Subject: [PATCH] Add first horrible implementation of hydrus dictionaries Signed-off-by: trivernis --- .gitignore | 3 + .idea/.gitignore | 8 ++ .idea/discord.xml | 7 ++ .idea/hydrus-ptr-client.iml | 12 +++ .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 ++ Cargo.toml | 29 +++++++ src/client.rs | 89 +++++++++++++++++++++ src/client_builder.rs | 67 ++++++++++++++++ src/constants.rs | 111 ++++++++++++++++++++++++++ src/endpoints/mod.rs | 32 ++++++++ src/endpoints/options.rs | 49 ++++++++++++ src/error.rs | 24 ++++++ src/hydrus_serializable/dictionary.rs | 37 +++++++++ src/hydrus_serializable/mod.rs | 71 ++++++++++++++++ src/hydrus_serializable/wrapper.rs | 10 +++ src/lib.rs | 10 +++ tests/common.rs | 25 ++++++ tests/endpoints.rs | 7 ++ 19 files changed, 605 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/discord.xml create mode 100644 .idea/hydrus-ptr-client.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/client_builder.rs create mode 100644 src/constants.rs create mode 100644 src/endpoints/mod.rs create mode 100644 src/endpoints/options.rs create mode 100644 src/error.rs create mode 100644 src/hydrus_serializable/dictionary.rs create mode 100644 src/hydrus_serializable/mod.rs create mode 100644 src/hydrus_serializable/wrapper.rs create mode 100644 src/lib.rs create mode 100644 tests/common.rs create mode 100644 tests/endpoints.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3549fae --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +.env \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..30bab2a --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/hydrus-ptr-client.iml b/.idea/hydrus-ptr-client.iml new file mode 100644 index 0000000..457e3e6 --- /dev/null +++ b/.idea/hydrus-ptr-client.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..aca3dd0 --- /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..7a299eb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "hydrus-ptr-client" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tracing = "0.1.31" +thiserror = "1.0.30" +serde_json = "1.0.79" +flate2 = "1.0.22" +reqwest = "0.11.9" + +[dependencies.serde] +version = "1.0.136" +features = ["derive"] + +[dev-dependencies] +tracing-subscriber = "0.3.9" +dotenv = "0.15.0" +lazy_static = "1.4.0" + +[dev-dependencies.tokio] +version = "1.17.0" +features = ["rt-multi-thread", "macros"] + +[features] +rustls = ["reqwest/rustls"] \ No newline at end of file diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..c232e40 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,89 @@ +pub use crate::endpoints::*; +use crate::{ClientBuilder, Error, Result}; +use flate2::write::ZlibDecoder; +use reqwest::Response; +use serde::Serialize; +use std::fmt::Debug; +use std::io::Write; + +pub struct Client { + pub(crate) client: reqwest::Client, + pub(crate) base_url: String, + pub(crate) access_key: String, +} + +impl Client { + /// Creates a new client builder + pub fn builder() -> ClientBuilder { + ClientBuilder::default() + } + + /// Creates a new PTR Client + pub fn new(endpoint: S1, access_key: S2) -> Self { + Self { + base_url: endpoint.to_string(), + client: reqwest::Client::new(), + access_key: access_key.to_string(), + } + } + + #[tracing::instrument(skip(self), level = "debug")] + pub async fn options(&self) -> Result { + self.get::(&()).await + } + + /// Performs a get request to the given Get Endpoint + #[tracing::instrument(skip(self), level = "trace")] + async fn get(&self, query: &Q) -> Result { + tracing::trace!("GET request to {}", E::path()); + let response = self + .client + .get(format!("{}/{}", self.base_url, E::path())) + .query(query) + .header("Hydrus-Key", self.access_key.to_string()) + .send() + .await?; + let body = Self::get_body(response).await?; + let bytes = Self::decompress_body(body)?; + let response_type = Self::deserialize_body(bytes)?; + tracing::trace!("response is: {:?}", response_type); + + Ok(response_type) + } + + /// Returns the body from the response + #[tracing::instrument(level = "trace")] + async fn get_body(response: Response) -> Result> { + if response.status().is_success() { + Ok(response.bytes().await?.to_vec()) + } else { + let message = response.text().await?; + Err(Error::Response(message)) + } + } + + /// Uses zlib to decompress the body + #[tracing::instrument(skip(bytes), level = "trace")] + fn decompress_body(mut bytes: Vec) -> Result> { + tracing::trace!("body length {}", bytes.len()); + + let mut buf = Vec::new(); + let mut decoder = ZlibDecoder::new(buf); + + decoder.write_all(&mut bytes)?; + buf = decoder.finish()?; + + tracing::trace!("result length {}", buf.len()); + + Ok(buf) + } + + /// Deserializes the body to the given type + #[tracing::instrument(skip(bytes), level = "trace")] + fn deserialize_body(bytes: Vec) -> Result { + let json_value: serde_json::Value = serde_json::from_reader(&bytes[..])?; + tracing::trace!("json value = {}", json_value.to_string()); + + T::from_json(json_value) + } +} diff --git a/src/client_builder.rs b/src/client_builder.rs new file mode 100644 index 0000000..1890264 --- /dev/null +++ b/src/client_builder.rs @@ -0,0 +1,67 @@ +use crate::Client; +use crate::{Error, Result}; +use std::time::Duration; + +pub struct ClientBuilder { + reqwest_builder: reqwest::ClientBuilder, + endpoint: String, + access_key: Option, +} + +impl Default for ClientBuilder { + fn default() -> Self { + Self { + reqwest_builder: reqwest::ClientBuilder::new(), + endpoint: String::from("https://ptr.hydrus.network:45871"), + access_key: None, + } + } +} + +impl ClientBuilder { + /// Doesn't validate ssl certificates of the endpoint. + /// + /// # Warning + /// Turning this on allows invalid and expired certificates which is a security risk. + pub fn accept_invalid_certs(mut self, accept: bool) -> Self { + self.reqwest_builder = self.reqwest_builder.danger_accept_invalid_certs(accept); + + self + } + + /// Sets the request timeout + pub fn timeout(mut self, timeout: Duration) -> Self { + self.reqwest_builder = self.reqwest_builder.timeout(timeout); + + self + } + + /// Sets the endpoint of the client. + /// The default endpoint is `https://ptr.hydrus.network:45871` + pub fn endpoint(mut self, endpoint: S) -> Self { + self.endpoint = endpoint.to_string(); + + self + } + + /// Sets the access key. This key is required for requests + /// to the PTR. + pub fn access_key(mut self, access_key: S) -> Self { + self.access_key = Some(access_key.to_string()); + + self + } + + /// Validates the configuration and builds the client + pub fn build(self) -> Result { + let access_key = self + .access_key + .ok_or_else(|| Error::Builder(String::from("missing access key")))?; + + Ok(Client { + client: self.reqwest_builder.build()?, + base_url: self.endpoint, + access_key, + }) + } +} diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..fe5a3f2 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,111 @@ +#![allow(unused)] + +pub const HYDRUS_TYPE_BASE: u64 = 0; +pub const HYDRUS_TYPE_BASE_NAMED: u64 = 1; +pub const HYDRUS_TYPE_SHORTCUT_SET: u64 = 2; +pub const HYDRUS_TYPE_SUBSCRIPTION_LEGACY: u64 = 3; +pub const HYDRUS_TYPE_PERIODIC: u64 = 4; +pub const HYDRUS_TYPE_GALLERY_IDENTIFIER: u64 = 5; +pub const HYDRUS_TYPE_TAG_IMPORT_OPTIONS: u64 = 6; +pub const HYDRUS_TYPE_FILE_IMPORT_OPTIONS: u64 = 7; +pub const HYDRUS_TYPE_FILE_SEED_CACHE: u64 = 8; +pub const HYDRUS_TYPE_HDD_IMPORT: u64 = 9; +pub const HYDRUS_TYPE_SERVER_TO_CLIENT_CONTENT_UPDATE_PACKAGE: u64 = 10; +pub const HYDRUS_TYPE_SERVER_TO_CLIENT_SERVICE_UPDATE_PACKAGE: u64 = 11; +pub const HYDRUS_TYPE_MANAGEMENT_CONTROLLER: u64 = 12; +pub const HYDRUS_TYPE_GUI_SESSION_LEGACY: u64 = 13; +pub const HYDRUS_TYPE_PREDICATE: u64 = 14; +pub const HYDRUS_TYPE_FILE_SEARCH_CONTEXT: u64 = 15; +pub const HYDRUS_TYPE_EXPORT_FOLDER: u64 = 16; +pub const HYDRUS_TYPE_WATCHER_IMPORT: u64 = 17; +pub const HYDRUS_TYPE_SIMPLE_DOWNLOADER_IMPORT: u64 = 18; +pub const HYDRUS_TYPE_IMPORT_FOLDER: u64 = 19; +pub const HYDRUS_TYPE_MULTIPLE_GALLERY_IMPORT: u64 = 20; +pub const HYDRUS_TYPE_DICTIONARY: u64 = 21; +pub const HYDRUS_TYPE_CLIENT_OPTIONS: u64 = 22; +pub const HYDRUS_TYPE_CONTENT: u64 = 23; +pub const HYDRUS_TYPE_PETITION: u64 = 24; +pub const HYDRUS_TYPE_ACCOUNT_IDENTIFIER: u64 = 25; +pub const HYDRUS_TYPE_LIST: u64 = 26; +pub const HYDRUS_TYPE_PARSE_FORMULA_HTML: u64 = 27; +pub const HYDRUS_TYPE_URLS_IMPORT: u64 = 28; +pub const HYDRUS_TYPE_PARSE_NODE_CONTENT_LINK: u64 = 29; +pub const HYDRUS_TYPE_CONTENT_PARSER: u64 = 30; +pub const HYDRUS_TYPE_PARSE_FORMULA_JSON: u64 = 31; +pub const HYDRUS_TYPE_PARSE_ROOT_FILE_LOOKUP: u64 = 32; +pub const HYDRUS_TYPE_BYTES_DICT: u64 = 33; +pub const HYDRUS_TYPE_CONTENT_UPDATE: u64 = 34; +pub const HYDRUS_TYPE_CREDENTIALS: u64 = 35; +pub const HYDRUS_TYPE_DEFINITIONS_UPDATE: u64 = 36; +pub const HYDRUS_TYPE_METADATA: u64 = 37; +pub const HYDRUS_TYPE_BANDWIDTH_RULES: u64 = 38; +pub const HYDRUS_TYPE_BANDWIDTH_TRACKER: u64 = 39; +pub const HYDRUS_TYPE_CLIENT_TO_SERVER_UPDATE: u64 = 40; +pub const HYDRUS_TYPE_SHORTCUT: u64 = 41; +pub const HYDRUS_TYPE_APPLICATION_COMMAND: u64 = 42; +pub const HYDRUS_TYPE_DUPLICATE_ACTION_OPTIONS: u64 = 43; +pub const HYDRUS_TYPE_TAG_FILTER: u64 = 44; +pub const HYDRUS_TYPE_NETWORK_BANDWIDTH_MANAGER_LEGACY: u64 = 45; +pub const HYDRUS_TYPE_NETWORK_SESSION_MANAGER_LEGACY: u64 = 46; +pub const HYDRUS_TYPE_NETWORK_CONTEXT: u64 = 47; +pub const HYDRUS_TYPE_NETWORK_LOGIN_MANAGER: u64 = 48; +pub const HYDRUS_TYPE_MEDIA_SORT: u64 = 49; +pub const HYDRUS_TYPE_URL_CLASS: u64 = 50; +pub const HYDRUS_TYPE_STRING_MATCH: u64 = 51; +pub const HYDRUS_TYPE_CHECKER_OPTIONS: u64 = 52; +pub const HYDRUS_TYPE_NETWORK_DOMAIN_MANAGER: u64 = 53; +pub const HYDRUS_TYPE_SUBSCRIPTION_QUERY_LEGACY: u64 = 54; +pub const HYDRUS_TYPE_STRING_CONVERTER: u64 = 55; +pub const HYDRUS_TYPE_FILENAME_TAGGING_OPTIONS: u64 = 56; +pub const HYDRUS_TYPE_FILE_SEED: u64 = 57; +pub const HYDRUS_TYPE_PAGE_PARSER: u64 = 58; +pub const HYDRUS_TYPE_PARSE_FORMULA_COMPOUND: u64 = 59; +pub const HYDRUS_TYPE_PARSE_FORMULA_CONTEXT_VARIABLE: u64 = 60; +pub const HYDRUS_TYPE_TAG_SUMMARY_GENERATOR: u64 = 61; +pub const HYDRUS_TYPE_PARSE_RULE_HTML: u64 = 62; +pub const HYDRUS_TYPE_SIMPLE_DOWNLOADER_PARSE_FORMULA: u64 = 63; +pub const HYDRUS_TYPE_MULTIPLE_WATCHER_IMPORT: u64 = 64; +pub const HYDRUS_TYPE_SERVICE_TAG_IMPORT_OPTIONS: u64 = 65; +pub const HYDRUS_TYPE_GALLERY_SEED: u64 = 66; +pub const HYDRUS_TYPE_GALLERY_SEED_LOG: u64 = 67; +pub const HYDRUS_TYPE_GALLERY_IMPORT: u64 = 68; +pub const HYDRUS_TYPE_GALLERY_URL_GENERATOR: u64 = 69; +pub const HYDRUS_TYPE_NESTED_GALLERY_URL_GENERATOR: u64 = 70; +pub const HYDRUS_TYPE_DOMAIN_METADATA_PACKAGE: u64 = 71; +pub const HYDRUS_TYPE_LOGIN_CREDENTIAL_DEFINITION: u64 = 72; +pub const HYDRUS_TYPE_LOGIN_SCRIPT_DOMAIN: u64 = 73; +pub const HYDRUS_TYPE_LOGIN_STEP: u64 = 74; +pub const HYDRUS_TYPE_CLIENT_API_MANAGER: u64 = 75; +pub const HYDRUS_TYPE_CLIENT_API_PERMISSIONS: u64 = 76; +pub const HYDRUS_TYPE_SERVICE_KEYS_TO_TAGS: u64 = 77; +pub const HYDRUS_TYPE_MEDIA_COLLECT: u64 = 78; +pub const HYDRUS_TYPE_TAG_DISPLAY_MANAGER: u64 = 79; +pub const HYDRUS_TYPE_TAG_SEARCH_CONTEXT: u64 = 80; +pub const HYDRUS_TYPE_FAVOURITE_SEARCH_MANAGER: u64 = 81; +pub const HYDRUS_TYPE_NOTE_IMPORT_OPTIONS: u64 = 82; +pub const HYDRUS_TYPE_STRING_SPLITTER: u64 = 83; +pub const HYDRUS_TYPE_STRING_PROCESSOR: u64 = 84; +pub const HYDRUS_TYPE_TAG_AUTOCOMPLETE_OPTIONS: u64 = 85; +pub const HYDRUS_TYPE_SUBSCRIPTION_QUERY_LOG_CONTAINER: u64 = 86; +pub const HYDRUS_TYPE_SUBSCRIPTION_QUERY_HEADER: u64 = 87; +pub const HYDRUS_TYPE_SUBSCRIPTION: u64 = 88; +pub const HYDRUS_TYPE_FILE_SEED_CACHE_STATUS: u64 = 89; +pub const HYDRUS_TYPE_SUBSCRIPTION_CONTAINER: u64 = 90; +pub const HYDRUS_TYPE_COLUMN_LIST_STATUS: u64 = 91; +pub const HYDRUS_TYPE_COLUMN_LIST_MANAGER: u64 = 92; +pub const HYDRUS_TYPE_NUMBER_TEST: u64 = 93; +pub const HYDRUS_TYPE_NETWORK_BANDWIDTH_MANAGER: u64 = 94; +pub const HYDRUS_TYPE_NETWORK_SESSION_MANAGER: u64 = 95; +pub const HYDRUS_TYPE_NETWORK_SESSION_MANAGER_SESSION_CONTAINER: u64 = 96; +pub const HYDRUS_TYPE_NETWORK_BANDWIDTH_MANAGER_TRACKER_CONTAINER: u64 = 97; +pub const HYDRUS_TYPE_SIDECAR_EXPORTER: u64 = 98; +pub const HYDRUS_TYPE_STRING_SORTER: u64 = 99; +pub const HYDRUS_TYPE_STRING_SLICER: u64 = 100; +pub const HYDRUS_TYPE_TAG_SORT: u64 = 101; +pub const HYDRUS_TYPE_ACCOUNT_TYPE: u64 = 102; +pub const HYDRUS_TYPE_LOCATION_SEARCH_CONTEXT: u64 = 103; +pub const HYDRUS_TYPE_GUI_SESSION_CONTAINER: u64 = 104; +pub const HYDRUS_TYPE_GUI_SESSION_PAGE_DATA: u64 = 105; +pub const HYDRUS_TYPE_GUI_SESSION_CONTAINER_PAGE_NOTEBOOK: u64 = 106; +pub const HYDRUS_TYPE_GUI_SESSION_CONTAINER_PAGE_SINGLE: u64 = 107; +pub const HYDRUS_TYPE_PRESENTATION_IMPORT_OPTIONS: u64 = 108; diff --git a/src/endpoints/mod.rs b/src/endpoints/mod.rs new file mode 100644 index 0000000..9e6cce5 --- /dev/null +++ b/src/endpoints/mod.rs @@ -0,0 +1,32 @@ +mod options; + +use crate::Result; +use std::fmt::Debug; + +pub use options::*; + +#[macro_export] +macro_rules! fix { + ($opt:expr) => { + $opt.ok_or_else(|| crate::Error::Malformed)? + }; +} + +pub trait Endpoint { + fn path() -> &'static str; +} + +pub trait GetEndpoint: Endpoint { + type Response: FromJson + Debug; +} + +pub trait PostEndpoint: Endpoint { + type Request; + type Response: FromJson + Debug; +} + +pub trait FromJson { + fn from_json(value: serde_json::Value) -> Result + where + Self: Sized; +} diff --git a/src/endpoints/options.rs b/src/endpoints/options.rs new file mode 100644 index 0000000..063dd1d --- /dev/null +++ b/src/endpoints/options.rs @@ -0,0 +1,49 @@ +use crate::hydrus_serializable::dictionary::HydrusDictionary; +use crate::hydrus_serializable::wrapper::HydrusSerWrapper; +use crate::Result; +use crate::{fix, Endpoint, FromJson, GetEndpoint}; +use serde_json::Value; + +pub struct Options; + +impl Endpoint for Options { + fn path() -> &'static str { + "options" + } +} + +impl GetEndpoint for Options { + type Response = OptionsResponse; +} + +#[derive(Clone, Debug)] +pub struct OptionsResponse { + server_message: String, + update_period: u64, + nullification_period: u64, + tag_filter: Value, +} + +impl FromJson for OptionsResponse { + fn from_json(value: serde_json::Value) -> Result { + let response = serde_json::from_value::>(value)?; + let options_value = fix!(response.inner.get_one(&"service_options".into())); + let options_value = + serde_json::from_value::>(options_value.clone())? + .inner; + + let server_message = + fix!(fix!(options_value.get_one(&"server_message".into())).as_str()).to_string(); + let update_period = fix!(fix!(options_value.get_one(&"update_period".into())).as_u64()); + let nullification_period = + fix!(fix!(options_value.get_one(&"nullification_period".into())).as_u64()); + let tag_filter = fix!(options_value.get_one(&"tag_filter".into())).clone(); + + Ok(Self { + server_message, + update_period, + nullification_period, + tag_filter, + }) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..be79eaa --- /dev/null +++ b/src/error.rs @@ -0,0 +1,24 @@ +use thiserror::Error; + +pub type Result = std::result::Result; + +#[derive(Debug, Error)] +pub enum Error { + #[error("reqwest error {0}")] + Reqwest(#[from] reqwest::Error), + + #[error("api returned error response: {0}")] + Response(String), + + #[error("failed to parse content as json: {0}")] + JSON(#[from] serde_json::Error), + + #[error("io error {0}")] + Io(#[from] std::io::Error), + + #[error("builder error: {0}")] + Builder(String), + + #[error("malformed response")] + Malformed, +} diff --git a/src/hydrus_serializable/dictionary.rs b/src/hydrus_serializable/dictionary.rs new file mode 100644 index 0000000..82dbadb --- /dev/null +++ b/src/hydrus_serializable/dictionary.rs @@ -0,0 +1,37 @@ +use crate::constants::HYDRUS_TYPE_DICTIONARY; +use crate::hydrus_serializable::HydrusSerializable; +use serde::Deserialize; +use serde_json::Value; + +#[derive(Clone, Debug, Deserialize)] +pub struct HydrusDictionary { + list_sim_sim: Vec<(Value, Value)>, + list_sim_ser: Vec<(Value, Value)>, + list_ser_sim: Vec<(Value, Value)>, + list_ser_ser: Vec<(Value, Value)>, +} + +impl HydrusSerializable for HydrusDictionary { + fn type_id() -> u64 { + HYDRUS_TYPE_DICTIONARY + } +} + +impl HydrusDictionary { + /// Returns the first value for a given key + pub fn get_one(&self, key: &Value) -> Option<&Value> { + self.get(key).into_iter().next() + } + + /// Returns all values for a given key + pub fn get(&self, key: &Value) -> Vec<&Value> { + self.list_sim_sim + .iter() + .chain(self.list_sim_ser.iter()) + .chain(self.list_ser_sim.iter()) + .chain(self.list_ser_ser.iter()) + .filter(|(k, _)| k == key) + .map(|(_, v)| v) + .collect() + } +} diff --git a/src/hydrus_serializable/mod.rs b/src/hydrus_serializable/mod.rs new file mode 100644 index 0000000..db86b78 --- /dev/null +++ b/src/hydrus_serializable/mod.rs @@ -0,0 +1,71 @@ +use crate::hydrus_serializable::dictionary::HydrusDictionary; +use serde::de::{DeserializeOwned, EnumAccess, Error, MapAccess, SeqAccess, Visitor}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_json::Value; +use std::cmp::Ordering; +use std::fmt::Formatter; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; + +pub mod dictionary; +pub mod wrapper; + +pub trait HydrusSerializable: DeserializeOwned { + fn type_id() -> u64; +} + +#[derive(Clone, Debug)] +pub struct SerializableId(u64, PhantomData); + +impl<'de, T: HydrusSerializable> Deserialize<'de> for SerializableId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_u64(SerIdVisitor(PhantomData)) + } +} + +struct SerIdVisitor(PhantomData); + +impl<'de, T: HydrusSerializable> Visitor<'de> for SerIdVisitor { + type Value = SerializableId; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + write!(formatter, "an unsigned integer equal to {}", T::type_id()) + } + + fn visit_u8(self, v: u8) -> Result + where + E: Error, + { + self.visit_u64(v as u64) + } + + fn visit_u16(self, v: u16) -> Result + where + E: Error, + { + self.visit_u64(v as u64) + } + + fn visit_u32(self, v: u32) -> Result + where + E: Error, + { + self.visit_u64(v as u64) + } + + fn visit_u64(self, v: u64) -> Result + where + E: Error, + { + let expected_value = T::type_id(); + if v != expected_value { + Err(E::custom(format!("type not equal to {}", expected_value))) + } else { + Ok(SerializableId(expected_value, PhantomData)) + } + } +} diff --git a/src/hydrus_serializable/wrapper.rs b/src/hydrus_serializable/wrapper.rs new file mode 100644 index 0000000..2d0bd97 --- /dev/null +++ b/src/hydrus_serializable/wrapper.rs @@ -0,0 +1,10 @@ +use crate::hydrus_serializable::{HydrusSerializable, SerializableId}; +use serde::Deserialize; + +#[derive(Clone, Debug, Deserialize)] +#[serde(bound = "")] +pub struct HydrusSerWrapper { + pub type_id: SerializableId, + pub version: u8, + pub inner: T, +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d28159a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,10 @@ +mod client; +mod client_builder; +pub(crate) mod constants; +mod endpoints; +mod error; +pub mod hydrus_serializable; + +pub use client::*; +pub use client_builder::*; +pub use error::*; diff --git a/tests/common.rs b/tests/common.rs new file mode 100644 index 0000000..51029b9 --- /dev/null +++ b/tests/common.rs @@ -0,0 +1,25 @@ +use hydrus_ptr_client::Client; +use std::env; +use std::sync::{Arc, Mutex, MutexGuard}; + +fn setup() { + lazy_static::lazy_static! { static ref SETUP_DONE: Arc> = Arc::new(Mutex::new(false)); } + let mut setup_done: MutexGuard = SETUP_DONE.lock().unwrap(); + + if !*setup_done { + dotenv::dotenv().expect("failed to initialize dotenv"); + tracing_subscriber::fmt::init(); + *setup_done = true; + } +} + +pub fn get_client() -> Client { + setup(); + + Client::builder() + .endpoint(env::var("PTR_URL").unwrap()) + .access_key(env::var("PTR_ACCESS_KEY").unwrap()) + .accept_invalid_certs(true) + .build() + .unwrap() +} diff --git a/tests/endpoints.rs b/tests/endpoints.rs new file mode 100644 index 0000000..2a76f14 --- /dev/null +++ b/tests/endpoints.rs @@ -0,0 +1,7 @@ +mod common; + +#[tokio::test] +async fn test_options() { + let client = common::get_client(); + client.options().await.unwrap(); +}