From 6e1135e1024cb1fb9faa4d196f5b25d018615430 Mon Sep 17 00:00:00 2001 From: TotallyNot <44345987+TotallyNot@users.noreply.github.com> Date: Sun, 22 Jan 2023 00:50:16 +0100 Subject: [PATCH] fix Competition deserialisation --- torn-api/Cargo.toml | 12 +- torn-api/benches/deserialisation_benchmark.rs | 31 +++- torn-api/src/common.rs | 41 ++++++ torn-api/src/de_util.rs | 52 ++++++- torn-api/src/faction.rs | 136 ++++++++++++++++-- torn-api/src/lib.rs | 38 +++-- torn-api/src/local.rs | 61 +++++--- torn-api/src/send.rs | 61 +++++--- torn-api/src/torn.rs | 16 ++- torn-api/src/user.rs | 112 +++++---------- 10 files changed, 405 insertions(+), 155 deletions(-) create mode 100644 torn-api/src/common.rs diff --git a/torn-api/Cargo.toml b/torn-api/Cargo.toml index 8c0efc8..bd78901 100644 --- a/torn-api/Cargo.toml +++ b/torn-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "torn-api" -version = "0.5.3" +version = "0.5.4" edition = "2021" authors = ["Pyrit [2111649]"] license = "MIT" @@ -13,9 +13,16 @@ name = "deserialisation_benchmark" harness = false [features] -default = [ "reqwest" ] +default = [ "reqwest", "user", "faction", "torn" ] reqwest = [ "dep:reqwest" ] awc = [ "dep:awc" ] +decimal = [ "dep:rust_decimal" ] + +user = [ "__common" ] +faction = [ "__common" ] +torn = [ "__common" ] + +__common = [] [dependencies] serde = { version = "1", features = [ "derive" ] } @@ -28,6 +35,7 @@ futures = "0.3" reqwest = { version = "0.11", default-features = false, features = [ "json" ], optional = true } awc = { version = "3", default-features = false, optional = true } +rust_decimal = { version = "1", default-features = false, optional = true, features = [ "serde" ] } torn-api-macros = { path = "../torn-api-macros", version = "0.1.1" } diff --git a/torn-api/benches/deserialisation_benchmark.rs b/torn-api/benches/deserialisation_benchmark.rs index 359f087..945eb5f 100644 --- a/torn-api/benches/deserialisation_benchmark.rs +++ b/torn-api/benches/deserialisation_benchmark.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use torn_api::{faction, user, ThreadSafeApiClient}; +use torn_api::{faction, send::ApiClient, user}; pub fn user_benchmark(c: &mut Criterion) { dotenv::dotenv().unwrap(); @@ -54,12 +54,37 @@ pub fn faction_benchmark(c: &mut Criterion) { .unwrap() }); - c.bench_function("user deserialize", |b| { + c.bench_function("faction deserialize", |b| { b.iter(|| { response.basic().unwrap(); }) }); } -criterion_group!(benches, user_benchmark, faction_benchmark); +pub fn attacks_full(c: &mut Criterion) { + dotenv::dotenv().unwrap(); + let rt = tokio::runtime::Builder::new_current_thread() + .enable_io() + .enable_time() + .build() + .unwrap(); + let response = rt.block_on(async { + let key = std::env::var("APIKEY").expect("api key"); + let client = reqwest::Client::default(); + + client + .torn_api(key) + .faction(|b| b.selections(&[faction::Selection::AttacksFull])) + .await + .unwrap() + }); + + c.bench_function("attacksfull deserialize", |b| { + b.iter(|| { + response.attacks_full().unwrap(); + }) + }); +} + +criterion_group!(benches, user_benchmark, faction_benchmark, attacks_full); criterion_main!(benches); diff --git a/torn-api/src/common.rs b/torn-api/src/common.rs new file mode 100644 index 0000000..953317f --- /dev/null +++ b/torn-api/src/common.rs @@ -0,0 +1,41 @@ +use chrono::{serde::ts_seconds, DateTime, Utc}; +use serde::Deserialize; + +use crate::de_util; + +#[derive(Debug, Clone, Deserialize)] +pub struct LastAction { + #[serde(with = "ts_seconds")] + pub timestamp: DateTime, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] +pub enum State { + Okay, + Traveling, + Hospital, + Abroad, + Jail, + Federal, + Fallen, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum StateColour { + Green, + Red, + Blue, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Status<'a> { + pub description: &'a str, + #[serde(deserialize_with = "de_util::empty_string_is_none")] + pub details: Option<&'a str>, + #[serde(rename = "color")] + pub colour: StateColour, + pub state: State, + #[serde(deserialize_with = "de_util::zero_date_is_none")] + pub until: Option>, +} diff --git a/torn-api/src/de_util.rs b/torn-api/src/de_util.rs index cf2a2a5..7a79b33 100644 --- a/torn-api/src/de_util.rs +++ b/torn-api/src/de_util.rs @@ -1,11 +1,13 @@ -use chrono::{DateTime, NaiveDateTime, Utc}; -use serde::de::{Deserialize, Deserializer, Error, Unexpected}; +#![allow(unused)] -pub fn empty_string_is_none<'de, D>(deserializer: D) -> Result, D::Error> +use chrono::{DateTime, NaiveDateTime, Utc}; +use serde::de::{Deserialize, Deserializer, Error, Unexpected, Visitor}; + +pub(crate) fn empty_string_is_none<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { - let s = String::deserialize(deserializer)?; + let s: &str = Deserialize::deserialize(deserializer)?; if s.is_empty() { Ok(None) } else { @@ -13,7 +15,7 @@ where } } -pub fn string_is_long<'de, D>(deserializer: D) -> Result, D::Error> +pub(crate) fn string_is_long<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { @@ -27,7 +29,7 @@ where } } -pub fn zero_date_is_none<'de, D>(deserializer: D) -> Result>, D::Error> +pub(crate) fn zero_date_is_none<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, { @@ -40,7 +42,7 @@ where } } -pub fn int_is_bool<'de, D>(deserializer: D) -> Result +pub(crate) fn int_is_bool<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { @@ -52,3 +54,39 @@ where x => Err(Error::invalid_value(Unexpected::Signed(x), &"0 or 1")), } } + +pub(crate) fn empty_string_int_option<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + struct DumbVisitor; + + impl<'de> Visitor<'de> for DumbVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "Empty string or integer") + } + + // serde_json will treat all unsigned integers as u64 + fn visit_u64(self, v: u64) -> Result + where + E: Error, + { + Ok(Some(v as i32)) + } + + fn visit_borrowed_str(self, v: &'de str) -> Result + where + E: Error, + { + if v.is_empty() { + Ok(None) + } else { + Err(E::invalid_value(Unexpected::Str(v), &self)) + } + } + } + + deserializer.deserialize_any(DumbVisitor) +} diff --git a/torn-api/src/faction.rs b/torn-api/src/faction.rs index 028cab5..2a4d652 100644 --- a/torn-api/src/faction.rs +++ b/torn-api/src/faction.rs @@ -1,31 +1,42 @@ use std::collections::BTreeMap; +use chrono::{serde::ts_seconds, DateTime, Utc}; use serde::Deserialize; use torn_api_macros::ApiCategory; +use crate::de_util; + +pub use crate::common::{LastAction, Status}; + #[derive(Debug, Clone, Copy, ApiCategory)] #[api(category = "faction")] pub enum Selection { #[api(type = "Basic", flatten)] Basic, + + #[api(type = "BTreeMap", field = "attacks")] + AttacksFull, + + #[api(type = "BTreeMap", field = "attacks")] + Attacks, } #[derive(Debug, Clone, Deserialize)] -pub struct Member { - pub name: String, +pub struct Member<'a> { + pub name: &'a str, pub level: i16, pub days_in_faction: i16, - pub position: String, - pub status: super::user::Status, - pub last_action: super::user::LastAction, + pub position: &'a str, + pub status: Status<'a>, + pub last_action: LastAction, } #[derive(Debug, Clone, Deserialize)] -pub struct Basic { +pub struct Basic<'a> { #[serde(rename = "ID")] pub id: i32, - pub name: String, + pub name: &'a str, pub leader: i32, pub respect: i32, @@ -33,7 +44,112 @@ pub struct Basic { pub capacity: i16, pub best_chain: i32, - pub members: BTreeMap, + #[serde(borrow)] + pub members: BTreeMap>, +} + +#[derive(Debug, Clone, Copy, Deserialize)] +pub enum AttackResult { + Attacked, + Mugged, + Hospitalized, + Lost, + Arrested, + Escape, + Interrupted, + Assist, + Timeout, + Stalemate, + Special, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Attack<'a> { + pub code: &'a str, + #[serde(with = "ts_seconds")] + pub timestamp_started: DateTime, + #[serde(with = "ts_seconds")] + pub timestamp_ended: DateTime, + + #[serde(deserialize_with = "de_util::empty_string_int_option")] + pub attacker_id: Option, + #[serde(deserialize_with = "de_util::empty_string_int_option")] + pub attacker_faction: Option, + pub defender_id: i32, + #[serde(deserialize_with = "de_util::empty_string_int_option")] + pub defender_faction: Option, + pub result: AttackResult, + + #[serde(deserialize_with = "de_util::int_is_bool")] + pub stealthed: bool, + + #[cfg(feature = "decimal")] + pub respect: rust_decimal::Decimal, + + #[cfg(not(feature = "decimal"))] + pub respect: f32, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct RespectModifiers { + pub fair_fight: f32, + pub war: f32, + pub retaliation: f32, + pub group_attack: f32, + pub overseas: f32, + pub chain_bonus: f32, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct AttackFull<'a> { + pub code: &'a str, + #[serde(with = "ts_seconds")] + pub timestamp_started: DateTime, + #[serde(with = "ts_seconds")] + pub timestamp_ended: DateTime, + + #[serde(deserialize_with = "de_util::empty_string_int_option")] + pub attacker_id: Option, + #[serde(deserialize_with = "de_util::empty_string_is_none")] + pub attacker_name: Option<&'a str>, + #[serde(deserialize_with = "de_util::empty_string_int_option")] + pub attacker_faction: Option, + #[serde( + deserialize_with = "de_util::empty_string_is_none", + rename = "attacker_factionname" + )] + pub attacker_faction_name: Option<&'a str>, + + pub defender_id: i32, + pub defender_name: &'a str, + #[serde(deserialize_with = "de_util::empty_string_int_option")] + pub defender_faction: Option, + #[serde( + deserialize_with = "de_util::empty_string_is_none", + rename = "defender_factionname" + )] + pub defender_faction_name: Option<&'a str>, + + pub result: AttackResult, + + #[serde(deserialize_with = "de_util::int_is_bool")] + pub stealthed: bool, + #[serde(deserialize_with = "de_util::int_is_bool")] + pub raid: bool, + #[serde(deserialize_with = "de_util::int_is_bool")] + pub ranked_war: bool, + + #[cfg(feature = "decimal")] + pub respect: rust_decimal::Decimal, + #[cfg(feature = "decimal")] + pub respect_loss: rust_decimal::Decimal, + + #[cfg(not(feature = "decimal"))] + pub respect: f32, + #[cfg(not(feature = "decimal"))] + pub respect_loss: f32, + + pub modifiers: RespectModifiers, } #[cfg(test)] @@ -47,10 +163,12 @@ mod tests { let response = Client::default() .torn_api(key) - .faction(|b| b.selections(&[Selection::Basic])) + .faction(|b| b.selections(&[Selection::Basic, Selection::Attacks])) .await .unwrap(); response.basic().unwrap(); + response.attacks().unwrap(); + response.attacks_full().unwrap(); } } diff --git a/torn-api/src/lib.rs b/torn-api/src/lib.rs index 5c05602..872077f 100644 --- a/torn-api/src/lib.rs +++ b/torn-api/src/lib.rs @@ -1,23 +1,32 @@ #![warn(clippy::all, clippy::perf, clippy::style, clippy::suspicious)] -pub mod faction; pub mod local; pub mod send; -pub mod torn; + +#[cfg(feature = "user")] pub mod user; +#[cfg(feature = "faction")] +pub mod faction; + +#[cfg(feature = "torn")] +pub mod torn; + #[cfg(feature = "awc")] pub mod awc; #[cfg(feature = "reqwest")] pub mod reqwest; +#[cfg(feature = "__common")] +mod common; + mod de_util; use std::fmt::Write; use chrono::{DateTime, Utc}; -use serde::de::{DeserializeOwned, Error as DeError}; +use serde::{de::Error as DeError, Deserialize}; use thiserror::Error; pub struct ApiResponse { @@ -53,16 +62,18 @@ impl ApiResponse { } } - fn decode(&self) -> serde_json::Result + #[allow(dead_code)] + fn decode<'de, D>(&'de self) -> serde_json::Result where - D: DeserializeOwned, + D: Deserialize<'de>, { D::deserialize(&self.value) } - fn decode_field(&self, field: &'static str) -> serde_json::Result + #[allow(dead_code)] + fn decode_field<'de, D>(&'de self, field: &'static str) -> serde_json::Result where - D: DeserializeOwned, + D: Deserialize<'de>, { self.value .get(field) @@ -70,6 +81,7 @@ impl ApiResponse { .and_then(D::deserialize) } + #[allow(dead_code)] fn decode_field_with<'de, V, F>(&'de self, field: &'static str, fun: F) -> serde_json::Result where F: FnOnce(&'de serde_json::Value) -> serde_json::Result, @@ -236,6 +248,7 @@ where } #[cfg(test)] +#[allow(unused)] pub(crate) mod tests { use std::sync::Once; @@ -265,12 +278,13 @@ pub(crate) mod tests { std::env::var("APIKEY").expect("api key") } + #[cfg(feature = "user")] #[test] fn selection_raw_value() { assert_eq!(user::Selection::Basic.raw_value(), "basic"); } - #[cfg(feature = "reqwest")] + #[cfg(all(feature = "reqwest", feature = "user"))] #[tokio::test] async fn reqwest() { let key = setup(); @@ -278,15 +292,11 @@ pub(crate) mod tests { Client::default().torn_api(key).user(|b| b).await.unwrap(); } - #[cfg(feature = "awc")] + #[cfg(all(feature = "awc", feature = "user"))] #[actix_rt::test] async fn awc() { let key = setup(); - Client::default() - .torn_api(key) - .user(None, |b| b) - .await - .unwrap(); + Client::default().torn_api(key).user(|b| b).await.unwrap(); } } diff --git a/torn-api/src/local.rs b/torn-api/src/local.rs index 17013a2..1b005c4 100644 --- a/torn-api/src/local.rs +++ b/torn-api/src/local.rs @@ -2,17 +2,16 @@ use std::collections::HashMap; use async_trait::async_trait; -use crate::{ - faction, torn, user, ApiCategoryResponse, ApiClientError, ApiRequest, ApiRequestBuilder, - ApiResponse, DirectExecutor, -}; +use crate::{ApiCategoryResponse, ApiClientError, ApiRequest, ApiResponse, DirectExecutor}; pub struct ApiProvider<'a, C, E> where C: ApiClient, E: RequestExecutor, { + #[allow(dead_code)] client: &'a C, + #[allow(dead_code)] executor: E, } @@ -25,11 +24,14 @@ where Self { client, executor } } - pub async fn user(&self, build: F) -> Result + #[cfg(feature = "user")] + pub async fn user(&self, build: F) -> Result where - F: FnOnce(ApiRequestBuilder) -> ApiRequestBuilder, + F: FnOnce( + crate::ApiRequestBuilder, + ) -> crate::ApiRequestBuilder, { - let mut builder = ApiRequestBuilder::default(); + let mut builder = crate::ApiRequestBuilder::default(); builder = build(builder); self.executor @@ -37,18 +39,21 @@ where .await } + #[cfg(feature = "user")] pub async fn users( &self, ids: L, build: F, - ) -> HashMap> + ) -> HashMap> where - F: FnOnce(ApiRequestBuilder) -> ApiRequestBuilder, + F: FnOnce( + crate::ApiRequestBuilder, + ) -> crate::ApiRequestBuilder, I: num_traits::AsPrimitive + std::hash::Hash + std::cmp::Eq, i64: num_traits::AsPrimitive, L: IntoIterator, { - let mut builder = ApiRequestBuilder::default(); + let mut builder = crate::ApiRequestBuilder::default(); builder = build(builder); self.executor @@ -63,11 +68,14 @@ where .collect() } - pub async fn faction(&self, build: F) -> Result + #[cfg(feature = "faction")] + pub async fn faction(&self, build: F) -> Result where - F: FnOnce(ApiRequestBuilder) -> ApiRequestBuilder, + F: FnOnce( + crate::ApiRequestBuilder, + ) -> crate::ApiRequestBuilder, { - let mut builder = ApiRequestBuilder::default(); + let mut builder = crate::ApiRequestBuilder::default(); builder = build(builder); self.executor @@ -75,18 +83,21 @@ where .await } + #[cfg(feature = "faction")] pub async fn factions( &self, ids: L, build: F, - ) -> HashMap> + ) -> HashMap> where - F: FnOnce(ApiRequestBuilder) -> ApiRequestBuilder, + F: FnOnce( + crate::ApiRequestBuilder, + ) -> crate::ApiRequestBuilder, I: num_traits::AsPrimitive + std::hash::Hash + std::cmp::Eq, i64: num_traits::AsPrimitive, L: IntoIterator, { - let mut builder = ApiRequestBuilder::default(); + let mut builder = crate::ApiRequestBuilder::default(); builder = build(builder); self.executor @@ -101,11 +112,14 @@ where .collect() } - pub async fn torn(&self, build: F) -> Result + #[cfg(feature = "torn")] + pub async fn torn(&self, build: F) -> Result where - F: FnOnce(ApiRequestBuilder) -> ApiRequestBuilder, + F: FnOnce( + crate::ApiRequestBuilder, + ) -> crate::ApiRequestBuilder, { - let mut builder = ApiRequestBuilder::default(); + let mut builder = crate::ApiRequestBuilder::default(); builder = build(builder); self.executor @@ -113,18 +127,21 @@ where .await } + #[cfg(feature = "torn")] pub async fn torns( &self, ids: L, build: F, - ) -> HashMap> + ) -> HashMap> where - F: FnOnce(ApiRequestBuilder) -> ApiRequestBuilder, + F: FnOnce( + crate::ApiRequestBuilder, + ) -> crate::ApiRequestBuilder, I: num_traits::AsPrimitive + std::hash::Hash + std::cmp::Eq, i64: num_traits::AsPrimitive, L: IntoIterator, { - let mut builder = ApiRequestBuilder::default(); + let mut builder = crate::ApiRequestBuilder::default(); builder = build(builder); self.executor diff --git a/torn-api/src/send.rs b/torn-api/src/send.rs index 2989bec..7258494 100644 --- a/torn-api/src/send.rs +++ b/torn-api/src/send.rs @@ -2,17 +2,16 @@ use std::collections::HashMap; use async_trait::async_trait; -use crate::{ - faction, torn, user, ApiCategoryResponse, ApiClientError, ApiRequest, ApiRequestBuilder, - ApiResponse, DirectExecutor, -}; +use crate::{ApiCategoryResponse, ApiClientError, ApiRequest, ApiResponse, DirectExecutor}; pub struct ApiProvider<'a, C, E> where C: ApiClient, E: RequestExecutor, { + #[allow(dead_code)] client: &'a C, + #[allow(dead_code)] executor: E, } @@ -25,11 +24,14 @@ where Self { client, executor } } - pub async fn user(&self, build: F) -> Result + #[cfg(feature = "user")] + pub async fn user(&self, build: F) -> Result where - F: FnOnce(ApiRequestBuilder) -> ApiRequestBuilder, + F: FnOnce( + crate::ApiRequestBuilder, + ) -> crate::ApiRequestBuilder, { - let mut builder = ApiRequestBuilder::default(); + let mut builder = crate::ApiRequestBuilder::default(); builder = build(builder); self.executor @@ -37,18 +39,21 @@ where .await } + #[cfg(feature = "user")] pub async fn users( &self, ids: L, build: F, - ) -> HashMap> + ) -> HashMap> where - F: FnOnce(ApiRequestBuilder) -> ApiRequestBuilder, + F: FnOnce( + crate::ApiRequestBuilder, + ) -> crate::ApiRequestBuilder, I: num_traits::AsPrimitive + std::hash::Hash + std::cmp::Eq, i64: num_traits::AsPrimitive, L: IntoIterator, { - let mut builder = ApiRequestBuilder::default(); + let mut builder = crate::ApiRequestBuilder::default(); builder = build(builder); self.executor @@ -63,11 +68,14 @@ where .collect() } - pub async fn faction(&self, build: F) -> Result + #[cfg(feature = "faction")] + pub async fn faction(&self, build: F) -> Result where - F: FnOnce(ApiRequestBuilder) -> ApiRequestBuilder, + F: FnOnce( + crate::ApiRequestBuilder, + ) -> crate::ApiRequestBuilder, { - let mut builder = ApiRequestBuilder::default(); + let mut builder = crate::ApiRequestBuilder::default(); builder = build(builder); self.executor @@ -75,18 +83,21 @@ where .await } + #[cfg(feature = "faction")] pub async fn factions( &self, ids: L, build: F, - ) -> HashMap> + ) -> HashMap> where - F: FnOnce(ApiRequestBuilder) -> ApiRequestBuilder, + F: FnOnce( + crate::ApiRequestBuilder, + ) -> crate::ApiRequestBuilder, I: num_traits::AsPrimitive + std::hash::Hash + std::cmp::Eq, i64: num_traits::AsPrimitive, L: IntoIterator, { - let mut builder = ApiRequestBuilder::default(); + let mut builder = crate::ApiRequestBuilder::default(); builder = build(builder); self.executor @@ -101,11 +112,14 @@ where .collect() } - pub async fn torn(&self, build: F) -> Result + #[cfg(feature = "torn")] + pub async fn torn(&self, build: F) -> Result where - F: FnOnce(ApiRequestBuilder) -> ApiRequestBuilder, + F: FnOnce( + crate::ApiRequestBuilder, + ) -> crate::ApiRequestBuilder, { - let mut builder = ApiRequestBuilder::default(); + let mut builder = crate::ApiRequestBuilder::default(); builder = build(builder); self.executor @@ -113,18 +127,21 @@ where .await } + #[cfg(feature = "torn")] pub async fn torns( &self, ids: L, build: F, - ) -> HashMap> + ) -> HashMap> where - F: FnOnce(ApiRequestBuilder) -> ApiRequestBuilder, + F: FnOnce( + crate::ApiRequestBuilder, + ) -> crate::ApiRequestBuilder, I: num_traits::AsPrimitive + std::hash::Hash + std::cmp::Eq, i64: num_traits::AsPrimitive, L: IntoIterator, { - let mut builder = ApiRequestBuilder::default(); + let mut builder = crate::ApiRequestBuilder::default(); builder = build(builder); self.executor diff --git a/torn-api/src/torn.rs b/torn-api/src/torn.rs index 5a0f2d4..f8c69f4 100644 --- a/torn-api/src/torn.rs +++ b/torn-api/src/torn.rs @@ -46,6 +46,20 @@ where formatter.write_str("struct Competition") } + fn visit_some(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_map(self) + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, @@ -77,7 +91,7 @@ where } } - deserializer.deserialize_map(CompetitionVisitor) + deserializer.deserialize_option(CompetitionVisitor) } #[cfg(test)] diff --git a/torn-api/src/user.rs b/torn-api/src/user.rs index bbaadc1..912baae 100644 --- a/torn-api/src/user.rs +++ b/torn-api/src/user.rs @@ -1,4 +1,3 @@ -use chrono::{serde::ts_seconds, DateTime, Utc}; use serde::{ de::{self, MapAccess, Visitor}, Deserialize, Deserializer, @@ -6,7 +5,9 @@ use serde::{ use torn_api_macros::ApiCategory; -use super::de_util; +use crate::de_util; + +pub use crate::common::{LastAction, Status}; #[derive(Debug, Clone, Copy, ApiCategory)] #[api(category = "user")] @@ -30,22 +31,16 @@ pub enum Gender { Enby, } -#[derive(Debug, Clone, Deserialize)] -pub struct LastAction { - #[serde(with = "ts_seconds")] - pub timestamp: DateTime, -} - #[derive(Debug, Clone)] -pub struct Faction { +pub struct Faction<'a> { pub faction_id: i32, - pub faction_name: String, + pub faction_name: &'a str, pub days_in_faction: i16, - pub position: String, - pub faction_tag: Option, + pub position: &'a str, + pub faction_tag: Option<&'a str>, } -fn deserialize_faction<'de, D>(deserializer: D) -> Result, D::Error> +fn deserialize_faction<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, { @@ -62,7 +57,7 @@ where struct FactionVisitor; impl<'de> Visitor<'de> for FactionVisitor { - type Value = Option; + type Value = Option>; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("struct Faction") @@ -128,44 +123,13 @@ where deserializer.deserialize_struct("Faction", FIELDS, FactionVisitor) } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] -pub enum State { - Okay, - Traveling, - Hospital, - Abroad, - Jail, - Federal, - Fallen, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum StateColour { - Green, - Red, - Blue, -} - #[derive(Debug, Clone, Deserialize)] -pub struct Status { - pub description: String, - #[serde(deserialize_with = "de_util::empty_string_is_none")] - pub details: Option, - #[serde(rename = "color")] - pub colour: StateColour, - pub state: State, - #[serde(deserialize_with = "de_util::zero_date_is_none")] - pub until: Option>, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct Basic { +pub struct Basic<'a> { pub player_id: i32, - pub name: String, + pub name: &'a str, pub level: i16, pub gender: Gender, - pub status: Status, + pub status: Status<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -238,6 +202,20 @@ where formatter.write_str("struct Competition") } + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(self) + } + fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, @@ -259,11 +237,11 @@ where attacks = Some(map.next_value()?); } Field::Team => { - let team_raw: String = map.next_value()?; + let team_raw: &str = map.next_value()?; team = if team_raw.is_empty() { None } else { - Some(match team_raw.as_str() { + Some(match team_raw { "firestarters" => EliminationTeam::Firestarters, "hard-boiled" => EliminationTeam::HardBoiled, "quack-addicts" => EliminationTeam::QuackAddicts, @@ -277,7 +255,7 @@ where "wolf-pack" => EliminationTeam::WolfPack, "sleepyheads" => EliminationTeam::Sleepyheads, _ => Err(de::Error::unknown_variant( - &team_raw, + team_raw, &[ "firestarters", "hard-boiled", @@ -320,14 +298,14 @@ where } } - deserializer.deserialize_map(CompetitionVisitor) + deserializer.deserialize_option(CompetitionVisitor) } #[derive(Debug, Clone, Deserialize)] -pub struct Profile { +pub struct Profile<'a> { pub player_id: i32, - pub name: String, - pub rank: String, + pub name: &'a str, + pub rank: &'a str, pub level: i16, pub gender: Gender, pub age: i32, @@ -335,8 +313,8 @@ pub struct Profile { pub life: LifeBar, pub last_action: LastAction, #[serde(deserialize_with = "deserialize_faction")] - pub faction: Option, - pub status: Status, + pub faction: Option>, + pub status: Status<'a>, #[serde(deserialize_with = "deserialize_comp")] pub competition: Option, @@ -436,20 +414,6 @@ mod tests { assert!(faction.is_none()); } - #[async_test] - async fn team_visible() { - let key = setup(); - - let response = Client::default() - .torn_api(key) - .user(|b| b.selections(&[Selection::Profile])) - .await - .unwrap(); - - let profile = response.profile().unwrap(); - assert!(profile.competition.is_some()); - } - #[async_test] async fn bulk() { let key = setup(); @@ -469,14 +433,12 @@ mod tests { async fn discord() { let key = setup(); - let basic = Client::default() + let response = Client::default() .torn_api(key) .user(|b| b.id(374272176892674048i64).selections(&[Selection::Basic])) .await - .unwrap() - .basic() .unwrap(); - assert_eq!(basic.player_id, 2111649); + assert_eq!(response.basic().unwrap().player_id, 2111649); } }