use crate::core::{HtmlFragment, Instant};
use crate::email::EmailAddress;
use crate::link::VersionedEtwinLink;
use crate::temporal::LatestTemporal;
use crate::types::AnyError;
use async_trait::async_trait;
use auto_impl::auto_impl;
#[cfg(feature = "serde")]
use etwin_serde_tools::{deserialize_nested_option, Deserialize, Serialize};
#[cfg(feature = "sqlx")]
use sqlx::{postgres, Postgres};
use std::collections::{BTreeMap, BTreeSet};
use std::convert::Infallible;
use std::num::NonZeroU16;
use std::str::FromStr;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestPassword(String);
impl HammerfestPassword {
pub fn new(raw: String) -> Self {
Self(raw)
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl FromStr for HammerfestPassword {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::new(s.to_string()))
}
}
declare_new_enum!(
pub enum HammerfestServer {
#[str("hammerfest.fr")]
HammerfestFr,
#[str("hfest.net")]
HfestNet,
#[str("hammerfest.es")]
HammerfestEs,
}
pub type ParseError = HammerfestServerParseError;
const SQL_NAME = "hammerfest_server";
);
declare_new_enum!(
pub enum HammerfestQuestStatus {
#[str("None")]
None,
#[str("Pending")]
Pending,
#[str("Complete")]
Complete,
}
pub type ParseError = HammerfestQuestStatusParseError;
const SQL_NAME = "hammerfest_quest_status";
);
declare_new_string! {
pub struct HammerfestUsername(String);
pub type ParseError = HammerfestUsernameParseError;
const PATTERN = r"^[0-9A-Za-z]{1,12}$";
const SQL_NAME = "hammerfest_username";
}
declare_decimal_id! {
pub struct HammerfestUserId(u32);
pub type ParseError = HammerfestUserIdParseError;
const BOUNDS = 1..1_000_000_000;
const SQL_NAME = "hammerfest_user_id";
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "HammerfestUser"))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestUserIdRef {
pub server: HammerfestServer,
pub id: HammerfestUserId,
}
declare_new_string! {
pub struct HammerfestSessionKey(String);
pub type ParseError = HammerfestSessionKeyParseError;
const PATTERN = r"^[0-9a-z]{26}$";
const SQL_NAME = "hammerfest_session_key";
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestCredentials {
pub server: HammerfestServer,
pub username: HammerfestUsername,
pub password: HammerfestPassword,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "HammerfestUser"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ShortHammerfestUser {
pub server: HammerfestServer,
pub id: HammerfestUserId,
pub username: HammerfestUsername,
}
impl ShortHammerfestUser {
pub const fn as_ref(&self) -> HammerfestUserIdRef {
HammerfestUserIdRef {
server: self.server,
id: self.id,
}
}
}
impl From<StoredHammerfestUser> for ShortHammerfestUser {
fn from(value: StoredHammerfestUser) -> Self {
Self {
server: value.server,
id: value.id,
username: value.username,
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "HammerfestUser"))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StoredHammerfestUser {
pub server: HammerfestServer,
pub id: HammerfestUserId,
pub username: HammerfestUsername,
pub archived_at: Instant,
pub profile: Option<LatestTemporal<StoredHammerfestProfile>>,
pub inventory: Option<LatestTemporal<BTreeMap<HammerfestItemId, u32>>>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "HammerfestUser"))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HammerfestUser {
pub server: HammerfestServer,
pub id: HammerfestUserId,
pub username: HammerfestUsername,
pub archived_at: Instant,
pub profile: Option<LatestTemporal<StoredHammerfestProfile>>,
pub inventory: Option<LatestTemporal<BTreeMap<HammerfestItemId, u32>>>,
pub etwin: VersionedEtwinLink,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StoredHammerfestProfile {
pub best_score: u32,
pub best_level: u32,
pub game_completed: bool,
pub items: BTreeSet<HammerfestItemId>,
pub quests: BTreeMap<HammerfestQuestId, HammerfestQuestStatus>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestSession {
pub key: HammerfestSessionKey,
pub user: ShortHammerfestUser,
pub ctime: Instant,
pub atime: Instant,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StoredHammerfestSession {
pub key: HammerfestSessionKey,
pub user: HammerfestUserIdRef,
pub ctime: Instant,
pub atime: Instant,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestGetProfileByIdOptions {
pub server: HammerfestServer,
pub user_id: HammerfestUserId,
}
declare_new_int! {
pub struct HammerfestLadderLevel(u8);
pub type RangeError = HammerfestLadderLevelRangeError;
const BOUNDS = 0..=4;
type SqlType = i16;
const SQL_NAME = "hammerfest_ladder_level";
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HammerfestProfileResponse {
pub session: Option<HammerfestSessionUser>,
pub profile: Option<HammerfestProfile>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HammerfestProfile {
pub user: ShortHammerfestUser,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
#[cfg_attr(feature = "serde", serde(default, deserialize_with = "deserialize_nested_option"))]
pub email: Option<Option<EmailAddress>>,
pub best_score: u32,
pub best_level: u8,
pub has_carrot: bool,
pub season_score: u32,
pub ladder_level: HammerfestLadderLevel,
pub hall_of_fame: Option<HammerfestHallOfFameMessage>,
pub items: BTreeSet<HammerfestItemId>,
pub quests: BTreeMap<HammerfestQuestId, HammerfestQuestStatus>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HammerfestInventoryResponse {
pub session: HammerfestSessionUser,
pub inventory: BTreeMap<HammerfestItemId, u32>,
}
declare_decimal_id! {
pub struct HammerfestQuestId(u8);
pub type ParseError = HammerfestQuestIdParseError;
const BOUNDS = 0..76;
const SQL_NAME = "hammerfest_quest_id";
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestHallOfFameMessage {
pub date: Instant,
pub message: String,
}
declare_decimal_id! {
pub struct HammerfestItemId(u16);
pub type ParseError = HammerfestItemIdParseError;
const BOUNDS = 0..10_000;
const SQL_NAME = "hammerfest_item_id";
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestShopResponse {
pub session: HammerfestSessionUser,
pub shop: HammerfestShop,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestSessionUser {
pub user: ShortHammerfestUser,
pub tokens: u32,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestGodchild {
pub user: ShortHammerfestUser,
pub tokens: u32,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestGodchildrenResponse {
pub session: HammerfestSessionUser,
pub godchildren: Vec<HammerfestGodchild>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestShop {
pub weekly_tokens: u8,
pub purchased_tokens: Option<u8>,
pub has_quest_bonus: bool,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestDate {
pub month: u8,
pub day: u8,
pub weekday: u8,
}
#[cfg(feature = "sqlx")]
impl sqlx::Type<Postgres> for HammerfestDate {
fn type_info() -> postgres::PgTypeInfo {
postgres::PgTypeInfo::with_name("hammerfest_date")
}
fn compatible(ty: &postgres::PgTypeInfo) -> bool {
*ty == Self::type_info()
}
}
#[cfg(feature = "sqlx")]
impl<'r> sqlx::Decode<'r, Postgres> for HammerfestDate {
fn decode(value: postgres::PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
let mut decoder = postgres::types::PgRecordDecoder::new(value)?;
let month = decoder.try_decode::<crate::pg_num::PgU8>()?;
let day = decoder.try_decode::<crate::pg_num::PgU8>()?;
let weekday = decoder.try_decode::<crate::pg_num::PgU8>()?;
let month: u8 = u8::from(month);
let day: u8 = u8::from(day);
let weekday: u8 = u8::from(weekday);
Ok(Self { month, day, weekday })
}
}
#[cfg(feature = "sqlx")]
impl<'q> sqlx::Encode<'q, Postgres> for HammerfestDate {
fn encode_by_ref(&self, buf: &mut postgres::PgArgumentBuffer) -> sqlx::encode::IsNull {
let mut encoder = postgres::types::PgRecordEncoder::new(buf);
encoder.encode(crate::pg_num::PgU8::from(self.month));
encoder.encode(crate::pg_num::PgU8::from(self.day));
encoder.encode(crate::pg_num::PgU8::from(self.weekday));
encoder.finish();
sqlx::encode::IsNull::No
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestDateTime {
#[cfg_attr(feature = "serde", serde(flatten))]
pub date: HammerfestDate,
pub hour: u8,
pub minute: u8,
}
#[cfg(feature = "sqlx")]
impl sqlx::Type<Postgres> for HammerfestDateTime {
fn type_info() -> postgres::PgTypeInfo {
postgres::PgTypeInfo::with_name("hammerfest_date_time")
}
fn compatible(ty: &postgres::PgTypeInfo) -> bool {
*ty == Self::type_info()
}
}
#[cfg(feature = "sqlx")]
impl<'r> sqlx::Decode<'r, Postgres> for HammerfestDateTime {
fn decode(value: postgres::PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
let mut decoder = postgres::types::PgRecordDecoder::new(value)?;
let month = decoder.try_decode::<crate::pg_num::PgU8>()?;
let day = decoder.try_decode::<crate::pg_num::PgU8>()?;
let weekday = decoder.try_decode::<crate::pg_num::PgU8>()?;
let hour = decoder.try_decode::<crate::pg_num::PgU8>()?;
let minute = decoder.try_decode::<crate::pg_num::PgU8>()?;
let month: u8 = u8::from(month);
let day: u8 = u8::from(day);
let weekday: u8 = u8::from(weekday);
let hour: u8 = u8::from(hour);
let minute: u8 = u8::from(minute);
Ok(Self {
date: HammerfestDate { month, day, weekday },
hour,
minute,
})
}
}
#[cfg(feature = "sqlx")]
impl<'q> sqlx::Encode<'q, Postgres> for HammerfestDateTime {
fn encode_by_ref(&self, buf: &mut postgres::PgArgumentBuffer) -> sqlx::encode::IsNull {
let mut encoder = postgres::types::PgRecordEncoder::new(buf);
encoder.encode(crate::pg_num::PgU8::from(self.date.month));
encoder.encode(crate::pg_num::PgU8::from(self.date.day));
encoder.encode(crate::pg_num::PgU8::from(self.date.weekday));
encoder.encode(crate::pg_num::PgU8::from(self.hour));
encoder.encode(crate::pg_num::PgU8::from(self.minute));
encoder.finish();
sqlx::encode::IsNull::No
}
}
declare_decimal_id! {
pub struct HammerfestForumThemeId(u8);
pub type ParseError = HammerfestForumThemeIdParseError;
const BOUNDS = 0..100;
const SQL_NAME = "hammerfest_forum_theme_id";
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "HammerfestUser"))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestForumThemeIdRef {
pub server: HammerfestServer,
pub id: HammerfestForumThemeId,
}
declare_new_string! {
pub struct HammerfestForumThemeTitle(String);
pub type ParseError = HammerfestForumThemeTitleParseError;
const PATTERN = r"^.{1,100}$";
const SQL_NAME = "hammerfest_forum_theme_title";
}
declare_new_string! {
pub struct HammerfestForumThemeDescription(String);
pub type ParseError = HammerfestForumThemeDescriptionParseError;
const PATTERN = r"^.{1,500}$";
const SQL_NAME = "hammerfest_forum_theme_description";
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ShortHammerfestForumTheme {
pub server: HammerfestServer,
pub id: HammerfestForumThemeId,
pub name: HammerfestForumThemeTitle,
pub is_public: bool,
}
impl ShortHammerfestForumTheme {
pub const fn as_ref(&self) -> HammerfestForumThemeIdRef {
HammerfestForumThemeIdRef {
server: self.server,
id: self.id,
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestForumHomeResponse {
pub session: Option<HammerfestSessionUser>,
pub themes: Vec<HammerfestForumTheme>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestForumTheme {
#[cfg_attr(feature = "serde", serde(flatten))]
pub short: ShortHammerfestForumTheme,
pub description: HammerfestForumThemeDescription,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestForumThemePageResponse {
pub session: Option<HammerfestSessionUser>,
pub page: HammerfestForumThemePage,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestForumThemePage {
pub theme: ShortHammerfestForumTheme,
pub sticky: Vec<HammerfestForumThread>,
pub threads: HammerfestForumThreadListing,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestForumThreadListing {
pub page1: NonZeroU16,
pub pages: NonZeroU16,
pub items: Vec<HammerfestForumThread>, }
declare_decimal_id! {
pub struct HammerfestForumThreadId(u32);
pub type ParseError = HammerfestForumThreadIdParseError;
const BOUNDS = 0..1_000_000_000;
const SQL_NAME = "hammerfest_forum_thread_id";
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "HammerfestUser"))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestForumThreadIdRef {
pub server: HammerfestServer,
pub id: HammerfestForumThreadId,
}
declare_new_string! {
pub struct HammerfestForumThreadTitle(String);
pub type ParseError = HammerfestForumThreadTitleParseError;
const PATTERN = r"^.{1,100}$";
const SQL_NAME = "hammerfest_forum_thread_title";
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ShortHammerfestForumThread {
pub server: HammerfestServer,
pub id: HammerfestForumThreadId,
pub name: HammerfestForumThreadTitle,
pub is_closed: bool,
}
impl ShortHammerfestForumThread {
pub const fn as_ref(&self) -> HammerfestForumThreadIdRef {
HammerfestForumThreadIdRef {
server: self.server,
id: self.id,
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", serde(tag = "kind"))]
pub enum HammerfestForumThreadKind {
#[cfg_attr(feature = "serde", serde(rename = "sticky"))]
Sticky,
#[cfg_attr(feature = "serde", serde(rename = "regular"))]
Regular { latest_post_date: HammerfestDate },
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestForumThread {
#[cfg_attr(feature = "serde", serde(flatten))]
pub short: ShortHammerfestForumThread,
pub author: ShortHammerfestUser,
pub author_role: HammerfestForumRole,
#[cfg_attr(feature = "serde", serde(flatten))]
pub kind: HammerfestForumThreadKind,
pub reply_count: u16,
}
impl HammerfestForumThread {
pub const fn as_ref(&self) -> HammerfestForumThreadIdRef {
self.short.as_ref()
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestForumThreadPageResponse {
pub session: Option<HammerfestSessionUser>,
pub page: HammerfestForumThreadPage,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestForumThreadPage {
pub theme: ShortHammerfestForumTheme,
pub thread: ShortHammerfestForumThread,
pub posts: HammerfestForumPostListing,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestForumPostListing {
pub page1: NonZeroU16,
pub pages: NonZeroU16,
pub items: Vec<HammerfestForumPost>, }
declare_decimal_id! {
pub struct HammerfestForumPostId(u32);
pub type ParseError = HammerfestForumPostIdParseError;
const BOUNDS = 0..1_000_000_000;
const SQL_NAME = "hammerfest_forum_post_id";
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestForumPost {
pub id: Option<HammerfestForumPostId>,
pub author: HammerfestForumPostAuthor,
pub ctime: HammerfestDateTime,
pub content: HtmlFragment,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HammerfestForumPostAuthor {
#[cfg_attr(feature = "serde", serde(flatten))]
pub user: ShortHammerfestUser,
pub has_carrot: bool,
pub ladder_level: HammerfestLadderLevel,
pub rank: Option<u32>,
pub role: HammerfestForumRole,
}
declare_new_enum!(
pub enum HammerfestForumRole {
#[str("None")]
None,
#[str("Moderator")]
Moderator,
#[str("Administrator")]
Administrator,
}
pub type ParseError = HammerfestForumRoleParseError;
const SQL_NAME = "hammerfest_forum_role";
);
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GetHammerfestUserOptions {
pub server: HammerfestServer,
pub id: HammerfestUserId,
pub time: Option<Instant>,
}
#[async_trait]
#[auto_impl(&, Arc)]
pub trait HammerfestClient: Send + Sync {
async fn create_session(&self, options: &HammerfestCredentials) -> Result<HammerfestSession, AnyError>;
async fn test_session(
&self,
server: HammerfestServer,
key: &HammerfestSessionKey,
) -> Result<Option<HammerfestSession>, AnyError>;
async fn get_profile_by_id(
&self,
session: Option<&HammerfestSession>,
options: &HammerfestGetProfileByIdOptions,
) -> Result<HammerfestProfileResponse, AnyError>;
async fn get_own_items(&self, session: &HammerfestSession) -> Result<HammerfestInventoryResponse, AnyError>;
async fn get_own_godchildren(&self, session: &HammerfestSession) -> Result<HammerfestGodchildrenResponse, AnyError>;
async fn get_own_shop(&self, session: &HammerfestSession) -> Result<HammerfestShopResponse, AnyError>;
async fn get_forum_themes(
&self,
session: Option<&HammerfestSession>,
server: HammerfestServer,
) -> Result<HammerfestForumHomeResponse, AnyError>;
async fn get_forum_theme_page(
&self,
session: Option<&HammerfestSession>,
server: HammerfestServer,
theme_id: HammerfestForumThemeId,
page1: NonZeroU16,
) -> Result<HammerfestForumThemePageResponse, AnyError>;
async fn get_forum_thread_page(
&self,
session: Option<&HammerfestSession>,
server: HammerfestServer,
thread_id: HammerfestForumThreadId,
page1: NonZeroU16,
) -> Result<HammerfestForumThreadPageResponse, AnyError>;
}
#[async_trait]
#[auto_impl(&, Arc)]
pub trait HammerfestStore: Send + Sync {
async fn get_short_user(&self, options: &GetHammerfestUserOptions) -> Result<Option<ShortHammerfestUser>, AnyError>;
async fn get_user(&self, options: &GetHammerfestUserOptions) -> Result<Option<StoredHammerfestUser>, AnyError>;
async fn touch_short_user(&self, options: &ShortHammerfestUser) -> Result<StoredHammerfestUser, AnyError>;
async fn touch_shop(&self, response: &HammerfestShopResponse) -> Result<(), AnyError>;
async fn touch_profile(&self, response: &HammerfestProfileResponse) -> Result<(), AnyError>;
async fn touch_inventory(&self, response: &HammerfestInventoryResponse) -> Result<(), AnyError>;
async fn touch_godchildren(&self, response: &HammerfestGodchildrenResponse) -> Result<(), AnyError>;
async fn touch_theme_page(&self, response: &HammerfestForumThemePageResponse) -> Result<(), AnyError>;
async fn touch_thread_page(&self, response: &HammerfestForumThreadPageResponse) -> Result<(), AnyError>;
}
pub fn hammerfest_reply_count_to_page_count(reply_count: u16) -> NonZeroU16 {
let post_count = reply_count + 1;
const POSTS_PER_PAGE: u16 = 15;
let (q, r) = (post_count / POSTS_PER_PAGE, post_count % POSTS_PER_PAGE);
let pages = if r == 0 { q } else { q + 1 };
NonZeroU16::new(pages).unwrap()
}
#[cfg(test)]
mod test {
#[cfg(feature = "serde")]
use super::*;
#[cfg(feature = "serde")]
use crate::core::PeriodLower;
#[cfg(feature = "serde")]
use crate::temporal::{ForeignRetrieved, ForeignSnapshot};
#[cfg(feature = "serde")]
use std::fs;
#[cfg(feature = "serde")]
#[allow(clippy::unnecessary_wraps)]
fn get_nullable_short_hammerfest_user_alice() -> Option<ShortHammerfestUser> {
Some(ShortHammerfestUser {
server: HammerfestServer::HammerfestFr,
id: "123".parse().unwrap(),
username: "alice".parse().unwrap(),
})
}
#[cfg(feature = "serde")]
#[test]
fn read_nullable_short_hammerfest_user_alice() {
let s = fs::read_to_string("../../test-resources/core/hammerfest/nullable-short-hammerfest-user/alice/value.json")
.unwrap();
let actual: Option<ShortHammerfestUser> = serde_json::from_str(&s).unwrap();
let expected = get_nullable_short_hammerfest_user_alice();
assert_eq!(actual, expected);
}
#[cfg(feature = "serde")]
#[test]
fn write_nullable_short_hammerfest_user_alice() {
let value = get_nullable_short_hammerfest_user_alice();
let actual: String = serde_json::to_string_pretty(&value).unwrap();
let expected =
fs::read_to_string("../../test-resources/core/hammerfest/nullable-short-hammerfest-user/alice/value.json")
.unwrap();
assert_eq!(&actual, expected.trim());
}
#[cfg(feature = "serde")]
fn get_nullable_short_hammerfest_user_null() -> Option<ShortHammerfestUser> {
None
}
#[cfg(feature = "serde")]
#[test]
fn read_nullable_short_hammerfest_user_null() {
let s = fs::read_to_string("../../test-resources/core/hammerfest/nullable-short-hammerfest-user/null/value.json")
.unwrap();
let actual: Option<ShortHammerfestUser> = serde_json::from_str(&s).unwrap();
let expected = get_nullable_short_hammerfest_user_null();
assert_eq!(actual, expected);
}
#[cfg(feature = "serde")]
#[test]
fn write_nullable_short_hammerfest_user_null() {
let value = get_nullable_short_hammerfest_user_null();
let actual: String = serde_json::to_string_pretty(&value).unwrap();
let expected =
fs::read_to_string("../../test-resources/core/hammerfest/nullable-short-hammerfest-user/null/value.json")
.unwrap();
assert_eq!(&actual, expected.trim());
}
#[cfg(feature = "serde")]
fn get_short_hammerfest_user_demurgos() -> ShortHammerfestUser {
ShortHammerfestUser {
server: HammerfestServer::HfestNet,
id: "205769".parse().unwrap(),
username: "Demurgos".parse().unwrap(),
}
}
#[cfg(feature = "serde")]
#[test]
fn read_short_hammerfest_user_demurgos() {
let s =
fs::read_to_string("../../test-resources/core/hammerfest/short-hammerfest-user/demurgos/value.json").unwrap();
let actual: ShortHammerfestUser = serde_json::from_str(&s).unwrap();
let expected = get_short_hammerfest_user_demurgos();
assert_eq!(actual, expected);
}
#[cfg(feature = "serde")]
#[test]
fn write_short_hammerfest_user_demurgos() {
let value = get_short_hammerfest_user_demurgos();
let actual: String = serde_json::to_string_pretty(&value).unwrap();
let expected =
fs::read_to_string("../../test-resources/core/hammerfest/short-hammerfest-user/demurgos/value.json").unwrap();
assert_eq!(&actual, expected.trim());
}
#[cfg(feature = "serde")]
fn get_short_hammerfest_user_elseabora() -> ShortHammerfestUser {
ShortHammerfestUser {
server: HammerfestServer::HammerfestFr,
id: "127".parse().unwrap(),
username: "elseabora".parse().unwrap(),
}
}
#[cfg(feature = "serde")]
#[test]
fn read_short_hammerfest_user_elseabora() {
let s =
fs::read_to_string("../../test-resources/core/hammerfest/short-hammerfest-user/elseabora/value.json").unwrap();
let actual: ShortHammerfestUser = serde_json::from_str(&s).unwrap();
let expected = get_short_hammerfest_user_elseabora();
assert_eq!(actual, expected);
}
#[cfg(feature = "serde")]
#[test]
fn write_short_hammerfest_user_elseabora() {
let value = get_short_hammerfest_user_elseabora();
let actual: String = serde_json::to_string_pretty(&value).unwrap();
let expected =
fs::read_to_string("../../test-resources/core/hammerfest/short-hammerfest-user/elseabora/value.json").unwrap();
assert_eq!(&actual, expected.trim());
}
#[cfg(feature = "serde")]
fn get_hammerfest_user_alice() -> HammerfestUser {
let date = Instant::ymd_hms_milli(2021, 1, 1, 0, 0, 0, 1);
HammerfestUser {
server: HammerfestServer::HammerfestFr,
id: "123".parse().unwrap(),
username: "alice".parse().unwrap(),
archived_at: date,
etwin: VersionedEtwinLink::default(),
profile: Some(LatestTemporal {
latest: ForeignSnapshot {
period: PeriodLower::unbounded(date),
retrieved: ForeignRetrieved { latest: date },
value: StoredHammerfestProfile {
best_score: 102505,
best_level: 45,
game_completed: false,
items: ["0".parse().unwrap(), "78".parse().unwrap(), "1000".parse().unwrap()]
.into_iter()
.collect(),
quests: [
("0".parse().unwrap(), HammerfestQuestStatus::Complete),
("10".parse().unwrap(), HammerfestQuestStatus::Pending),
("20".parse().unwrap(), HammerfestQuestStatus::None),
]
.into_iter()
.collect(),
},
},
}),
inventory: Some(LatestTemporal {
latest: ForeignSnapshot {
period: PeriodLower::unbounded(date),
retrieved: ForeignRetrieved { latest: date },
value: [
("0".parse().unwrap(), 4520),
("10".parse().unwrap(), 5),
("78".parse().unwrap(), 11),
("1000".parse().unwrap(), 10532),
("1001".parse().unwrap(), 9),
]
.into_iter()
.collect(),
},
}),
}
}
#[cfg(feature = "serde")]
#[test]
fn read_hammerfest_user_alice() {
let s = fs::read_to_string("../../test-resources/core/hammerfest/hammerfest-user/alice/value.json").unwrap();
let actual: HammerfestUser = serde_json::from_str(&s).unwrap();
let expected = get_hammerfest_user_alice();
assert_eq!(actual, expected);
}
#[cfg(feature = "serde")]
#[test]
fn write_hammerfest_user_alice() {
let value = get_hammerfest_user_alice();
let actual: String = serde_json::to_string_pretty(&value).unwrap();
let expected = fs::read_to_string("../../test-resources/core/hammerfest/hammerfest-user/alice/value.json").unwrap();
assert_eq!(&actual, expected.trim());
}
}